11import fs from 'node:fs'
22import { addTemplate } from '@nuxt/kit'
3- import { stringify } from 'yaml'
43import type { NuxtPage } from '@nuxt/schema'
5- import { nonNullable } from './../../../runtime/helpers/type'
64import { defineVuepalFeature } from '../defineFeature'
5+ import type { FileCache } from '../../classes/FileCache'
6+ import type { ModuleHelper } from '../../classes/ModuleHelper'
7+ import { logger } from '../../helpers'
8+
9+ function nonNullable < T > ( value : T ) : value is NonNullable < T > {
10+ return value !== null && value !== undefined
11+ }
712
813/**
914 * Extracts the language mapping.
@@ -20,97 +25,111 @@ const extractLanguageMapping = (
2025 return
2126 }
2227
23- try {
24- const jsonString = `{${ match . trim ( ) . replace ( / ' / g, '"' ) } }`
28+ const jsonString = `{${ match . trim ( ) . replace ( / ' / g, '"' ) } }`
2529
26- const mapping = eval ( `(${ jsonString } )` )
27- if ( typeof mapping !== 'object' ) {
30+ const fn = new Function ( `return ${ jsonString } ` )
31+ const mapping = fn ( )
32+ if ( typeof mapping !== 'object' ) {
33+ return
34+ }
35+
36+ for ( const key in mapping ) {
37+ if ( typeof key !== 'string' ) {
2838 return
2939 }
3040
31- for ( const key in mapping ) {
32- if ( typeof key !== 'string' ) {
33- return
34- }
35-
36- const value = mapping [ key ]
41+ const value = mapping [ key ]
3742
38- if ( typeof value !== 'string' ) {
39- return
40- }
43+ if ( typeof value !== 'string' ) {
44+ return
4145 }
42-
43- return mapping
44- } catch ( e ) {
45- console . log ( 'Error in Vuepal:' )
46- console . log ( e )
4746 }
48- }
4947
50- type ExtractedDrupalFrontendRoute = {
51- aliases : Record < string , string >
52- path : string
53- name : string
48+ return mapping
5449}
5550
56- type DrupalFrontendRouteEntry = {
57- aliases : Record < string , string >
51+ type ExtractedPage = {
52+ filePath : string
53+ isDrupalFrontendRoute : boolean
54+ yml ?: string
5855}
5956
60- const extractFrontendRouteData = async (
61- page : NuxtPage ,
62- isSingleLanguage : boolean ,
63- ) : Promise < ExtractedDrupalFrontendRoute | undefined > => {
64- if ( ! page . file || ! page . name ) {
65- return
66- }
67- const code = await fs . promises . readFile ( page . file ) . then ( ( v ) => v . toString ( ) )
57+ class PageCollector {
58+ private cache : FileCache < ExtractedPage >
59+ private templateContents = ''
6860
69- if ( ! code . includes ( 'drupalFrontendRoute' ) ) {
70- return
61+ constructor (
62+ helper : ModuleHelper ,
63+ private langcodes : string [ ] ,
64+ ) {
65+ this . cache = helper . createFileCache ( )
7166 }
7267
73- const aliases = extractLanguageMapping ( code )
74- if ( ! aliases && ! isSingleLanguage ) {
75- return
76- }
77- return {
78- path : page . path ,
79- name : page . name ,
80- aliases : aliases || { } ,
81- }
82- }
68+ private async handlePage ( page : NuxtPage ) : Promise < ExtractedPage | undefined > {
69+ if ( ! page . file ) {
70+ return
71+ }
72+
73+ if ( ! page . name ) {
74+ return
75+ }
76+
77+ const name = page . name
78+
79+ const cached = this . cache . get ( page . file )
80+ if ( cached ) {
81+ return cached
82+ }
8383
84- const generateFrontendRoutesYaml = (
85- pages : NuxtPage [ ] ,
86- langcodes : string [ ] ,
87- ) : Promise < string > => {
88- const isSingleLanguage = langcodes . length === 1
89- return Promise . all (
90- pages . map ( ( v ) => extractFrontendRouteData ( v , isSingleLanguage ) ) ,
91- ) . then ( ( routes ) => {
92- const sortedRoutes = routes
93- . filter ( nonNullable )
94- . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
95- const keys = sortedRoutes . reduce < Record < string , DrupalFrontendRouteEntry > > (
96- ( acc , v ) => {
97- const allLangcodes : Record < string , string > = langcodes . reduce <
98- Record < string , string >
99- > ( ( acc , langcode ) => {
100- acc [ langcode ] = v . aliases [ langcode ] || v . path
101- return acc
102- } , { } )
103- acc [ v . name ] = {
104- aliases : allLangcodes ,
84+ const contents = await fs . promises
85+ . readFile ( page . file )
86+ . then ( ( v ) => v . toString ( ) )
87+
88+ const extracted : ExtractedPage = {
89+ filePath : page . file ,
90+ isDrupalFrontendRoute : false ,
91+ }
92+
93+ if ( contents . includes ( 'drupalFrontendRoute' ) ) {
94+ extracted . isDrupalFrontendRoute = true
95+ try {
96+ const languageMapping = extractLanguageMapping ( contents )
97+ if ( languageMapping ) {
98+ const mapping = Object . entries ( languageMapping )
99+ . map ( ( [ langcode , path ] ) => {
100+ return ` ${ langcode } : '${ path } '`
101+ } )
102+ . join ( '\n' )
103+ extracted . yml = ` ${ name } :\n aliases:\n${ mapping } `
105104 }
105+ } catch ( e ) {
106+ logger . warn (
107+ `Failed to extract language mapping in page "${ page . file } "` ,
108+ e ,
109+ )
110+ }
111+ }
112+
113+ this . cache . set ( page . file , extracted )
114+ return extracted
115+ }
106116
107- return acc
108- } ,
109- { } ,
117+ public async handlePages ( pages : NuxtPage [ ] ) {
118+ const mapped = await Promise . all (
119+ pages . map ( ( page ) => this . handlePage ( page ) ) ,
120+ ) . then ( ( result ) =>
121+ result
122+ . map ( ( v ) => v ?. yml )
123+ . filter ( nonNullable )
124+ . sort ( ) ,
110125 )
111126
112- return stringify ( { keys } , { sortMapEntries : true } )
113- } )
127+ this . templateContents = `keys:\n${ mapped . join ( '\n' ) } `
128+ }
129+
130+ getTemplateContents ( ) : string {
131+ return this . templateContents
132+ }
114133}
115134
116135export default defineVuepalFeature < {
@@ -129,24 +148,6 @@ export default defineVuepalFeature<{
129148 name : 'frontendRouting' ,
130149 description : '' ,
131150 setup ( helper , options ) {
132- if ( ! helper . isModuleBuild && options ?. outputPath ) {
133- let templateContents = ''
134- const templatePath = helper . resolvers . root . resolve ( options . outputPath )
135-
136- addTemplate ( {
137- filename : templatePath ,
138- write : true ,
139- getContents : ( ctx ) => {
140- return ''
141- } ,
142- } )
143- }
144-
145- // function buildTemplateContents() {
146- // const pages: NuxtPage[] = ctx.app.pages || []
147- // return generateFrontendRoutesYaml(pages, options.langcodes)
148- // }
149-
150151 helper . addTemplate ( 'page-meta' , null , ( ) => {
151152 return `
152153declare module "#app" {
@@ -155,7 +156,7 @@ declare module "#app" {
155156 * If set to true, this route is considered a "Drupal Frontend Route".
156157 * It will generate an entry in the frontend_routing.settings.yml file.
157158 *
158- * This will make sure that the node connected to this route will always
159+ * This will make sure that the node connected to this Nuxt page will always
159160 * have the paths defined in this component. It will not be possible to
160161 * override the path in Drupal.
161162 */
@@ -166,5 +167,36 @@ declare module "#app" {
166167export {}
167168`
168169 } )
170+
171+ if ( helper . isModuleBuild ) {
172+ return
173+ }
174+
175+ const outputPath = options ?. outputPath
176+ const langcodes = options ?. langcodes
177+ if ( ! outputPath ) {
178+ throw new Error ( `Missing required option "frontendRouting.outputPath".` )
179+ }
180+
181+ if ( ! langcodes ?. length ) {
182+ throw new Error ( `Missing required option "frontendRouting.langcodes".` )
183+ }
184+
185+ const collector = new PageCollector ( helper , langcodes )
186+
187+ addTemplate ( {
188+ filename : helper . resolvers . root . resolve ( options . outputPath ) ,
189+ write : true ,
190+ getContents : ( ) => collector . getTemplateContents ( ) ,
191+ } )
192+
193+ // This hook is called by Nuxt when any page changes.
194+ // During development, the hook is called *after* the builder:watch
195+ // event.
196+ helper . nuxt . hooks . hook ( 'pages:resolved' , async ( pages ) => {
197+ helper . logDebug ( 'frontendRouting: pages:resolved start' )
198+ await collector . handlePages ( pages )
199+ helper . logDebug ( 'frontendRouting: pages:resolved done' )
200+ } )
169201 } ,
170202} )
0 commit comments