Merge branch 'nuxt:main' into main

This commit is contained in:
David Nahodyl 2024-06-16 12:56:32 -04:00 committed by GitHub
commit f38cf6b65f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 682 additions and 894 deletions

View File

@ -46,7 +46,7 @@ jobs:
run: pnpm build
- name: Run benchmarks
uses: CodSpeedHQ/action@0b631f8998f2389eb5144632b6f9f8fabd33a86e # v2.4.1
uses: CodSpeedHQ/action@f11c406b8c87cda176ff341ed4925bc98086f6d1 # v2.4.2
with:
run: pnpm vitest bench
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@ -5,7 +5,7 @@
<p>
<a href="https://www.npmjs.com/package/nuxt"><img src="https://img.shields.io/npm/v/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a>
<a href="https://www.npmjs.com/package/nuxt"><img src="https://img.shields.io/npm/dm/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"></a>
<a href="./LICENSE"><img src="https://img.shields.io/github/license/nuxt/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="License"></a>
<a href="https://github.com/nuxt/nuxt/tree/main/LICENSE"><img src="https://img.shields.io/github/license/nuxt/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="License"></a>
<a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt%20Docs-18181B?logo=nuxt.js" alt="Website"></a>
<a href="https://chat.nuxt.dev"><img src="https://img.shields.io/badge/Nuxt%20Discord-18181B?logo=discord" alt="Discord"></a>
</p>
@ -120,4 +120,4 @@ If you expect to be using Nuxt 2 beyond the EOL (End of Life) date (June 30, 202
## <a name="license">⚖️ License</a>
[MIT](./LICENSE)
[MIT](https://github.com/nuxt/nuxt/tree/main/LICENSE)

View File

@ -117,10 +117,13 @@ In most cases, Nuxt can work with third-party content that is not generated or c
Accordingly, you should make sure that the following options are unchecked / disabled in Cloudflare. Otherwise, unnecessary re-rendering or hydration errors could impact your production application.
1. Speed > Optimization > Auto Minify: Uncheck JavaScript, CSS and HTML
2. Speed > Optimization > Disable "Rocket Loader™"
3. Speed > Optimization > Disable "Mirage"
1. Speed > Optimization > Content Optimization > Auto Minify: Uncheck JavaScript, CSS and HTML
2. Speed > Optimization > Content Optimization > Disable "Rocket Loader™"
3. Speed > Optimization > Image Optimization > Disable "Mirage"
4. Scrape Shield > Disable "Email Address Obfuscation"
5. Scrape Shield > Disable "Server-side Excludes"
With these settings, you can be sure that Cloudflare won't inject scripts into your Nuxt application that may cause unwanted side effects.
::tip
Their location on the Cloudfalre dashboard sometimes changes so don't hesitate to look around.
::

View File

@ -150,10 +150,10 @@ Read more about `useError` composable.
### `createError`
```ts [TS Signature]
function createError (err: { cause, data, message, name, stack, statusCode, statusMessage, fatal }): Error
function createError (err: string | { cause, data, message, name, stack, statusCode, statusMessage, fatal }): Error
```
Create an error object with additional metadata. It is usable in both the Vue and Server portions of your app, and is meant to be thrown.
Create an error object with additional metadata. You can pass a string to be set as the error `message` or an object containing error properties. It is usable in both the Vue and Server portions of your app, and is meant to be thrown.
If you throw an error created with `createError`:
- on server-side, it will trigger a full-screen error page which you can clear with [`clearError`](#clearerror).

View File

@ -155,7 +155,7 @@ export default defineNuxtModule({
// Compatibility constraints
compatibility: {
// Semver version of supported nuxt versions
nuxt: '^3.0.0'
nuxt: '>=3.0.0'
}
},
// Default configuration options for your module, can also be a function returning those

View File

@ -18,7 +18,7 @@ Within your pages, components, and plugins you can use useAsyncData to get acces
```vue [pages/index.vue]
<script setup lang="ts">
const { data, pending, error, refresh } = await useAsyncData(
const { data, pending, error, refresh, clear } = await useAsyncData(
'mountains',
() => $fetch('https://api.nuxtjs.dev/mountains')
)
@ -26,7 +26,7 @@ const { data, pending, error, refresh } = await useAsyncData(
```
::note
`data`, `pending`, `status` and `error` are Vue refs and they should be accessed with `.value` when used within the `<script setup>`, while `refresh`/`execute` is a plain function for refetching data.
`data`, `pending`, `status` and `error` are Vue refs and they should be accessed with `.value` when used within the `<script setup>`, while `refresh`/`execute` and `clear` are plain functions.
::
### Watch Params
@ -92,6 +92,7 @@ Learn how to use `transform` and `getCachedData` to avoid superfluous calls to a
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
- `error`: an error object if the data fetching failed.
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `pending` to `false`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
By default, Nuxt waits until a `refresh` is finished before it can be executed again.

View File

@ -19,14 +19,14 @@ It automatically generates a key based on URL and fetch options, provides type h
```vue [pages/modules.vue]
<script setup lang="ts">
const { data, pending, error, refresh } = await useFetch('/api/modules', {
const { data, pending, error, refresh, clear } = await useFetch('/api/modules', {
pick: ['title']
})
</script>
```
::note
`data`, `pending`, `status` and `error` are Vue refs and they should be accessed with `.value` when used within the `<script setup>`, while `refresh`/`execute` is a plain function for refetching data.
`data`, `pending`, `status` and `error` are Vue refs and they should be accessed with `.value` when used within the `<script setup>`, while `refresh`/`execute` and `clear` are plain functions..
::
Using the `query` option, you can add search parameters to your query. This option is extended from [unjs/ofetch](https://github.com/unjs/ofetch) and is using [unjs/ufo](https://github.com/unjs/ufo) to create the URL. Objects are automatically stringified.
@ -43,7 +43,7 @@ The above example results in `https://api.nuxt.com/modules?param1=value1&param2=
You can also use [interceptors](https://github.com/unjs/ofetch#%EF%B8%8F-interceptors):
```ts
const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
const { data, pending, error, refresh, clear } = await useFetch('/api/auth/login', {
onRequest({ request, options }) {
// Set the request headers
options.headers = options.headers || {}
@ -128,6 +128,7 @@ Learn how to use `transform` and `getCachedData` to avoid superfluous calls to a
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
- `error`: an error object if the data fetching failed.
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `pending` to `false`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
By default, Nuxt waits until a `refresh` is finished before it can be executed again.

View File

@ -12,7 +12,9 @@ You can use this function to create an error object with additional metadata. It
## Parameters
- `err`: `{ cause, data, message, name, stack, statusCode, statusMessage, fatal }`
- `err`: `string | { cause, data, message, name, stack, statusCode, statusMessage, fatal }`
You can pass either a string or an object to the `createError` function. If you pass a string, it will be used as the error `message`, and the `statusCode` will default to `500`. If you pass an object, you can set multiple properties of the error, such as `statusCode`, `message`, and other error properties.
## In Vue App
@ -48,4 +50,6 @@ export default eventHandler(() => {
})
```
In API routes, using `createError` by passing an object with a short `statusMessage` is recommended because it can be accessed on the client side. Otherwise, a `message` passed to `createError` on an API route will not propagate to the client. Alternatively, you can use the `data` property to pass data back to the client. In any case, always consider avoiding to put dynamic user input to the message to avoid potential security issues.
:read-more{to="/docs/getting-started/error-handling"}

View File

@ -42,11 +42,11 @@
"magic-string": "^0.30.10",
"nuxt": "workspace:*",
"rollup": "^4.18.0",
"vite": "5.3.0",
"vue": "3.4.27"
"vite": "5.3.1",
"vue": "3.4.29"
},
"devDependencies": {
"@eslint/js": "9.4.0",
"@eslint/js": "9.5.0",
"@nuxt/eslint-config": "0.3.13",
"@nuxt/kit": "workspace:*",
"@nuxt/test-utils": "3.13.1",
@ -64,7 +64,7 @@
"changelogen": "0.5.5",
"consola": "3.2.3",
"devalue": "5.0.0",
"eslint": "9.4.0",
"eslint": "9.5.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-perfectionist": "2.11.0",
"eslint-typegen": "0.2.4",
@ -89,7 +89,7 @@
"ufo": "1.5.3",
"vitest": "1.6.0",
"vitest-environment-nuxt": "1.0.0",
"vue": "3.4.27",
"vue": "3.4.29",
"vue-router": "4.3.3",
"vue-tsc": "2.0.21"
},

View File

@ -1,6 +1,6 @@
{
"name": "@nuxt/kit",
"version": "3.12.1",
"version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@ -54,7 +54,7 @@
"lodash-es": "4.17.21",
"nitropack": "2.9.6",
"unbuild": "latest",
"vite": "5.3.0",
"vite": "5.3.1",
"vitest": "1.6.0",
"webpack": "5.92.0"
},

View File

@ -43,8 +43,11 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
* Note: By default plugin is prepended to the plugins array. You can use second argument to append (push) instead.
* @example
* ```js
* import { createResolver } from '@nuxt/kit'
* const resolver = createResolver(import.meta.url)
*
* addPlugin({
* src: path.resolve(__dirname, 'templates/foo.js'),
* src: resolver.resolve('templates/foo.js'),
* filename: 'foo.server.js' // [optional] only include in server bundle
* })
* ```

View File

@ -1,6 +1,6 @@
{
"name": "nuxt",
"version": "3.12.1",
"version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@ -68,8 +68,8 @@
"@unhead/dom": "^1.9.13",
"@unhead/ssr": "^1.9.13",
"@unhead/vue": "^1.9.13",
"@vue/shared": "^3.4.27",
"acorn": "8.11.3",
"@vue/shared": "^3.4.29",
"acorn": "8.12.0",
"c12": "^1.11.1",
"chokidar": "^3.6.0",
"cookie-es": "^1.1.0",
@ -112,7 +112,7 @@
"unplugin-vue-router": "^0.7.0",
"unstorage": "^1.10.2",
"untyped": "^1.4.2",
"vue": "^3.4.27",
"vue": "^3.4.29",
"vue-bundle-renderer": "^2.1.0",
"vue-devtools-stub": "^0.1.0",
"vue-router": "^4.3.3"
@ -124,9 +124,9 @@
"@types/estree": "1.0.5",
"@types/fs-extra": "11.0.4",
"@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.4.27",
"@vue/compiler-sfc": "3.4.29",
"unbuild": "latest",
"vite": "5.3.0",
"vite": "5.3.1",
"vitest": "1.6.0"
},
"peerDependencies": {

View File

@ -311,6 +311,11 @@ export function useAsyncData<
result = pick(result as any, options.pick) as DataT
}
if (import.meta.dev && import.meta.server && !result) {
// @ts-expect-error private property
console.warn(`[nuxt] \`${options._functionName || 'useAsyncData'}\` should return a value that is not \`null\` or \`undefined\` or the request may be duplicated on the client side.`)
}
nuxtApp.payload.data[key] = result
asyncData.data.value = result

View File

@ -198,6 +198,7 @@ function cookieRef<T> (value: T | undefined, delay: number, shouldWatch: boolean
if (shouldWatch) { unsubscribe = watch(internalRef, trigger) }
function createExpirationTimeout () {
elapsed = 0
clearTimeout(timeout)
const timeRemaining = delay - elapsed
const timeoutLength = timeRemaining < MAX_TIMEOUT_DELAY ? timeRemaining : MAX_TIMEOUT_DELAY

View File

@ -60,9 +60,15 @@ export function useScriptGoogleTagManager (...args: unknown[]) {
export function useScriptSegment (...args: unknown[]) {
renderStubMessage('useScriptSegment')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptFacebookPixel (...args: unknown[]) {
renderStubMessage('useScriptFacebookPixel')
export function useScriptClarity (...args: unknown[]) {
renderStubMessage('useScriptClarity')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptMetaPixel (...args: unknown[]) {
renderStubMessage('useScriptMetaPixel')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptXPixel (...args: unknown[]) {
@ -100,3 +106,13 @@ export function useScriptGoogleMaps (...args: unknown[]) {
export function useScriptNpm (...args: unknown[]) {
renderStubMessage('useScriptNpm')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleAdsense (...args: unknown[]) {
renderStubMessage('useScriptGoogleAdsense')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptYouTubePlayer (...args: unknown[]) {
renderStubMessage('useScriptYouTubePlayer')
}

View File

@ -28,7 +28,7 @@ const SCRIPT_RE = /<script[^>]*>/g
const HAS_SLOT_OR_CLIENT_RE = /<slot[^>]*>|nuxt-client/
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
const IMPORT_CODE = '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
const IMPORT_CODE = '\nimport { mergeProps as __mergeProps } from \'vue\'' + '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
const EXTRACTED_ATTRS_RE = /v-(?:if|else-if|else)(="[^"]*")?/g
function wrapWithVForDiv (code: string, vfor: string): string {
@ -156,7 +156,7 @@ function getPropsToString (bindings: Record<string, string>): string {
const vfor = bindings['v-for']?.split(' in ').map((v: string) => v.trim()) as [string, string] | undefined
if (Object.keys(bindings).length === 0) { return 'undefined' }
const content = Object.entries(bindings).filter(b => b[0] && (b[0] !== '_bind' && b[0] !== 'v-for')).map(([name, value]) => isBinding(name) ? `[\`${name.slice(1)}\`]: ${value}` : `[\`${name}\`]: \`${value}\``).join(',')
const data = bindings._bind ? `mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
const data = bindings._bind ? `__mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
if (!vfor) {
return `[${data}]`
} else {

View File

@ -46,10 +46,12 @@ export async function build (nuxt: Nuxt) {
if (!nuxt.options._prepare) {
await Promise.all([checkForExternalConfigurationFiles(), bundle(nuxt)])
await nuxt.callHook('build:done')
}
if (!nuxt.options.dev) {
await nuxt.callHook('close', nuxt)
if (!nuxt.options.dev) {
await nuxt.callHook('close', nuxt)
}
} else {
nuxt.hook('prepare:types', () => nuxt.close())
}
}

View File

@ -355,6 +355,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
nitroConfig.rollupConfig!.plugins!.push(
ImportProtectionPlugin.rollup({
rootDir: nuxt.options.rootDir,
modulesDir: nuxt.options.modulesDir,
patterns: nuxtImportProtections(nuxt, { isNitro: true }),
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/],
}),
@ -497,7 +498,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
for (const route of ['/200.html', '/404.html']) {
routes.add(route)
}
if (!nuxt.options.ssr) {
if (nuxt.options.ssr) {
if (nitro.options.prerender.crawlLinks) {
routes.add('/')
}
} else {
routes.add('/index.html')
}
})

View File

@ -49,14 +49,13 @@ export function createNuxt (options: NuxtOptions): Nuxt {
addHooks: hooks.addHooks,
hook: hooks.hook,
ready: () => initNuxt(nuxt),
close: async () => {
await hooks.callHook('close', nuxt)
hooks.removeAllHooks()
},
close: () => hooks.callHook('close', nuxt),
vfs: {},
apps: {},
}
hooks.hookOnce('close', () => { hooks.removeAllHooks() })
return nuxt
}
@ -168,6 +167,7 @@ async function initNuxt (nuxt: Nuxt) {
// Exclude top-level resolutions by plugins
exclude: [join(nuxt.options.srcDir, 'index.html')],
patterns: nuxtImportProtections(nuxt),
modulesDir: nuxt.options.modulesDir,
}
addVitePlugin(() => ImportProtectionPlugin.vite(config))
addWebpackPlugin(() => ImportProtectionPlugin.webpack(config))
@ -664,7 +664,9 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
const nuxt = createNuxt(options)
await Promise.all(keyDependencies.map(dependency => checkDependencyVersion(dependency, nuxt._version)))
for (const dep of keyDependencies) {
checkDependencyVersion(dep, nuxt._version)
}
// We register hooks layer-by-layer so any overrides need to be registered separately
if (opts.overrides?.hooks) {

View File

@ -1,14 +1,13 @@
import { createRequire } from 'node:module'
import { createUnplugin } from 'unplugin'
import { logger } from '@nuxt/kit'
import { resolvePath } from 'mlly'
import { isAbsolute, join, relative, resolve } from 'pathe'
import escapeRE from 'escape-string-regexp'
import type { NuxtOptions } from 'nuxt/schema'
const _require = createRequire(import.meta.url)
interface ImportProtectionOptions {
rootDir: string
modulesDir: string[]
patterns: [importPattern: string | RegExp, warning?: string][]
exclude?: Array<RegExp | string>
}
@ -58,6 +57,7 @@ export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: {
export const ImportProtectionPlugin = createUnplugin(function (options: ImportProtectionOptions) {
const cache: Record<string, Map<string | RegExp, boolean>> = {}
const importersToExclude = options?.exclude || []
const proxy = resolvePath('unenv/runtime/mock/proxy', { url: options.modulesDir })
return {
name: 'nuxt:import-protection',
enforce: 'pre',
@ -85,7 +85,7 @@ export const ImportProtectionPlugin = createUnplugin(function (options: ImportPr
matched = true
}
if (matched) {
return _require.resolve('unenv/runtime/mock/proxy')
return proxy
}
return null
},

View File

@ -698,7 +698,7 @@ function replaceIslandTeleports (ssrContext: NuxtSSRContext, html: string) {
if (matchClientComp) {
const [, uid, clientId] = matchClientComp
if (!uid || !clientId) { continue }
html = html.replace(new RegExp(` data-island-component="${clientId}"[^>]*>`), (full) => {
html = html.replace(new RegExp(` data-island-uid="${uid}" data-island-component="${clientId}"[^>]*>`), (full) => {
return full + teleports[key]
})
continue

View File

@ -426,7 +426,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
`export const resetAsyncDataToUndefined = ${ctx.nuxt.options.experimental.resetAsyncDataToUndefined}`,
`export const nuxtDefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}`,
`export const fetchDefaults = ${JSON.stringify(fetchDefaults)}`,
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootAttrs.id ? `'#${ctx.nuxt.options.app.rootAttrs.id}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,

View File

@ -275,20 +275,6 @@ export default defineNuxtModule({
}
})
// TODO: inject routes in `200.html` in next nitro upgrade (2.9.7+) via https://github.com/unjs/nitro/pull/2517
if (!nuxt.options.dev && !nuxt.options._prepare) {
nuxt.hook('app:templatesGenerated', (app) => {
const nitro = useNitro()
if (nitro.options.prerender.crawlLinks) {
for (const page of app.pages!) {
if (page.path && !page.path.includes(':')) {
nitro.options.prerender.routes.push(page.path)
}
}
}
})
}
nuxt.hook('imports:extend', (imports) => {
imports.push(
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },

View File

@ -1,7 +1,7 @@
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { join } from 'pathe'
import { createCommonJS, findExports } from 'mlly'
import { findExports } from 'mlly'
import * as VueFunctions from 'vue'
import type { Import } from 'unimport'
import { createUnimport } from 'unimport'
@ -59,8 +59,8 @@ const excludedNuxtHelpers = ['useHydration', 'useHead', 'useSeoMeta', 'useServer
describe('imports:nuxt', () => {
try {
const { __dirname } = createCommonJS(import.meta.url)
const entrypointContents = readFileSync(join(__dirname, '../src/app/composables/index.ts'), 'utf8')
const entrypointPath = fileURLToPath(new URL('../src/app/composables/index.ts', import.meta.url))
const entrypointContents = readFileSync(entrypointPath, 'utf8')
const names = findExports(entrypointContents).flatMap(i => i.names || i.name)
for (let name of names) {

View File

@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import type { Component, Nuxt } from '@nuxt/schema'
import { kebabCase } from 'scule'
import { normalize } from 'pathe'
import { createTransformPlugin } from '../src/components/transform'
@ -92,7 +93,7 @@ function createTransformer (components: Component[], mode: 'client' | 'server' |
return async (code: string, id: string) => {
const result = await (plugin as any).transform!(code, id)
return (typeof result === 'string' ? result : result?.code)?.replaceAll(rootDir, '<repo>/')
return (typeof result === 'string' ? result : result?.code)?.replaceAll(normalize(rootDir), '<repo>/')
}
}

View File

@ -1,3 +1,4 @@
import { fileURLToPath } from 'node:url'
import { normalize } from 'pathe'
import { describe, expect, it } from 'vitest'
import { ImportProtectionPlugin, nuxtImportProtections } from '../src/core/plugins/import-protection'
@ -40,6 +41,7 @@ describe('import protection', () => {
const transformWithImportProtection = (id: string, importer: string) => {
const plugin = ImportProtectionPlugin.rollup({
rootDir: '/root',
modulesDir: [fileURLToPath(new URL('..', import.meta.url))],
patterns: nuxtImportProtections({
options: {
modules: ['some-nuxt-module'],

View File

@ -71,6 +71,7 @@ describe('islandTransform - server and island components', () => {
</div>
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -80,6 +81,42 @@ describe('islandTransform - server and island components', () => {
`)
})
it('generates bindings when props are needed to be merged', async () => {
const result = await viteTransform(`<script setup lang="ts">
withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
things: () => [],
somethingElse: "yay",
});
</script>
<template>
<template v-for="thing in things">
<slot name="thing" v-bind="thing" />
</template>
</template>
`, 'hello.server.vue')
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
"<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
things: () => [],
somethingElse: "yay",
});
</script>
<template>
<template v-for="thing in things">
<NuxtTeleportSsrSlot name="thing" :props="[__mergeProps(thing, { })]"><slot name="thing" v-bind="thing" /></NuxtTeleportSsrSlot>
</template>
</template>
"
`)
})
it('expect slot fallback transform to match inline snapshot', async () => {
const result = await viteTransform(`<template>
<div>
@ -103,6 +140,7 @@ describe('islandTransform - server and island components', () => {
</div>
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -168,6 +206,7 @@ describe('islandTransform - server and island components', () => {
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -195,6 +234,7 @@ describe('islandTransform - server and island components', () => {
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
"<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -234,6 +274,7 @@ describe('islandTransform - server and island components', () => {
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -267,6 +308,7 @@ describe('islandTransform - server and island components', () => {
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -302,6 +344,7 @@ describe('islandTransform - server and island components', () => {
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
@ -325,6 +368,7 @@ describe('islandTransform - server and island components', () => {
expect(result).toMatchInlineSnapshot(`
"<script setup>
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'</script><template>
@ -351,6 +395,7 @@ describe('islandTransform - server and island components', () => {
expect(result).toMatchInlineSnapshot(`
"<script setup>
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'</script><template>
@ -396,6 +441,7 @@ describe('islandTransform - server and island components', () => {
</template>
<script setup lang="ts">
import { mergeProps as __mergeProps } from 'vue'
import { vforToArray as __vforToArray } from '#app/components/utils'
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'

View File

@ -1,10 +1,11 @@
import { fileURLToPath } from 'node:url'
import { resolve } from 'pathe'
import { expect, it, vi } from 'vitest'
import type { ComponentsDir } from 'nuxt/schema'
import { scanComponents } from '../src/components/scan'
const fixtureDir = resolve(__dirname, 'fixture')
const fixtureDir = fileURLToPath(new URL('fixture', import.meta.url))
const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)
vi.mock('@nuxt/kit', () => ({

View File

@ -1,6 +1,6 @@
import { resolve } from 'pathe'
import { fileURLToPath } from 'node:url'
export const fixtureDir = resolve(__dirname, 'fixture')
export const fixtureDir = fileURLToPath(new URL('fixture', import.meta.url))
export function normalizeLineEndings (str: string, normalized = '\n') {
return str.replace(/\r?\n/g, normalized)

View File

@ -1,6 +1,6 @@
{
"name": "@nuxt/schema",
"version": "3.12.1",
"version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@ -42,8 +42,8 @@
"@unhead/schema": "1.9.13",
"@vitejs/plugin-vue": "5.0.4",
"@vitejs/plugin-vue-jsx": "4.0.0",
"@vue/compiler-core": "3.4.27",
"@vue/compiler-sfc": "3.4.27",
"@vue/compiler-core": "3.4.29",
"@vue/compiler-sfc": "3.4.29",
"@vue/language-core": "2.0.21",
"c12": "1.11.1",
"esbuild-loader": "4.1.0",
@ -54,8 +54,8 @@
"unbuild": "latest",
"unctx": "2.3.1",
"unenv": "1.9.0",
"vite": "5.3.0",
"vue": "3.4.27",
"vite": "5.3.1",
"vue": "3.4.29",
"vue-bundle-renderer": "2.1.0",
"vue-loader": "17.4.2",
"vue-router": "4.3.3",

View File

@ -1,4 +1,5 @@
import { consola } from 'consola'
import { resolve } from 'pathe'
import { isTest } from 'std-env'
import { withoutLeadingSlash } from 'ufo'
import { defineUntypedSchema } from 'untyped'
@ -109,5 +110,8 @@ export default defineUntypedSchema({
},
},
},
cacheDir: {
$resolve: async (val, get) => val ?? resolve(await get('rootDir') as string, 'node_modules/.cache/vite'),
},
},
})

View File

@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import { applyDefaults } from 'untyped'
import { normalize } from 'pathe'
import { NuxtConfigSchema } from '../src'
import type { NuxtOptions } from '../src'
@ -75,7 +76,9 @@ describe('nuxt folder structure', () => {
})
function getDirs (options: NuxtOptions) {
const stripRoot = (dir: string) => dir.replace(process.cwd(), '<cwd>')
const stripRoot = (dir: string) => {
return normalize(dir).replace(normalize(process.cwd()), '<cwd>')
}
return {
rootDir: stripRoot(options.rootDir),
serverDir: stripRoot(options.serverDir),

View File

@ -1,10 +1,13 @@
import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { promises as fsp } from 'node:fs'
import type { Plugin } from 'vite'
import { template } from 'lodash-es'
import genericMessages from '../templates/messages.json'
const r = (...path: string[]) => resolve(join(__dirname, '..', ...path))
const templatesRoot = fileURLToPath(new URL('..', import.meta.url))
const r = (...path: string[]) => resolve(join(templatesRoot, ...path))
export const DevRenderingPlugin = () => {
return <Plugin>{

View File

@ -1,8 +1,11 @@
import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { promises as fsp } from 'node:fs'
import { globby } from 'globby'
const r = (...path: string[]) => resolve(join(__dirname, '..', ...path))
const templatesRoot = fileURLToPath(new URL('..', import.meta.url))
const r = (...path: string[]) => resolve(join(templatesRoot, ...path))
async function main () {
const templates = await globby(r('dist/templates/*.js'))

View File

@ -33,6 +33,6 @@
"prettier": "3.3.2",
"scule": "1.3.0",
"unocss": "0.61.0",
"vite": "5.3.0"
"vite": "5.3.1"
}
}

View File

@ -1,5 +1,5 @@
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import { resolve } from 'node:path'
import { readdirSync } from 'node:fs'
import { defineConfig } from 'vite'
@ -8,7 +8,8 @@ import UnoCSS from 'unocss/vite'
import { DevRenderingPlugin } from './lib/dev'
import { RenderPlugin } from './lib/render'
const r = (...path: string[]) => fileURLToPath(new URL(join(...path), import.meta.url))
const rootDir = fileURLToPath(new URL('.', import.meta.url))
const r = (...path: string[]) => resolve(rootDir, ...path)
export default defineConfig({
build: {
@ -32,7 +33,7 @@ export default defineConfig({
],
server: {
fs: {
allow: ['./templates', __dirname],
allow: ['./templates', rootDir],
},
},
})

View File

@ -1,6 +1,6 @@
{
"name": "@nuxt/vite-builder",
"version": "3.12.1",
"version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@ -30,7 +30,7 @@
"@types/fs-extra": "11.0.4",
"rollup": "4.18.0",
"unbuild": "latest",
"vue": "3.4.27"
"vue": "3.4.29"
},
"dependencies": {
"@nuxt/kit": "workspace:*",
@ -63,7 +63,7 @@
"ufo": "^1.5.3",
"unenv": "^1.9.0",
"unplugin": "^1.10.1",
"vite": "^5.3.0",
"vite": "^5.3.1",
"vite-node": "^1.6.0",
"vite-plugin-checker": "^0.6.4",
"vue-bundle-renderer": "^2.1.0"

View File

@ -118,7 +118,7 @@ export async function buildClient (ctx: ViteBuildContext) {
'vue',
],
},
cacheDir: resolve(ctx.nuxt.options.rootDir, 'node_modules/.cache/vite', 'client'),
cacheDir: resolve(ctx.nuxt.options.rootDir, ctx.config.cacheDir ?? 'node_modules/.cache/vite', 'client'),
build: {
sourcemap: ctx.nuxt.options.sourcemap.client ? ctx.config.build?.sourcemap ?? ctx.nuxt.options.sourcemap.client : false,
manifest: 'manifest.json',

View File

@ -19,6 +19,7 @@ export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
for (const [name, opts] of plugins) {
if (opts) {
// TODO: remove use of requireModule in favour of ESM import
const plugin = requireModule(name, {
paths: [
...nuxt.options.modulesDir,

View File

@ -71,7 +71,7 @@ export async function buildServer (ctx: ViteBuildContext) {
/(nuxt|nuxt3|nuxt-nightly)\/(dist|src|app)/,
],
},
cacheDir: resolve(ctx.nuxt.options.rootDir, 'node_modules/.cache/vite', 'server'),
cacheDir: resolve(ctx.nuxt.options.rootDir, ctx.config.cacheDir ?? 'node_modules/.cache/vite', 'server'),
build: {
// we'll display this in nitro build output
reportCompressedSize: false,

View File

@ -1,6 +1,6 @@
{
"name": "@nuxt/webpack-builder",
"version": "3.12.1",
"version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@ -42,7 +42,7 @@
"hash-sum": "^2.0.0",
"lodash-es": "4.17.21",
"magic-string": "^0.30.10",
"memfs": "^4.9.2",
"memfs": "^4.9.3",
"mini-css-extract-plugin": "^2.9.0",
"mlly": "^1.7.1",
"ohash": "^1.1.3",
@ -77,9 +77,8 @@
"@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9",
"@types/webpack-virtual-modules": "0.4.2",
"unbuild": "latest",
"vue": "3.4.27"
"vue": "3.4.29"
},
"peerDependencies": {
"vue": "^3.3.4"

View File

@ -1,5 +1,5 @@
import { fileURLToPath } from 'node:url'
import createResolver from 'postcss-import-resolver'
import { createCommonJS } from 'mlly'
import { requireModule } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema'
import { defu } from 'defu'
@ -61,9 +61,10 @@ export const getPostcssConfig = (nuxt: Nuxt) => {
// Keep the order of default plugins
if (!Array.isArray(postcssOptions.plugins) && isPureObject(postcssOptions.plugins)) {
// Map postcss plugins into instances on object mode once
const cjs = createCommonJS(import.meta.url)
const cwd = fileURLToPath(new URL('.', import.meta.url))
postcssOptions.plugins = sortPlugins(postcssOptions).map((pluginName: string) => {
const pluginFn = requireModule(pluginName, { paths: [cjs.__dirname] })
// TODO: remove use of requireModule in favour of ESM import
const pluginFn = requireModule(pluginName, { paths: [cwd] })
const pluginOptions = postcssOptions.plugins[pluginName]
if (!pluginOptions || typeof pluginFn !== 'function') { return null }
return pluginFn(pluginOptions)

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,9 @@ if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
npm whoami
fi
# use absolute urls for better rendering on npm
sed -i '' 's/\.\/\.github\/assets/https:\/\/github.com\/nuxt\/nuxt\/tree\/main\/\.github\/assets/g' README.md
# Release packages
for p in packages/* ; do
if [[ $p == "packages/nuxi" ]] ; then

View File

@ -8,6 +8,9 @@ git restore -s@ -SW -- packages examples
# Build all once to ensure things are nice
pnpm build
# use absolute urls for better rendering on npm
sed -i '' 's/\.\/\.github\/assets/https:\/\/github.com\/nuxt\/nuxt\/tree\/main\/\.github\/assets/g' README.md
# Release packages
for PKG in packages/* ; do
if [[ $PKG == "packages/nuxi" ]] ; then

View File

@ -8,6 +8,9 @@ git restore -s@ -SW -- packages examples
# Build all once to ensure things are nice
pnpm build
# use absolute urls for better rendering on npm
sed -i '' 's/\.\/\.github\/assets/https:\/\/github.com\/nuxt\/nuxt\/tree\/main\/\.github\/assets/g' README.md
# Release packages
for PKG in packages/* ; do
if [[ $PKG == "packages/nuxi" ]] ; then

View File

@ -19,7 +19,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
for (const outputDir of ['.output', '.output-inline']) {
it('default client bundle size', async () => {
const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public'))
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot(`"105k"`)
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot(`"106k"`)
expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[
"_nuxt/entry.js",
@ -35,7 +35,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1338k"`)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1340k"`)
const packages = modules.files
.filter(m => m.endsWith('package.json'))
@ -72,7 +72,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"528k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"530k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"76.2k"`)