refactor(kit,nuxt)!: drop nuxt 2 + ejs template compile support (#27706)

This commit is contained in:
Daniel Roe 2024-06-19 13:56:54 +01:00 committed by GitHub
parent 77685e43e4
commit f5ab7eb494
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 24 additions and 214 deletions

View File

@ -12,5 +12,4 @@ export default defineBuildConfig({
'vite', 'vite',
'h3', 'h3',
], ],
failOnWarn: false,
}) })

View File

@ -36,7 +36,6 @@
"ignore": "^5.3.1", "ignore": "^5.3.1",
"jiti": "^1.21.6", "jiti": "^1.21.6",
"klona": "^2.0.6", "klona": "^2.0.6",
"knitwork": "^1.1.0",
"mlly": "^1.7.1", "mlly": "^1.7.1",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"pkg-types": "^1.1.1", "pkg-types": "^1.1.1",
@ -49,9 +48,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/hash-sum": "1.0.2", "@types/hash-sum": "1.0.2",
"@types/lodash-es": "4.17.12",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"lodash-es": "4.17.21",
"nitropack": "2.9.6", "nitropack": "2.9.6",
"unbuild": "latest", "unbuild": "latest",
"vite": "5.3.1", "vite": "5.3.1",

View File

@ -29,23 +29,6 @@ export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nu
} }
} }
// Bridge compatibility check
if (isNuxt2(nuxt)) {
const bridgeRequirement = constraints.bridge
const hasBridge = !!(nuxt.options as any).bridge
if (bridgeRequirement === true && !hasBridge) {
issues.push({
name: 'bridge',
message: 'Nuxt bridge is required',
})
} else if (bridgeRequirement === false && hasBridge) {
issues.push({
name: 'bridge',
message: 'Nuxt bridge is not supported',
})
}
}
// Builder compatibility check // Builder compatibility check
if (constraints.builder && typeof nuxt.options.builder === 'string') { if (constraints.builder && typeof nuxt.options.builder === 'string') {
const currentBuilder = builderMap[nuxt.options.builder] || nuxt.options.builder const currentBuilder = builderMap[nuxt.options.builder] || nuxt.options.builder

View File

@ -35,4 +35,3 @@ export {
} from './internal/cjs' } from './internal/cjs'
export type { ResolveModuleOptions, RequireModuleOptions } from './internal/cjs' export type { ResolveModuleOptions, RequireModuleOptions } from './internal/cjs'
export { tryResolveModule } from './internal/esm' export { tryResolveModule } from './internal/esm'
export * from './internal/template'

View File

@ -1,47 +0,0 @@
import { promises as fsp } from 'node:fs'
// TODO: swap out when https://github.com/lodash/lodash/pull/5649 is merged
import { template as lodashTemplate } from 'lodash-es'
import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
import type { NuxtTemplate } from '@nuxt/schema'
import { logger } from '../logger'
import { toArray } from '../utils'
/** @deprecated */
// TODO: Remove support for compiling ejs templates in v4
export async function compileTemplate<T> (template: NuxtTemplate<T>, ctx: any) {
const data = { ...ctx, options: template.options }
if (template.src) {
try {
const srcContents = await fsp.readFile(template.src, 'utf-8')
return lodashTemplate(srcContents, {})(data)
} catch (err) {
logger.error('Error compiling template: ', template)
throw err
}
}
if (template.getContents) {
return template.getContents(data)
}
throw new Error('Invalid template: ' + JSON.stringify(template))
}
/** @deprecated */
const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\}"(?=,?$)/gm, r => JSON.parse(r).replace(/^\{(.*)\}$/, '$1'))
/** @deprecated */
const importSources = (sources: string | string[], { lazy = false } = {}) => {
return toArray(sources).map((src) => {
const safeVariableName = genSafeVariableName(src)
if (lazy) {
return `const ${safeVariableName} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
}
return genImport(src, safeVariableName)
}).join('\n')
}
/** @deprecated */
const importName = genSafeVariableName
/** @deprecated */
export const templateUtils = { serialize, importName, importSources }

View File

@ -1,7 +1,6 @@
import type { NuxtTemplate } from '@nuxt/schema' import type { NuxtTemplate } from '@nuxt/schema'
import { join, parse, relative } from 'pathe' import { join, parse, relative } from 'pathe'
import { kebabCase } from 'scule' import { kebabCase } from 'scule'
import { isNuxt2 } from './compatibility'
import { useNuxt } from './context' import { useNuxt } from './context'
import { logger } from './logger' import { logger } from './logger'
import { addTemplate } from './template' import { addTemplate } from './template'
@ -11,21 +10,6 @@ export function addLayout (this: any, template: NuxtTemplate | string, name?: st
const { filename, src } = addTemplate(template) const { filename, src } = addTemplate(template)
const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '') const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '')
if (isNuxt2(nuxt)) {
// Nuxt 2 adds layouts in options
const layout = (nuxt.options as any).layouts[layoutName]
if (layout) {
return logger.warn(
`Not overriding \`${layoutName}\` (provided by \`${layout}\`) with \`${src || filename}\`.`,
)
}
(nuxt.options as any).layouts[layoutName] = `./${filename}`
if (name === 'error') {
this.addErrorLayout(filename)
}
return
}
// Nuxt 3 adds layouts on app // Nuxt 3 adds layouts on app
nuxt.hook('app:templates', (app) => { nuxt.hook('app:templates', (app) => {
if (layoutName in app.layouts) { if (layoutName in app.layouts) {

View File

@ -1,13 +1,10 @@
import { promises as fsp } from 'node:fs'
import { performance } from 'node:perf_hooks' import { performance } from 'node:perf_hooks'
import { defu } from 'defu' import { defu } from 'defu'
import { applyDefaults } from 'untyped' import { applyDefaults } from 'untyped'
import { dirname } from 'pathe' import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions } from '@nuxt/schema'
import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
import { logger } from '../logger' import { logger } from '../logger'
import { nuxtCtx, tryUseNuxt, useNuxt } from '../context' import { tryUseNuxt, useNuxt } from '../context'
import { checkNuxtCompatibility, isNuxt2 } from '../compatibility' import { checkNuxtCompatibility } from '../compatibility'
import { compileTemplate, templateUtils } from '../internal/template'
/** /**
* Define a Nuxt module, automatically merging defaults with user provided options, installing * Define a Nuxt module, automatically merging defaults with user provided options, installing
@ -58,9 +55,6 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
} }
} }
// Prepare
nuxt2Shims(nuxt)
// Resolve module and options // Resolve module and options
const _options = await getOptions(inlineOptions, nuxt) const _options = await getOptions(inlineOptions, nuxt)
@ -100,53 +94,3 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
return normalizedModule as NuxtModule<OptionsT> return normalizedModule as NuxtModule<OptionsT>
} }
// -- Nuxt 2 compatibility shims --
const NUXT2_SHIMS_KEY = '__nuxt2_shims_key__'
function nuxt2Shims (nuxt: Nuxt) {
// Avoid duplicate install and only apply to Nuxt2
if (!isNuxt2(nuxt) || nuxt[NUXT2_SHIMS_KEY as keyof Nuxt]) { return }
nuxt[NUXT2_SHIMS_KEY as keyof Nuxt] = true
// Allow using nuxt.hooks
// @ts-expect-error Nuxt 2 extends hookable
nuxt.hooks = nuxt
// Allow using useNuxt()
if (!nuxtCtx.tryUse()) {
nuxtCtx.set(nuxt)
nuxt.hook('close', () => nuxtCtx.unset())
}
// Support virtual templates with getContents() by writing them to .nuxt directory
let virtualTemplates: ResolvedNuxtTemplate[]
// @ts-expect-error Nuxt 2 hook
nuxt.hook('builder:prepared', (_builder, buildOptions) => {
virtualTemplates = buildOptions.templates.filter((t: any) => t.getContents)
for (const template of virtualTemplates) {
buildOptions.templates.splice(buildOptions.templates.indexOf(template), 1)
}
})
// @ts-expect-error Nuxt 2 hook
nuxt.hook('build:templates', async (templates) => {
const context = {
nuxt,
utils: templateUtils,
app: {
dir: nuxt.options.srcDir,
extensions: nuxt.options.extensions,
plugins: nuxt.options.plugins,
templates: [
...templates.templatesFiles,
...virtualTemplates,
],
templateVars: templates.templateVars,
},
}
for await (const template of virtualTemplates) {
const contents = await compileTemplate({ ...template, src: '' }, context)
await fsp.mkdir(dirname(template.dst), { recursive: true })
await fsp.writeFile(template.dst, contents)
}
})
}

View File

@ -2,7 +2,6 @@ import { existsSync, promises as fsp, lstatSync } from 'node:fs'
import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema' import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema'
import { dirname, isAbsolute, join, resolve } from 'pathe' import { dirname, isAbsolute, join, resolve } from 'pathe'
import { defu } from 'defu' import { defu } from 'defu'
import { isNuxt2 } from '../compatibility'
import { useNuxt } from '../context' import { useNuxt } from '../context'
import { requireModule } from '../internal/cjs' import { requireModule } from '../internal/cjs'
import { importModule } from '../internal/esm' import { importModule } from '../internal/esm'
@ -27,12 +26,7 @@ export async function installModule<
} }
// Call module // Call module
const res = ( const res = await nuxtModule(inlineOptions, nuxt) ?? {}
isNuxt2()
// @ts-expect-error Nuxt 2 `moduleContainer` is not typed
? await nuxtModule.call(nuxt.moduleContainer, inlineOptions, nuxt)
: await nuxtModule(inlineOptions, nuxt)
) ?? {}
if (res === false /* setup aborted */) { if (res === false /* setup aborted */) {
return return
} }

View File

@ -2,18 +2,11 @@ import type { NuxtHooks, NuxtMiddleware } from '@nuxt/schema'
import type { NitroRouteConfig } from 'nitropack' import type { NitroRouteConfig } from 'nitropack'
import { defu } from 'defu' import { defu } from 'defu'
import { useNuxt } from './context' import { useNuxt } from './context'
import { isNuxt2 } from './compatibility'
import { logger } from './logger' import { logger } from './logger'
import { toArray } from './utils' import { toArray } from './utils'
export function extendPages (cb: NuxtHooks['pages:extend']) { export function extendPages (cb: NuxtHooks['pages:extend']) {
const nuxt = useNuxt() useNuxt().hook('pages:extend', cb)
if (isNuxt2(nuxt)) {
// @ts-expect-error TODO: Nuxt 2 hook
nuxt.hook('build:extendRoutes', cb)
} else {
nuxt.hook('pages:extend', cb)
}
} }
export interface ExtendRouteRulesOptions { export interface ExtendRouteRulesOptions {

View File

@ -1,7 +1,7 @@
import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs' import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs'
import { dirname, join, relative, resolve } from 'pathe' import { dirname, join, relative, resolve } from 'pathe'
import { defu } from 'defu' import { defu } from 'defu'
import { compileTemplate as _compileTemplate, findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils } from '@nuxt/kit' import { findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath } from '@nuxt/kit'
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema' import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
import * as defaultTemplates from './templates' import * as defaultTemplates from './templates'
@ -53,9 +53,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
} }
// Compile templates into vfs // Compile templates into vfs
// TODO: remove utils in v4 const templateContext = { nuxt, app }
const templateContext = { utils: templateUtils, nuxt, app }
const compileTemplate = nuxt.options.experimental.compileTemplate ? _compileTemplate : futureCompileTemplate
const writes: Array<() => void> = [] const writes: Array<() => void> = []
const changedTemplates: Array<ResolvedNuxtTemplate<any>> = [] const changedTemplates: Array<ResolvedNuxtTemplate<any>> = []
@ -113,7 +111,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
} }
/** @internal */ /** @internal */
async function futureCompileTemplate<T> (template: NuxtTemplate<T>, ctx: { nuxt: Nuxt, app: NuxtApp, utils?: unknown }) { async function compileTemplate<T> (template: NuxtTemplate<T>, ctx: { nuxt: Nuxt, app: NuxtApp, utils?: unknown }) {
delete ctx.utils delete ctx.utils
if (template.src) { if (template.src) {

View File

@ -26,8 +26,6 @@ export default defineUntypedSchema({
* app: 'app' * app: 'app'
* }, * },
* experimental: { * experimental: {
* compileTemplate: true,
* templateUtils: true,
* relativeWatchPaths: true, * relativeWatchPaths: true,
* resetAsyncDataToUndefined: true, * resetAsyncDataToUndefined: true,
* defaults: { * defaults: {
@ -467,31 +465,6 @@ export default defineUntypedSchema({
*/ */
clientNodeCompat: false, clientNodeCompat: false,
/**
* Whether to use `lodash.template` to compile Nuxt templates.
*
* This flag will be removed with the release of v4 and exists only for
* advance testing within Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
*/
compileTemplate: {
async $resolve (val, get) {
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
},
},
/**
* Whether to provide a legacy `templateUtils` object (with `serialize`,
* `importName` and `importSources`) when compiling Nuxt templates.
*
* This flag will be removed with the release of v4 and exists only for
* advance testing within Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
*/
templateUtils: {
async $resolve (val, get) {
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
},
},
/** /**
* Whether to provide relative paths in the `builder:watch` hook. * Whether to provide relative paths in the `builder:watch` hook.
* *

View File

@ -1,8 +1,8 @@
import { runInNewContext } from 'node:vm'
import { join, resolve } from 'node:path' import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { promises as fsp } from 'node:fs' import { promises as fsp } from 'node:fs'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import { template } from 'lodash-es'
import genericMessages from '../templates/messages.json' import genericMessages from '../templates/messages.json'
const templatesRoot = fileURLToPath(new URL('..', import.meta.url)) const templatesRoot = fileURLToPath(new URL('..', import.meta.url))
@ -15,6 +15,8 @@ export const DevRenderingPlugin = () => {
async transformIndexHtml (html: string, context) { async transformIndexHtml (html: string, context) {
const page = context.originalUrl || '/' const page = context.originalUrl || '/'
if (page.endsWith('.png')) { return }
if (page === '/') { if (page === '/') {
const templateNames = await fsp.readdir(r('templates')) const templateNames = await fsp.readdir(r('templates'))
const serializedData = JSON.stringify({ templateNames }) const serializedData = JSON.stringify({ templateNames })
@ -25,11 +27,19 @@ export const DevRenderingPlugin = () => {
const messages = JSON.parse(await fsp.readFile(r(page, 'messages.json'), 'utf-8')) const messages = JSON.parse(await fsp.readFile(r(page, 'messages.json'), 'utf-8'))
return template(contents, { const chunks = contents.split(/\{{2,3}[^{}]+\}{2,3}/g)
interpolate: /\{\{\{?([\s\S]+?)\}?\}\}/g, let templateString = chunks.shift()
})({ for (const expression of contents.matchAll(/\{{2,3}([^{}]+)\}{2,3}/g)) {
const value = runInNewContext(expression[1].trim(), {
messages: { ...genericMessages, ...messages }, messages: { ...genericMessages, ...messages },
}) })
templateString += `${value}${chunks.shift()}`
}
if (chunks.length > 0) {
templateString += chunks.join('')
}
return templateString
}, },
} }
} }

View File

@ -20,7 +20,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/html-minifier": "4.0.5", "@types/html-minifier": "4.0.5",
"@types/lodash-es": "4.17.12",
"@unocss/reset": "0.61.0", "@unocss/reset": "0.61.0",
"critters": "0.0.22", "critters": "0.0.22",
"execa": "9.2.0", "execa": "9.2.0",
@ -28,7 +27,6 @@
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"jiti": "1.21.6", "jiti": "1.21.6",
"knitwork": "1.1.0", "knitwork": "1.1.0",
"lodash-es": "4.17.21",
"pathe": "1.1.2", "pathe": "1.1.2",
"prettier": "3.3.2", "prettier": "3.3.2",
"scule": "1.3.0", "scule": "1.3.0",

View File

@ -191,9 +191,6 @@ importers:
klona: klona:
specifier: ^2.0.6 specifier: ^2.0.6
version: 2.0.6 version: 2.0.6
knitwork:
specifier: ^1.1.0
version: 1.1.0
mlly: mlly:
specifier: ^1.7.1 specifier: ^1.7.1
version: 1.7.1 version: 1.7.1
@ -225,15 +222,9 @@ importers:
'@types/hash-sum': '@types/hash-sum':
specifier: 1.0.2 specifier: 1.0.2
version: 1.0.2 version: 1.0.2
'@types/lodash-es':
specifier: 4.17.12
version: 4.17.12
'@types/semver': '@types/semver':
specifier: 7.5.8 specifier: 7.5.8
version: 7.5.8 version: 7.5.8
lodash-es:
specifier: 4.17.21
version: 4.17.21
nitropack: nitropack:
specifier: 2.9.6 specifier: 2.9.6
version: 2.9.6(encoding@0.1.13)(magicast@0.3.4) version: 2.9.6(encoding@0.1.13)(magicast@0.3.4)
@ -584,9 +575,6 @@ importers:
'@types/html-minifier': '@types/html-minifier':
specifier: 4.0.5 specifier: 4.0.5
version: 4.0.5 version: 4.0.5
'@types/lodash-es':
specifier: 4.17.12
version: 4.17.12
'@unocss/reset': '@unocss/reset':
specifier: 0.61.0 specifier: 0.61.0
version: 0.61.0 version: 0.61.0
@ -608,9 +596,6 @@ importers:
knitwork: knitwork:
specifier: 1.1.0 specifier: 1.1.0
version: 1.1.0 version: 1.1.0
lodash-es:
specifier: 4.17.21
version: 4.17.21
pathe: pathe:
specifier: 1.1.2 specifier: 1.1.2
version: 1.1.2 version: 1.1.2