feat(schema): v4 opt-in with future.compatibilityVersion (#26925)

This commit is contained in:
Daniel Roe 2024-04-30 20:34:32 +01:00 committed by GitHub
parent ed79a1d4bf
commit 505e706dcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 85 additions and 10 deletions

View File

@ -198,6 +198,7 @@ jobs:
builder: ["vite", "webpack"] builder: ["vite", "webpack"]
context: ["async", "default"] context: ["async", "default"]
manifest: ["manifest-on", "manifest-off"] manifest: ["manifest-on", "manifest-off"]
version: ["v4", "v3"]
node: [18] node: [18]
exclude: exclude:
- env: "dev" - env: "dev"
@ -234,6 +235,7 @@ jobs:
TEST_BUILDER: ${{ matrix.builder }} TEST_BUILDER: ${{ matrix.builder }}
TEST_MANIFEST: ${{ matrix.manifest }} TEST_MANIFEST: ${{ matrix.manifest }}
TEST_CONTEXT: ${{ matrix.context }} TEST_CONTEXT: ${{ matrix.context }}
TEST_V4: ${{ matrix.version == 'v4' }}
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }} SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }}
- uses: codecov/codecov-action@84508663e988701840491b86de86b666e8a86bed # v4.3.0 - uses: codecov/codecov-action@84508663e988701840491b86de86b666e8a86bed # v4.3.0

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, findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } from '@nuxt/kit' import { compileTemplate as _compileTemplate, findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } 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'
@ -39,6 +39,8 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
const filteredTemplates = (app.templates as Array<ResolvedNuxtTemplate<any>>) const filteredTemplates = (app.templates as Array<ResolvedNuxtTemplate<any>>)
.filter(template => !options.filter || options.filter(template)) .filter(template => !options.filter || options.filter(template))
const compileTemplate = nuxt.options.experimental.compileTemplate ? _compileTemplate : futureCompileTemplate
const writes: Array<() => void> = [] const writes: Array<() => void> = []
await Promise.allSettled(filteredTemplates await Promise.allSettled(filteredTemplates
.map(async (template) => { .map(async (template) => {
@ -91,6 +93,24 @@ 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 }) {
delete ctx.utils
if (template.src) {
try {
return await fsp.readFile(template.src, 'utf-8')
} catch (err) {
logger.error(`[nuxt] Error reading template from \`${template.src}\``)
throw err
}
}
if (template.getContents) {
return template.getContents({ ...ctx, options: template.options! })
}
throw new Error('[nuxt] Invalid template. Templates must have either `src` or `getContents`: ' + JSON.stringify(template))
}
export async function resolveApp (nuxt: Nuxt, app: NuxtApp) { export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
// Resolve main (app.vue) // Resolve main (app.vue)
if (!app.mainComponent) { if (!app.mainComponent) {

View File

@ -85,7 +85,7 @@ function createWatcher () {
}) })
// TODO: consider moving to emit absolute path in 3.8 or 4.0 // TODO: consider moving to emit absolute path in 3.8 or 4.0
watcher.on('all', (event, path) => nuxt.callHook('builder:watch', event, normalize(relative(nuxt.options.srcDir, path)))) watcher.on('all', (event, path) => nuxt.callHook('builder:watch', event, nuxt.options.experimental.relativeWatchPaths ? normalize(relative(nuxt.options.srcDir, path)) : normalize(path)))
nuxt.hook('close', () => watcher?.close()) nuxt.hook('close', () => watcher?.close())
} }
@ -116,7 +116,7 @@ function createGranularWatcher () {
path = normalize(path) path = normalize(path)
if (!pending) { if (!pending) {
// TODO: consider moving to emit absolute path in 3.8 or 4.0 // TODO: consider moving to emit absolute path in 3.8 or 4.0
nuxt.callHook('builder:watch', event, relative(nuxt.options.srcDir, path)) nuxt.callHook('builder:watch', event, nuxt.options.experimental.relativeWatchPaths ? relative(nuxt.options.srcDir, path) : path)
} }
if (event === 'unlinkDir' && path in watchers) { if (event === 'unlinkDir' && path in watchers) {
watchers[path]?.close() watchers[path]?.close()
@ -125,7 +125,7 @@ function createGranularWatcher () {
if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) { if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) {
watchers[path] = chokidar.watch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] }) watchers[path] = chokidar.watch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
// TODO: consider moving to emit absolute path in 3.8 or 4.0 // TODO: consider moving to emit absolute path in 3.8 or 4.0
watchers[path].on('all', (event, p) => nuxt.callHook('builder:watch', event, normalize(relative(nuxt.options.srcDir, p)))) watchers[path].on('all', (event, p) => nuxt.callHook('builder:watch', event, nuxt.options.experimental.relativeWatchPaths ? normalize(relative(nuxt.options.srcDir, p)) : normalize(p)))
nuxt.hook('close', () => watchers[path]?.close()) nuxt.hook('close', () => watchers[path]?.close())
} }
}) })
@ -159,7 +159,7 @@ async function createParcelWatcher () {
for (const event of events) { for (const event of events) {
if (isIgnored(event.path)) { continue } if (isIgnored(event.path)) { continue }
// TODO: consider moving to emit absolute path in 3.8 or 4.0 // TODO: consider moving to emit absolute path in 3.8 or 4.0
nuxt.callHook('builder:watch', watchEvents[event.type], normalize(relative(nuxt.options.srcDir, event.path))) nuxt.callHook('builder:watch', watchEvents[event.type], nuxt.options.experimental.relativeWatchPaths ? normalize(relative(nuxt.options.srcDir, event.path)) : normalize(event.path))
} }
}, { }, {
ignore: [ ignore: [

View File

@ -1,7 +1,7 @@
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { existsSync, promises as fsp, readFileSync } from 'node:fs' import { existsSync, promises as fsp, readFileSync } from 'node:fs'
import { cpus } from 'node:os' import { cpus } from 'node:os'
import { join, normalize, relative, resolve } from 'pathe' import { join, relative, resolve } from 'pathe'
import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from 'radix3' import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from 'radix3'
import { randomUUID } from 'uncrypto' import { randomUUID } from 'uncrypto'
import { joinURL, withTrailingSlash } from 'ufo' import { joinURL, withTrailingSlash } from 'ufo'
@ -409,8 +409,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
// Trigger Nitro reload when SPA loading template changes // Trigger Nitro reload when SPA loading template changes
const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt) const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt)
nuxt.hook('builder:watch', async (_event, path) => { nuxt.hook('builder:watch', async (_event, relativePath) => {
if (normalize(path) === spaLoadingTemplateFilePath) { const path = resolve(nuxt.options.srcDir, relativePath)
if (path === spaLoadingTemplateFilePath) {
await nitro.hooks.callHook('rollup:reload') await nitro.hooks.callHook('rollup:reload')
} }
}) })

View File

@ -324,7 +324,7 @@ export default defineNuxtModule({
} }
nuxt.hook('builder:watch', async (event, relativePath) => { nuxt.hook('builder:watch', async (event, relativePath) => {
const path = join(nuxt.options.srcDir, relativePath) const path = resolve(nuxt.options.srcDir, relativePath)
if (!(path in pageToGlobMap)) { return } if (!(path in pageToGlobMap)) { return }
if (event === 'unlink') { if (event === 'unlink') {
delete inlineRules[path] delete inlineRules[path]

View File

@ -6,6 +6,11 @@ export default defineUntypedSchema({
* (possibly major) version of the framework. * (possibly major) version of the framework.
*/ */
future: { future: {
/**
* Enable early access to Nuxt v4 features or flags.
* @type {3 | 4}
*/
compatibilityVersion: 3,
/** /**
* This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting * This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting
* for frameworks like Nuxt and Vite. * for frameworks like Nuxt and Vite.
@ -329,7 +334,11 @@ export default defineUntypedSchema({
* Options that apply to `useAsyncData` (and also therefore `useFetch`) * Options that apply to `useAsyncData` (and also therefore `useFetch`)
*/ */
useAsyncData: { useAsyncData: {
deep: true, deep: {
async $resolve (val, get) {
return val ?? !((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
},
},
}, },
/** @type {Pick<typeof import('ofetch')['FetchOptions'], 'timeout' | 'retry' | 'retryDelay' | 'retryStatusCodes'>} */ /** @type {Pick<typeof import('ofetch')['FetchOptions'], 'timeout' | 'retry' | 'retryDelay' | 'retryStatusCodes'>} */
useFetch: {}, useFetch: {},
@ -349,5 +358,42 @@ export default defineUntypedSchema({
* @type {boolean} * @type {boolean}
*/ */
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+.
*/
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+.
*/
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.
*
* This flag will be removed with the release of v4 and exists only for
* advance testing within Nuxt v3.12+.
*/
relativeWatchPaths: {
async $resolve (val, get) {
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
},
},
}, },
}) })

View File

@ -7,6 +7,7 @@ export default defineNuxtConfig({
}, },
future: { future: {
typescriptBundlerResolution: process.env.MODULE_RESOLUTION === 'bundler', typescriptBundlerResolution: process.env.MODULE_RESOLUTION === 'bundler',
compatibilityVersion: process.env.TEST_V4 === 'true' ? 4 : 3,
}, },
buildDir: process.env.NITRO_BUILD_DIR, buildDir: process.env.NITRO_BUILD_DIR,
builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite', builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite',

View File

@ -11,6 +11,7 @@ declare module 'nitropack' {
} }
export default defineNuxtConfig({ export default defineNuxtConfig({
future: { compatibilityVersion: process.env.TEST_V4 === 'true' ? 4 : 3 },
app: { app: {
pageTransition: true, pageTransition: true,
layoutTransition: true, layoutTransition: true,

View File

@ -1,3 +1,4 @@
export default defineNuxtConfig({ export default defineNuxtConfig({
future: { compatibilityVersion: process.env.TEST_V4 === 'true' ? 4 : 3 },
experimental: { appManifest: true }, experimental: { appManifest: true },
}) })

View File

@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'
const testWithInlineVue = process.env.EXTERNAL_VUE === 'false' const testWithInlineVue = process.env.EXTERNAL_VUE === 'false'
export default defineNuxtConfig({ export default defineNuxtConfig({
future: { compatibilityVersion: process.env.TEST_V4 === 'true' ? 4 : 3 },
pages: false, pages: false,
experimental: { experimental: {
externalVue: !testWithInlineVue, externalVue: !testWithInlineVue,

View File

@ -1,5 +1,6 @@
// https://nuxt.com/docs/api/nuxt-config // https://nuxt.com/docs/api/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
future: { compatibilityVersion: process.env.TEST_V4 === 'true' ? 4 : 3 },
experimental: { experimental: {
externalVue: false, externalVue: false,
}, },

View File

@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'
const testWithInlineVue = process.env.EXTERNAL_VUE === 'false' const testWithInlineVue = process.env.EXTERNAL_VUE === 'false'
export default defineNuxtConfig({ export default defineNuxtConfig({
future: { compatibilityVersion: process.env.TEST_V4 === 'true' ? 4 : 3 },
experimental: { experimental: {
externalVue: !testWithInlineVue, externalVue: !testWithInlineVue,
}, },