Skip to content

Commit 172b340

Browse files
committed
feat: Use "native" page meta extraction
1 parent 004e17e commit 172b340

File tree

6 files changed

+60
-150
lines changed

6 files changed

+60
-150
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vuepal",
3-
"version": "3.0.0-alpha.2",
3+
"version": "3.0.0-alpha.3",
44
"description": "Nuxt+Vue Drupal Integration",
55
"license": "MIT",
66
"homepage": "https://github.com/liip/vuepal",

playground/nuxt.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default defineNuxtConfig({
7272
},
7373
frontendRouting: {
7474
enabled: true,
75-
langcodes: ['de', 'en'],
75+
defaultLanguage: 'de',
7676
outputPath: './../drupal/frontend_routing.settings.yml',
7777
},
7878
drupalRoute: {

src/build/classes/FileCache.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/build/classes/ModuleHelper.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { relative } from 'pathe'
1313
import type { Nuxt, NuxtPlugin, ResolvedNuxtTemplate } from 'nuxt/schema'
1414
import { fileExists, logger } from '../helpers'
1515
import { useGraphqlModuleContext } from 'nuxt-graphql-middleware/utils'
16-
import { FileCache } from './FileCache'
1716
import type { ModuleOptions } from '../types/options'
1817
import { FEATURE_KEYS, type ValidFeature } from '../features'
1918

@@ -75,8 +74,6 @@ export class ModuleHelper {
7574

7675
public readonly graphql: GraphqlModuleContext
7776

78-
public readonly caches: FileCache<unknown>[] = []
79-
8077
private enabledFeatures = new Set<ValidFeature>()
8178

8279
constructor(
@@ -360,16 +357,6 @@ export class ModuleHelper {
360357
this.graphql.addImportFile(resolved)
361358
}
362359

363-
public createFileCache<T>(): FileCache<T> {
364-
const cache = new FileCache<T>()
365-
this.caches.push(cache)
366-
return cache
367-
}
368-
369-
public clearFilePathCaches(filePath: string) {
370-
this.caches.forEach((cache) => cache.clear(filePath))
371-
}
372-
373360
public hasFeatureEnabled(key: ValidFeature): boolean {
374361
return this.enabledFeatures.has(key)
375362
}
Lines changed: 58 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,49 @@
1-
import fs from 'node:fs'
2-
import { addTemplate } from '@nuxt/kit'
1+
import { addTemplate, extendPages } from '@nuxt/kit'
32
import { relative } from 'pathe'
43
import type { NuxtPage } from '@nuxt/schema'
54
import { defineVuepalFeature } from '../defineFeature'
6-
import type { FileCache } from '../../classes/FileCache'
7-
import type { ModuleHelper } from '../../classes/ModuleHelper'
85
import { logger } from '../../helpers'
96

107
function nonNullable<T>(value: T): value is NonNullable<T> {
118
return value !== null && value !== undefined
129
}
1310

14-
/**
15-
* Extracts the language mapping.
16-
*/
17-
const extractLanguageMapping = (
18-
code: string,
19-
): Record<string, string> | undefined => {
20-
const RGX = /languageMapping:\s*\{([^}]+)\}/
21-
const matches = code.match(RGX)
22-
23-
const match = matches?.[1]
24-
25-
if (!match) {
26-
return
27-
}
28-
29-
const jsonString = `{${match.trim().replace(/'/g, '"')}}`
30-
31-
const fn = new Function(`return ${jsonString}`)
32-
const mapping = fn()
33-
if (typeof mapping !== 'object') {
34-
return
35-
}
36-
37-
for (const key in mapping) {
38-
if (typeof key !== 'string') {
39-
return
40-
}
41-
42-
const value = mapping[key]
43-
44-
if (typeof value !== 'string') {
45-
return
46-
}
47-
}
48-
49-
return mapping
50-
}
51-
5211
type ExtractedPage = {
5312
filePath: string
5413
isDrupalFrontendRoute: boolean
5514
yml?: string
5615
}
5716

5817
class PageCollector {
59-
private cache: FileCache<ExtractedPage>
6018
private templateContents = ''
6119

62-
constructor(
63-
helper: ModuleHelper,
64-
private langcodes: string[],
65-
) {
66-
this.cache = helper.createFileCache()
67-
}
68-
69-
private async handlePage(page: NuxtPage): Promise<ExtractedPage | undefined> {
70-
if (!page.file) {
71-
return
72-
}
73-
74-
if (!page.name) {
75-
return
76-
}
20+
constructor(private defaultLanguage: string) {}
7721

22+
private handlePage(page: NuxtPage): ExtractedPage | undefined {
7823
const name = page.name
7924

80-
const cached = this.cache.get(page.file)
81-
if (cached) {
82-
return cached
25+
if (!page.file) {
26+
return
8327
}
8428

85-
const contents = await fs.promises
86-
.readFile(page.file)
87-
.then((v) => v.toString())
88-
8929
const extracted: ExtractedPage = {
9030
filePath: page.file,
9131
isDrupalFrontendRoute: false,
9232
}
9333

94-
if (contents.includes('drupalFrontendRoute')) {
34+
if (page.meta?.drupalFrontendRoute) {
9535
extracted.isDrupalFrontendRoute = true
9636
try {
97-
const languageMapping = extractLanguageMapping(contents)
98-
if (languageMapping) {
99-
const mapping = Object.entries(languageMapping)
100-
.map(([langcode, path]) => {
101-
return ` ${langcode}: '${path}'`
102-
})
103-
.join('\n')
104-
extracted.yml = ` ${name}:\n aliases:\n${mapping}`
105-
}
37+
const mapping = Object.entries({
38+
...(page.meta.languageMapping || {}),
39+
[this.defaultLanguage]: page.path,
40+
})
41+
.map(([langcode, path]) => {
42+
return ` ${langcode}: '${path}'`
43+
})
44+
.sort()
45+
.join('\n')
46+
extracted.yml = ` ${name}:\n aliases:\n${mapping}`
10647
} catch (e) {
10748
logger.warn(
10849
`Failed to extract language mapping in page "${page.file}"`,
@@ -111,19 +52,15 @@ class PageCollector {
11152
}
11253
}
11354

114-
this.cache.set(page.file, extracted)
11555
return extracted
11656
}
11757

118-
public async handlePages(pages: NuxtPage[]) {
119-
const mapped = await Promise.all(
120-
pages.map((page) => this.handlePage(page)),
121-
).then((result) =>
122-
result
123-
.map((v) => v?.yml)
124-
.filter(nonNullable)
125-
.sort(),
126-
)
58+
public handlePages(pages: NuxtPage[]) {
59+
const mapped = pages
60+
.map((page) => this.handlePage(page))
61+
.map((v) => v?.yml)
62+
.filter(nonNullable)
63+
.sort()
12764

12865
this.templateContents = `keys:\n${mapped.join('\n')}`
12966
}
@@ -135,9 +72,9 @@ class PageCollector {
13572

13673
export default defineVuepalFeature<{
13774
/**
138-
* The supported language codes.
75+
* The default language.
13976
*/
140-
langcodes: string[]
77+
defaultLanguage: string
14178

14279
/**
14380
* The output path of the generated YML file.
@@ -180,30 +117,53 @@ export {}
180117
}
181118

182119
const outputPath = options?.outputPath
183-
const langcodes = options?.langcodes
184120
if (!outputPath) {
185121
throw new Error(`Missing required option "frontendRouting.outputPath".`)
186122
}
187123

188-
if (!langcodes?.length) {
189-
throw new Error(`Missing required option "frontendRouting.langcodes".`)
124+
if (!options.defaultLanguage) {
125+
throw new Error(
126+
`Missing required option "frontendRouting.defaultLanguage".`,
127+
)
128+
}
129+
130+
const languageNegotiationModuleIndex =
131+
helper.nuxt.options.modules.findIndex(
132+
(v) => v === 'nuxt-language-negotiation',
133+
)
134+
135+
// Module is installed.
136+
if (languageNegotiationModuleIndex !== -1) {
137+
const vuepalIndex = helper.nuxt.options.modules.findIndex(
138+
(v) => v === 'vuepal',
139+
)
140+
141+
// Make sure that nuxt-language-negotiation runs after vuepal, or else
142+
// the routes have already been translated.
143+
if (languageNegotiationModuleIndex < vuepalIndex) {
144+
throw new Error(
145+
'The "nuxt-language-negotiation" module must be put after "vuepal" in nuxt.config.ts',
146+
)
147+
}
190148
}
191149

192-
const collector = new PageCollector(helper, langcodes)
150+
const collector = new PageCollector(options.defaultLanguage)
193151

194152
addTemplate({
195153
filename: helper.resolvers.root.resolve(options.outputPath),
196154
write: true,
197155
getContents: () => collector.getTemplateContents(),
198156
})
199157

200-
// This hook is called by Nuxt when any page changes.
201-
// During development, the hook is called *after* the builder:watch
202-
// event.
203-
helper.nuxt.hooks.hook('pages:resolved', async (pages) => {
204-
helper.logDebug('frontendRouting: pages:resolved start')
205-
await collector.handlePages(pages)
206-
helper.logDebug('frontendRouting: pages:resolved done')
158+
helper.nuxt.options.experimental.scanPageMeta = true
159+
helper.nuxt.options.experimental.extraPageMetaExtractionKeys ||= []
160+
helper.nuxt.options.experimental.extraPageMetaExtractionKeys.push(
161+
'languageMapping',
162+
'drupalFrontendRoute',
163+
)
164+
165+
extendPages((pages) => {
166+
collector.handlePages(pages)
207167
})
208168
},
209169
})

src/module.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { fileURLToPath } from 'node:url'
2-
import { extname } from 'pathe'
32
import { name, version } from '../package.json'
43
import { defineNuxtModule, hasNuxtModule, installModule } from '@nuxt/kit'
54
import { ModuleHelper } from './build/classes/ModuleHelper'
@@ -127,26 +126,5 @@ declare module '#vuepal-build/adapter' {
127126
})
128127

129128
helper.applyBuildConfig()
130-
131-
if (helper.isDev) {
132-
const POSSIBLE_EXTENSIONS = ['.js', '.ts', '.vue', '.mjs']
133-
nuxt.hook('builder:watch', (_event, providedFilePath) => {
134-
const fileExtension = extname(providedFilePath).toLowerCase()
135-
if (!POSSIBLE_EXTENSIONS.includes(fileExtension)) {
136-
return
137-
}
138-
139-
// Hack: This is supposed to be absolute. But it's not. Sometimes.
140-
// Let's make sure it's really absolute. We have to assume that the path
141-
// is actually relative to the source directory. If not, HMR will be
142-
// broken.
143-
const pathAbsolute = providedFilePath.startsWith('/')
144-
? providedFilePath
145-
: helper.resolvers.src.resolve(providedFilePath)
146-
147-
helper.logDebug('builder:watch clear file caches')
148-
helper.clearFilePathCaches(pathAbsolute)
149-
})
150-
}
151129
},
152130
})

0 commit comments

Comments
 (0)