Merge branch 'main' into fix/21721-spa-loading

This commit is contained in:
Nikolay 2024-11-27 01:09:12 +07:00 committed by GitHub
commit 86db10ea88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 2195 additions and 2120 deletions

View File

@ -1,4 +1,4 @@
FROM node:lts@sha256:de4c8be8232b7081d8846360d73ce6dbff33c6636f2259cd14d82c0de1ac3ff2 FROM node:lts@sha256:5c76d05034644fa8ecc9c2aa84e0a83cd981d0ef13af5455b87b9adf5b216561
RUN apt-get update && \ RUN apt-get update && \
apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \ apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \

View File

@ -46,7 +46,7 @@ jobs:
run: pnpm build run: pnpm build
- name: Run benchmarks - name: Run benchmarks
uses: CodSpeedHQ/action@fa1dcde8d58f2ab0b407a6a24d6cc5a8c1444a8c # v3.1.0 uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0
with: with:
run: pnpm vitest bench run: pnpm vitest bench
token: ${{ secrets.CODSPEED_TOKEN }} token: ${{ secrets.CODSPEED_TOKEN }}

View File

@ -75,7 +75,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
with: with:
config: | config: |
paths: paths:
@ -91,7 +91,7 @@ jobs:
queries: +security-and-quality queries: +security-and-quality
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
with: with:
category: "/language:javascript-typescript" category: "/language:javascript-typescript"
@ -248,7 +248,7 @@ jobs:
TEST_PAYLOAD: ${{ matrix.payload }} TEST_PAYLOAD: ${{ matrix.payload }}
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }} SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 - uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on' if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -19,4 +19,4 @@ jobs:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0

View File

@ -29,7 +29,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Lychee link checker - name: Lychee link checker
uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 # for v1.8.0 uses: lycheeverse/lychee-action@4aa18b6ccdac05029fab067313a6a04f941e6494 # for v1.8.0
with: with:
# arguments with file types to check # arguments with file types to check
args: >- args: >-

View File

@ -1,84 +0,0 @@
name: release-pr
on:
issue_comment:
types: [created]
env:
# 7 GiB by default on GitHub, setting to 6 GiB
NODE_OPTIONS: --max-old-space-size=6144
permissions:
contents: read
jobs:
release-pr:
if: github.repository == 'nuxt/nuxt' && github.event.issue.pull_request && github.event.comment.body == '/trigger release'
concurrency:
group: release
permissions:
id-token: write
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Ensure action is by maintainer
uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0
id: check_role
with:
route: GET /repos/nuxt/nuxt/collaborators/${{ github.event.comment.user.login }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get PR Info
id: pr
env:
PR_NUMBER: ${{ github.event.issue.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
COMMENT_AT: ${{ github.event.comment.created_at }}
run: |
pr="$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/"${GH_REPO}"/pulls/"${PR_NUMBER}")"
head_sha="$(echo "$pr" | jq -r .head.sha)"
updated_at="$(echo "$pr" | jq -r .updated_at)"
if [[ $(date -d "$updated_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
exit 1
fi
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ steps.pr.outputs.head_sha }}
fetch-depth: 1
- run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Release Edge
run: ./scripts/release-edge.sh pr-${{ github.event.issue.number }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
- name: Post comment
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `:rocket: Release triggered! You can now install [nuxt@npm:nuxt-nightly@pr-${{ github.event.issue.number }}](https://www.npmjs.com/package/nuxt-nightly/v/pr-${{ github.event.issue.number }})`
})

View File

@ -68,7 +68,7 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
if: github.repository == 'nuxt/nuxt' && success() if: github.repository == 'nuxt/nuxt' && success()
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -21,8 +21,8 @@ Or follow the steps below to set up a new Nuxt project on your computer.
<!-- markdownlint-disable-next-line MD001 --> <!-- markdownlint-disable-next-line MD001 -->
#### Prerequisites #### Prerequisites
- **Node.js** - [`v18.0.0`](https://nodejs.org/en) or newer - **Node.js** - [`18.x`](https://nodejs.org/en) or newer (but we recommend the [active LTS release](https://github.com/nodejs/release#release-schedule))
- **Text editor** - We recommend [Visual Studio Code](https://code.visualstudio.com/) with the [official Vue extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously known as Volar) - **Text editor** - There is no IDE requirement, but we recommend [Visual Studio Code](https://code.visualstudio.com/) with the [official Vue extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously known as Volar) or [WebStorm](https://www.jetbrains.com/webstorm/), which, along with [other JetBrains IDEs](https://www.jetbrains.com/ides/), offers great Nuxt support right out-of-the-box.
- **Terminal** - In order to run Nuxt commands - **Terminal** - In order to run Nuxt commands
::note ::note

View File

@ -139,7 +139,7 @@ If you only have a single layout in your application, we recommend using [`app.v
If you want to create more layouts and learn how to use them in your pages, find more information in the [Layouts section](/docs/guide/directory-structure/layouts). If you want to create more layouts and learn how to use them in your pages, find more information in the [Layouts section](/docs/guide/directory-structure/layouts).
## Advanced: Extending the HTML template ## Advanced: Extending the HTML Template
::note ::note
If you only need to modify the `<head>`, you can refer to the [SEO and meta section](/docs/getting-started/seo-meta). If you only need to modify the `<head>`, you can refer to the [SEO and meta section](/docs/getting-started/seo-meta).

View File

@ -8,7 +8,7 @@ navigation.icon: i-ph-exclude-square
Nuxt leverages Vue's [`<Transition>`](https://vuejs.org/guide/built-ins/transition.html#the-transition-component) component to apply transitions between pages and layouts. Nuxt leverages Vue's [`<Transition>`](https://vuejs.org/guide/built-ins/transition.html#the-transition-component) component to apply transitions between pages and layouts.
:: ::
## Page transitions ## Page Transitions
You can enable page transitions to apply an automatic transition for all your [pages](/docs/guide/directory-structure/pages). You can enable page transitions to apply an automatic transition for all your [pages](/docs/guide/directory-structure/pages).
@ -113,7 +113,7 @@ Moving to the about page will add the 3d rotation effect:
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665063233/nuxt3/nuxt3-page-transitions-cutom.mp4" type="video/mp4"> <source src="https://res.cloudinary.com/nuxt/video/upload/v1665063233/nuxt3/nuxt3-page-transitions-cutom.mp4" type="video/mp4">
</video> </video>
## Layout transitions ## Layout Transitions
You can enable layout transitions to apply an automatic transition for all your [layouts](/docs/guide/directory-structure/layouts). You can enable layout transitions to apply an automatic transition for all your [layouts](/docs/guide/directory-structure/layouts).
@ -225,7 +225,7 @@ definePageMeta({
</script> </script>
``` ```
## Global settings ## Global Settings
You can customize these default transition names globally using `nuxt.config`. You can customize these default transition names globally using `nuxt.config`.
@ -468,6 +468,6 @@ export default defineNuxtRouteMiddleware(to => {
}) })
``` ```
### Known issues ### Known Issues
- If you perform data fetching within your page setup functions, that you may wish to reconsider using this feature for the moment. (By design, View Transitions completely freeze DOM updates whilst they are taking place.) We're looking at restrict the View Transition to the final moments before `<Suspense>` resolves, but in the interim you may want to consider carefully whether to adopt this feature if this describes you. - If you perform data fetching within your page setup functions, that you may wish to reconsider using this feature for the moment. (By design, View Transitions completely freeze DOM updates whilst they are taking place.) We're looking at restrict the View Transition to the final moments before `<Suspense>` resolves, but in the interim you may want to consider carefully whether to adopt this feature if this describes you.

View File

@ -19,8 +19,7 @@ The minimal `package.json` of your Nuxt application should looks like:
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"devDependencies": { "dependencies": {
"@nuxt/devtools": "latest",
"nuxt": "latest", "nuxt": "latest",
"vue": "latest", "vue": "latest",
"vue-router": "latest" "vue-router": "latest"

View File

@ -41,6 +41,10 @@ Remote islands need `experimental.componentIslands` to be `'local+remote'` in yo
It is strongly discouraged to enable `dangerouslyLoadClientComponents` as you can't trust a remote server's javascript. It is strongly discouraged to enable `dangerouslyLoadClientComponents` as you can't trust a remote server's javascript.
:: ::
::note
By default, component islands are scanned from the `~/components/islands/` directory. So the `~/components/islands/MyIsland.vue` component could be rendered with `<NuxtIsland name="MyIsland" />`.
::
## Slots ## Slots
Slots can be passed to an island component if declared. Slots can be passed to an island component if declared.

View File

@ -1,7 +1,6 @@
--- ---
title: "onPrehydrate" title: "onPrehydrate"
description: "Use onPrehydrate to run a callback on the client immediately before description: "Use onPrehydrate to run a callback on the client immediately before Nuxt hydrates the page."
Nuxt hydrates the page."
links: links:
- label: Source - label: Source
icon: i-simple-icons-github icon: i-simple-icons-github

View File

@ -54,7 +54,7 @@ const { data } = await useAsyncData('todos', () => $fetch('/api/todos'))
const newTodo = ref('') const newTodo = ref('')
const previousTodos = ref([]) const previousTodos = ref([])
// Access to the cached value of useFetch in todos.vue // Access to the cached value of useAsyncData in todos.vue
const { data: todos } = useNuxtData('todos') const { data: todos } = useNuxtData('todos')
const { data } = await useFetch('/api/addTodo', { const { data } = await useFetch('/api/addTodo', {

View File

@ -39,7 +39,7 @@ const title = ref('My title')
useSeoMeta({ useSeoMeta({
title, title,
description: () => `description: ${title.value}` description: () => `This is a description for the ${title.value} page`
}) })
</script> </script>
``` ```

View File

@ -41,8 +41,8 @@ In addition to the Nuxt framework, there are modules that are vital for the ecos
Module | Status | Nuxt Support | Repository | Description Module | Status | Nuxt Support | Repository | Description
------------------------------------|---------------------|--------------|------------|------------------- ------------------------------------|---------------------|--------------|------------|-------------------
[Scripts](https://scripts.nuxt.com) | Public Beta | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management. [Scripts](https://scripts.nuxt.com) | Public Beta | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management.
Auth Utils | Planned | 3.x | `nuxt/auth-utils` to be announced | The temporary repository [atinux/nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils) is available while awaiting its official integration into Nuxt via RFC.
A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255) A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255)
Auth | Planned | 3.x | `nuxt/auth` to be announced | Support is planned after session support.
Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices. Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices.
## Release Cycle ## Release Cycle

View File

@ -72,8 +72,9 @@ export default createConfigForNuxt({
'error', 'error',
{ {
argsIgnorePattern: '^_', argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true, ignoreRestSiblings: true,
varsIgnorePattern: '^_', varsIgnorePattern: '',
}, },
], ],
'@typescript-eslint/triple-slash-reference': 'off', '@typescript-eslint/triple-slash-reference': 'off',
@ -188,7 +189,6 @@ export default createConfigForNuxt({
}, },
}, },
// Sort rule keys in eslint config // Sort rule keys in eslint config
// @ts-expect-error incorrect types 🤔
{ {
files: ['**/eslint.config.mjs'], files: ['**/eslint.config.mjs'],
name: 'local/sort-eslint-config', name: 'local/sort-eslint-config',

View File

@ -1,27 +1,60 @@
{ {
"$schema": "https://unpkg.com/knip@5/schema.json", "$schema": "https://unpkg.com/knip@5/schema.json",
"ignoreBinaries": [
"prepack"
],
"ignoreWorkspaces": ["test/fixtures/basic"],
"ignoreDependencies": [
"bing",
"uno.css"
],
"workspaces": { "workspaces": {
".": { ".": {
"entry": [ "entry": [
"scripts/*", "scripts/*"
"test/*", ]
"test/fixtures/*" },
"test/fixtures/*": {
"entry": [
"nuxt.config.{js,ts}",
"modules/*.ts",
"types.ts"
] ]
}, },
"packages/*": { "packages/*": {
"entry": [ "entry": [
"src/index.ts", "src/index.ts",
"src/runtime/**/*.ts" "src/runtime/**/*.{js,ts,mjs}"
] ]
}, },
"packages/nuxt": { "packages/nuxt": {
"entry": [ "entry": [
"src/app/**/*.ts", "src/app/**/*.ts",
"src/app/*.ts", "src/app/*.ts",
"src/*/runtime/**/*.ts", "src/*/runtime/**/*.{ts,js}",
"src/core/templates.ts", "src/core/templates.ts",
"src/index.ts" "src/index.ts"
] ]
},
"packages/rspack": {
"entry": [
"../webpack/src/index.ts",
"../webpack/src/runtime/**/*.{js,ts,mjs}",
"builder.mjs"
]
},
"packages/ui-templates": {
"entry": [
"lib/*.ts",
"styles.ts"
]
},
"packages/webpack": {
"entry": [
"src/index.ts",
"src/runtime/**/*.{js,ts,mjs}",
"builder.mjs"
]
} }
} }
} }

View File

@ -17,5 +17,8 @@ export default defineNuxtConfig({
}, },
], ],
pages: process.env.DOCS_TYPECHECK === 'true', pages: process.env.DOCS_TYPECHECK === 'true',
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' }, typescript: {
shim: process.env.DOCS_TYPECHECK === 'true',
hoist: ['@vitejs/plugin-vue', 'vue-router'],
},
}) })

View File

@ -17,7 +17,7 @@
"lint:fix": "eslint . --cache --fix", "lint:fix": "eslint . --cache --fix",
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md", "lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md",
"lint:docs:fix": "markdownlint ./docs --fix && case-police 'docs/**/*.md' *.md --fix", "lint:docs:fix": "markdownlint ./docs --fix && case-police 'docs/**/*.md' *.md --fix",
"lint:knip": "pnpx knip", "lint:knip": "knip",
"play": "nuxi dev playground", "play": "nuxi dev playground",
"play:build": "nuxi build playground", "play:build": "nuxi build playground",
"play:generate": "nuxi generate playground", "play:generate": "nuxi generate playground",
@ -37,88 +37,84 @@
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@nuxt/rspack-builder": "workspace:*", "@nuxt/rspack-builder": "workspace:*",
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@nuxt/ui-templates": "workspace:*",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@types/node": "22.9.0", "@types/node": "22.10.0",
"@unhead/dom": "1.11.11", "@unhead/dom": "1.11.13",
"@unhead/shared": "1.11.11", "@unhead/schema": "1.11.13",
"@unhead/vue": "1.11.11", "@unhead/shared": "1.11.13",
"@unhead/schema": "1.11.11", "@unhead/ssr": "1.11.13",
"@unhead/ssr": "1.11.11", "@unhead/vue": "1.11.13",
"@vue/compiler-core": "3.5.12", "@vue/compiler-core": "3.5.13",
"@vue/compiler-dom": "3.5.12", "@vue/compiler-dom": "3.5.13",
"@vue/shared": "3.5.12", "@vue/shared": "3.5.13",
"c12": "2.0.1", "c12": "2.0.1",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"jiti": "2.4.0", "jiti": "2.4.0",
"magic-string": "^0.30.12", "magic-string": "^0.30.14",
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d", "nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"ohash": "1.1.4", "ohash": "1.1.4",
"postcss": "8.4.47", "postcss": "8.4.49",
"rollup": "4.24.4", "rollup": "4.27.4",
"send": ">=1.1.0", "send": ">=1.1.0",
"typescript": "5.6.3", "typescript": "5.6.3",
"ufo": "1.5.4", "ufo": "1.5.4",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"unhead": "1.11.11", "unhead": "1.11.13",
"vite": "5.4.10", "vite": "6.0.0",
"vue": "3.5.12" "vue": "3.5.13"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.14.0", "@nuxt/eslint-config": "0.7.2",
"@nuxt/eslint-config": "0.6.1",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@nuxt/rspack-builder": "workspace:*", "@nuxt/rspack-builder": "workspace:*",
"@nuxt/test-utils": "3.14.4", "@nuxt/test-utils": "3.14.4",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/eslint__js": "8.42.3", "@types/node": "22.10.0",
"@types/node": "22.9.0",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@unhead/schema": "1.11.11", "@unhead/schema": "1.11.13",
"@unhead/vue": "1.11.11", "@unhead/vue": "1.11.13",
"@vitejs/plugin-vue": "5.1.4", "@vitest/coverage-v8": "2.1.6",
"@vitest/coverage-v8": "2.1.4",
"@vue/test-utils": "2.4.6", "@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
"case-police": "0.7.0", "case-police": "0.7.2",
"changelogen": "0.5.7", "changelogen": "0.5.7",
"consola": "3.2.3", "consola": "3.2.3",
"cssnano": "7.0.6", "cssnano": "7.0.6",
"destr": "2.0.3", "destr": "2.0.3",
"devalue": "5.1.1", "devalue": "5.1.1",
"eslint": "9.14.0", "eslint": "9.15.0",
"eslint-plugin-no-only-tests": "3.3.0", "eslint-plugin-no-only-tests": "3.3.0",
"eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-perfectionist": "4.1.2",
"eslint-typegen": "0.3.2", "eslint-typegen": "0.3.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"happy-dom": "15.11.0", "happy-dom": "15.11.6",
"jiti": "2.4.0", "jiti": "2.4.0",
"markdownlint-cli": "0.42.0", "knip": "5.38.0",
"markdownlint-cli": "0.43.0",
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d", "nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
"nuxi": "3.15.0", "nuxi": "3.15.0",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"nuxt-content-twoslash": "0.1.1", "nuxt-content-twoslash": "0.1.1",
"ofetch": "1.4.1", "ofetch": "1.4.1",
"pathe": "1.1.2", "pathe": "1.1.2",
"playwright-core": "1.48.2", "playwright-core": "1.49.0",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"semver": "7.6.3", "semver": "7.6.3",
"sherif": "1.0.1", "sherif": "1.0.2",
"std-env": "3.8.0", "std-env": "3.8.0",
"tinyexec": "0.3.1", "tinyexec": "0.3.1",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.10",
"typescript": "5.6.3", "typescript": "5.6.3",
"ufo": "1.5.4", "ufo": "1.5.4",
"vitest": "2.1.4", "vitest": "2.1.6",
"vitest-environment-nuxt": "1.0.1", "vitest-environment-nuxt": "1.0.1",
"vue": "3.5.12", "vue": "3.5.13",
"vue-router": "4.4.5",
"vue-tsc": "2.1.10" "vue-tsc": "2.1.10"
}, },
"packageManager": "pnpm@9.12.3", "packageManager": "pnpm@9.14.2",
"engines": { "engines": {
"node": "^16.10.0 || >=18.0.0" "node": "^16.10.0 || >=18.0.0"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@nuxt/kit", "name": "@nuxt/kit",
"version": "3.12.2", "version": "4.0.0-0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/nuxt/nuxt.git", "url": "git+https://github.com/nuxt/nuxt.git",
@ -33,28 +33,27 @@
"destr": "^2.0.3", "destr": "^2.0.3",
"errx": "^0.1.0", "errx": "^0.1.0",
"globby": "^14.0.2", "globby": "^14.0.2",
"hash-sum": "^2.0.0",
"ignore": "^6.0.2", "ignore": "^6.0.2",
"jiti": "^2.4.0", "jiti": "^2.4.0",
"klona": "^2.0.6", "klona": "^2.0.6",
"mlly": "^1.7.2", "mlly": "^1.7.3",
"ohash": "^1.1.4",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"pkg-types": "^1.2.1", "pkg-types": "^1.2.1",
"scule": "^1.3.0", "scule": "^1.3.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unimport": "^3.13.1", "unimport": "^3.13.3",
"untyped": "^1.5.1" "untyped": "^1.5.1"
}, },
"devDependencies": { "devDependencies": {
"@rspack/core": "1.1.0", "@rspack/core": "1.1.4",
"@types/hash-sum": "1.0.2",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d", "nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"vite": "5.4.10", "vite": "6.0.0",
"vitest": "2.1.4", "vitest": "2.1.6",
"webpack": "5.96.1" "webpack": "5.96.1"
}, },
"engines": { "engines": {

View File

@ -28,7 +28,7 @@ export type { AddPluginOptions } from './plugin'
export { createResolver, findPath, resolveAlias, resolveFiles, resolveNuxtModule, resolvePath } from './resolve' export { createResolver, findPath, resolveAlias, resolveFiles, resolveNuxtModule, resolvePath } from './resolve'
export type { ResolvePathOptions, Resolver } from './resolve' export type { ResolvePathOptions, Resolver } from './resolve'
export { addServerHandler, addDevServerHandler, addServerPlugin, addPrerenderRoutes, useNitro, addServerImports, addServerImportsDir, addServerScanDir } from './nitro' export { addServerHandler, addDevServerHandler, addServerPlugin, addPrerenderRoutes, useNitro, addServerImports, addServerImportsDir, addServerScanDir } from './nitro'
export { addTemplate, addTypeTemplate, normalizeTemplate, updateTemplates, writeTypes } from './template' export { addTemplate, addServerTemplate, addTypeTemplate, normalizeTemplate, updateTemplates, writeTypes } from './template'
export { logger, useLogger } from './logger' export { logger, useLogger } from './logger'
// Internal Utils // Internal Utils

View File

@ -65,7 +65,7 @@ function _defineNuxtModule<
const optionsDefaults: TOptionsDefaults = const optionsDefaults: TOptionsDefaults =
module.defaults instanceof Function module.defaults instanceof Function
? module.defaults(nuxt) ? await module.defaults(nuxt)
: module.defaults ?? <TOptionsDefaults> {} : module.defaults ?? <TOptionsDefaults> {}
let options = defu(inlineOptions, nuxtConfigOptions, optionsDefaults) let options = defu(inlineOptions, nuxtConfigOptions, optionsDefaults)

View File

@ -5,6 +5,7 @@ import { dirname, isAbsolute, join, resolve } from 'pathe'
import { defu } from 'defu' import { defu } from 'defu'
import { createJiti } from 'jiti' import { createJiti } from 'jiti'
import { resolve as resolveModule } from 'mlly' import { resolve as resolveModule } from 'mlly'
import { isRelative } from 'ufo'
import { useNuxt } from '../context' import { useNuxt } from '../context'
import { resolveAlias, resolvePath } from '../resolve' import { resolveAlias, resolvePath } from '../resolve'
import { logger } from '../logger' import { logger } from '../logger'
@ -78,14 +79,23 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
// Import if input is string // Import if input is string
if (typeof nuxtModule === 'string') { if (typeof nuxtModule === 'string') {
const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule, join(nuxt.options.rootDir, nuxtModule)] const paths = new Set<string>()
nuxtModule = resolveAlias(nuxtModule, nuxt.options.alias)
if (isRelative(nuxtModule)) {
nuxtModule = resolve(nuxt.options.rootDir, nuxtModule)
}
paths.add(join(nuxtModule, 'nuxt'))
paths.add(join(nuxtModule, 'module'))
paths.add(nuxtModule)
for (const path of paths) { for (const path of paths) {
for (const parentURL of nuxt.options.modulesDir) { for (const parentURL of nuxt.options.modulesDir) {
try { try {
const resolved = resolveAlias(path, nuxt.options.alias) const src = isAbsolute(path)
const src = isAbsolute(resolved) ? pathToFileURL(await resolvePath(path, { cwd: parentURL, fallbackToOriginal: false, extensions: nuxt.options.extensions })).href
? pathToFileURL(await resolvePath(resolved, { cwd: parentURL, fallbackToOriginal: false, extensions: nuxt.options.extensions })).href : await resolveModule(path, { url: pathToFileURL(parentURL.replace(/\/node_modules\/?$/, '')), extensions: nuxt.options.extensions })
: await resolveModule(resolved, { url: pathToFileURL(parentURL.replace(/\/node_modules\/?$/, '')), extensions: nuxt.options.extensions })
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule nuxtModule = await jiti.import(src, { default: true }) as NuxtModule

View File

@ -1,6 +1,6 @@
import { existsSync, promises as fsp } from 'node:fs' import { existsSync, promises as fsp } from 'node:fs'
import { basename, isAbsolute, join, parse, relative, resolve } from 'pathe' import { basename, isAbsolute, join, parse, relative, resolve } from 'pathe'
import hash from 'hash-sum' import { hash } from 'ohash'
import type { Nuxt, NuxtServerTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema' import type { Nuxt, NuxtServerTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema'
import { withTrailingSlash } from 'ufo' import { withTrailingSlash } from 'ufo'
import { defu } from 'defu' import { defu } from 'defu'
@ -22,9 +22,9 @@ export function addTemplate<T> (_template: NuxtTemplate<T> | string) {
// Normalize template // Normalize template
const template = normalizeTemplate(_template) const template = normalizeTemplate(_template)
// Remove any existing template with the same filename // Remove any existing template with the same destination path
nuxt.options.build.templates = nuxt.options.build.templates nuxt.options.build.templates = nuxt.options.build.templates
.filter(p => normalizeTemplate(p).filename !== template.filename) .filter(p => normalizeTemplate(p).dst !== template.dst)
// Add to templates array // Add to templates array
nuxt.options.build.templates.push(template) nuxt.options.build.templates.push(template)

View File

@ -60,16 +60,16 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/devalue": "^2.0.2", "@nuxt/devalue": "^2.0.2",
"@nuxt/devtools": "^1.6.0", "@nuxt/devtools": "^1.6.1",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.6.0", "@nuxt/telemetry": "^2.6.0",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@unhead/dom": "^1.11.11", "@unhead/dom": "^1.11.13",
"@unhead/shared": "^1.11.11", "@unhead/shared": "^1.11.13",
"@unhead/ssr": "^1.11.11", "@unhead/ssr": "^1.11.13",
"@unhead/vue": "^1.11.11", "@unhead/vue": "^1.11.13",
"@vue/shared": "^3.5.12", "@vue/shared": "^3.5.13",
"acorn": "8.14.0", "acorn": "8.14.0",
"c12": "^2.0.1", "c12": "^2.0.1",
"chokidar": "^4.0.1", "chokidar": "^4.0.1",
@ -91,8 +91,8 @@
"jiti": "^2.4.0", "jiti": "^2.4.0",
"klona": "^2.0.6", "klona": "^2.0.6",
"knitwork": "^1.1.0", "knitwork": "^1.1.0",
"magic-string": "^0.30.12", "magic-string": "^0.30.14",
"mlly": "^1.7.2", "mlly": "^1.7.3",
"nanotar": "^0.1.1", "nanotar": "^0.1.1",
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d", "nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
"nuxi": "^3.15.0", "nuxi": "^3.15.0",
@ -106,34 +106,33 @@
"scule": "^1.3.0", "scule": "^1.3.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"std-env": "^3.8.0", "std-env": "^3.8.0",
"strip-literal": "^2.1.0", "strip-literal": "^2.1.1",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.10",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"ultrahtml": "^1.5.3", "ultrahtml": "^1.5.3",
"uncrypto": "^0.1.3", "uncrypto": "^0.1.3",
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unhead": "^1.11.11", "unhead": "^1.11.13",
"unimport": "^3.13.1", "unimport": "^3.13.3",
"unplugin": "^1.15.0", "unplugin": "^1.16.0",
"unplugin-vue-router": "^0.10.8", "unplugin-vue-router": "^0.10.8",
"unstorage": "^1.13.1", "unstorage": "^1.13.1",
"untyped": "^1.5.1", "untyped": "^1.5.1",
"vue": "^3.5.12", "vue": "^3.5.13",
"vue-bundle-renderer": "^2.1.1", "vue-bundle-renderer": "^2.1.1",
"vue-devtools-stub": "^0.1.0", "vue-devtools-stub": "^0.1.0",
"vue-router": "^4.4.5" "vue-router": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/scripts": "0.9.5", "@nuxt/scripts": "0.9.5",
"@nuxt/ui-templates": "1.3.4",
"@parcel/watcher": "2.5.0", "@parcel/watcher": "2.5.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@vitejs/plugin-vue": "5.1.4", "@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.12", "@vue/compiler-sfc": "3.5.13",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"vite": "5.4.10", "vite": "6.0.0",
"vitest": "2.1.4" "vitest": "2.1.6"
}, },
"peerDependencies": { "peerDependencies": {
"@parcel/watcher": "^2.1.0", "@parcel/watcher": "^2.1.0",

View File

@ -16,7 +16,6 @@ import layouts from '#build/layouts'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { appLayoutTransition as defaultLayoutTransition } from '#build/nuxt.config.mjs' import { appLayoutTransition as defaultLayoutTransition } from '#build/nuxt.config.mjs'
// TODO: revert back to defineAsyncComponent when https://github.com/vuejs/core/issues/6638 is resolved
const LayoutLoader = defineComponent({ const LayoutLoader = defineComponent({
name: 'LayoutLoader', name: 'LayoutLoader',
inheritAttrs: false, inheritAttrs: false,
@ -24,13 +23,10 @@ const LayoutLoader = defineComponent({
name: String, name: String,
layoutProps: Object, layoutProps: Object,
}, },
async setup (props, context) { setup (props, context) {
// This is a deliberate hack - this component must always be called with an explicit key to ensure // This is a deliberate hack - this component must always be called with an explicit key to ensure
// that setup reruns when the name changes. // that setup reruns when the name changes.
return () => h(layouts[props.name], props.layoutProps, context.slots)
const LayoutComponent = await layouts[props.name]().then((r: any) => r.default || r)
return () => h(LayoutComponent, props.layoutProps, context.slots)
}, },
}) })

View File

@ -506,9 +506,9 @@ function useObserver (): { observe: ObserveFn } | undefined {
observer.observe(element) observer.observe(element)
return () => { return () => {
callbacks.delete(element) callbacks.delete(element)
observer!.unobserve(element) observer?.unobserve(element)
if (callbacks.size === 0) { if (callbacks.size === 0) {
observer!.disconnect() observer?.disconnect()
observer = null observer = null
} }
} }

View File

@ -525,7 +525,7 @@ function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
} }
if (nuxtApp._asyncData[key]) { if (nuxtApp._asyncData[key]) {
nuxtApp._asyncData[key]!.data.value = nuxtApp._asyncData[key]!._default() nuxtApp._asyncData[key]!.data.value = unref(nuxtApp._asyncData[key]!._default())
nuxtApp._asyncData[key]!.error.value = undefined nuxtApp._asyncData[key]!.error.value = undefined
nuxtApp._asyncData[key]!.pending.value = false nuxtApp._asyncData[key]!.pending.value = false
nuxtApp._asyncData[key]!.status.value = 'idle' nuxtApp._asyncData[key]!.status.value = 'idle'

View File

@ -1,6 +1,7 @@
import { createUnplugin } from 'unplugin' import { createUnplugin } from 'unplugin'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import type { Component } from 'nuxt/schema' import type { Component } from 'nuxt/schema'
import type { Program } from 'acorn'
import { SX_RE, isVue } from '../../core/utils' import { SX_RE, isVue } from '../../core/utils'
interface NameDevPluginOptions { interface NameDevPluginOptions {
@ -34,6 +35,16 @@ export const ComponentNamePlugin = (options: NameDevPluginOptions) => createUnpl
const s = new MagicString(code) const s = new MagicString(code)
s.replace(NAME_RE, `__name: ${JSON.stringify(component.pascalName)}`) s.replace(NAME_RE, `__name: ${JSON.stringify(component.pascalName)}`)
// Without setup function, vue compiler does not generate __name
if (!s.hasChanged()) {
const ast = this.parse(code) as Program
const exportDefault = ast.body.find(node => node.type === 'ExportDefaultDeclaration')
if (exportDefault) {
const { start, end } = exportDefault.declaration
s.overwrite(start, end, `Object.assign(${code.slice(start, end)}, { __name: ${JSON.stringify(component.pascalName)} })`)
}
}
if (s.hasChanged()) { if (s.hasChanged()) {
return { return {
code: s.toString(), code: s.toString(),

View File

@ -10,7 +10,6 @@ import { generateApp as _generateApp, createApp } from './app'
import { checkForExternalConfigurationFiles } from './external-config-files' import { checkForExternalConfigurationFiles } from './external-config-files'
import { cleanupCaches, getVueHash } from './cache' import { cleanupCaches, getVueHash } from './cache'
const IS_RESTART_PATH_RE = /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i
export async function build (nuxt: Nuxt) { export async function build (nuxt: Nuxt) {
const app = createApp(nuxt) const app = createApp(nuxt)
nuxt.apps.default = app nuxt.apps.default = app
@ -21,19 +20,24 @@ export async function build (nuxt: Nuxt) {
if (nuxt.options.dev) { if (nuxt.options.dev) {
watch(nuxt) watch(nuxt)
nuxt.hook('builder:watch', async (event, relativePath) => { nuxt.hook('builder:watch', async (event, relativePath) => {
if (event === 'change') { return } // Unset mainComponent and errorComponent if app or error component is changed
const path = resolve(nuxt.options.srcDir, relativePath) if (event === 'add' || event === 'unlink') {
const relativePaths = nuxt.options._layers.map(l => relative(l.config.srcDir || l.cwd, path)) const path = resolve(nuxt.options.srcDir, relativePath)
const restartPath = relativePaths.find(relativePath => IS_RESTART_PATH_RE.test(relativePath)) for (const layer of nuxt.options._layers) {
if (restartPath) { const relativePath = relative(layer.config.srcDir || layer.cwd, path)
if (restartPath.startsWith('app')) { if (relativePath.match(/^app\./i)) {
app.mainComponent = undefined app.mainComponent = undefined
break
}
if (relativePath.match(/^error\./i)) {
app.errorComponent = undefined
break
}
} }
if (restartPath.startsWith('error')) {
app.errorComponent = undefined
}
await generateApp()
} }
// Recompile app templates
await generateApp()
}) })
nuxt.hook('builder:generateApp', (options) => { nuxt.hook('builder:generateApp', (options) => {
// Bypass debounce if we are selectively invalidating templates // Bypass debounce if we are selectively invalidating templates

View File

@ -6,7 +6,7 @@ import { isIgnored } from '@nuxt/kit'
import type { Nuxt, NuxtConfig, NuxtConfigLayer } from '@nuxt/schema' import type { Nuxt, NuxtConfig, NuxtConfigLayer } from '@nuxt/schema'
import { hash, murmurHash, objectHash } from 'ohash' import { hash, murmurHash, objectHash } from 'ohash'
import { glob } from 'tinyglobby' import { glob } from 'tinyglobby'
import _consola, { consola } from 'consola' import { consola } from 'consola'
import { dirname, join, relative } from 'pathe' import { dirname, join, relative } from 'pathe'
import { createTar, parseTar } from 'nanotar' import { createTar, parseTar } from 'nanotar'
import type { TarFileInput } from 'nanotar' import type { TarFileInput } from 'nanotar'

View File

@ -1,6 +1,5 @@
import { findPath, logger } from '@nuxt/kit' import { findPath, logger } from '@nuxt/kit'
import { basename } from 'pathe' import { basename } from 'pathe'
import { generateApp as _generateApp } from './app'
/** /**
* Check for those external configuration files that are not compatible with Nuxt, * Check for those external configuration files that are not compatible with Nuxt,

View File

@ -5,7 +5,6 @@ import { createDebugger, createHooks } from 'hookable'
import ignore from 'ignore' import ignore from 'ignore'
import type { LoadNuxtOptions } from '@nuxt/kit' import type { LoadNuxtOptions } from '@nuxt/kit'
import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit' import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
import { resolvePath as _resolvePath } from 'mlly'
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema' import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema'
import type { PackageJson } from 'pkg-types' import type { PackageJson } from 'pkg-types'
import { readPackageJSON } from 'pkg-types' import { readPackageJSON } from 'pkg-types'
@ -41,6 +40,7 @@ import { initNitro } from './nitro'
import schemaModule from './schema' import schemaModule from './schema'
import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata' import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata'
import { AsyncContextInjectionPlugin } from './plugins/async-context' import { AsyncContextInjectionPlugin } from './plugins/async-context'
import { ComposableKeysPlugin } from './plugins/composable-keys'
import { resolveDeepImportsPlugin } from './plugins/resolve-deep-imports' import { resolveDeepImportsPlugin } from './plugins/resolve-deep-imports'
import { prehydrateTransformPlugin } from './plugins/prehydrate' import { prehydrateTransformPlugin } from './plugins/prehydrate'
import { VirtualFSPlugin } from './plugins/virtual' import { VirtualFSPlugin } from './plugins/virtual'
@ -248,6 +248,13 @@ async function initNuxt (nuxt: Nuxt) {
// Add plugin normalization plugin // Add plugin normalization plugin
addBuildPlugin(RemovePluginMetadataPlugin(nuxt)) addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
// Add keys for useFetch, useAsyncData, etc.
addBuildPlugin(ComposableKeysPlugin({
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables,
}))
// shared folder import protection // shared folder import protection
const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared)) const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared))
const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared))) const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))
@ -409,11 +416,15 @@ async function initNuxt (nuxt: Nuxt) {
} }
// Add <NuxtWelcome> // Add <NuxtWelcome>
addComponent({ // TODO: revert when deep server component config is properly bundle-split: https://github.com/nuxt/nuxt/pull/29956
name: 'NuxtWelcome', const islandsConfig = nuxt.options.experimental.componentIslands
priority: 10, // built-in that we do not expect the user to override if (nuxt.options.dev || !(typeof islandsConfig === 'object' && islandsConfig.selectiveClient === 'deep')) {
filePath: resolve(nuxt.options.appDir, 'components/welcome'), addComponent({
}) name: 'NuxtWelcome',
priority: 10, // built-in that we do not expect the user to override
filePath: resolve(nuxt.options.appDir, 'components/welcome'),
})
}
addComponent({ addComponent({
name: 'NuxtLayout', name: 'NuxtLayout',

View File

@ -9,7 +9,7 @@ import type { CallExpression, Pattern } from 'estree'
import { parseQuery, parseURL } from 'ufo' import { parseQuery, parseURL } from 'ufo'
import escapeRE from 'escape-string-regexp' import escapeRE from 'escape-string-regexp'
import { findStaticImports, parseStaticImport } from 'mlly' import { findStaticImports, parseStaticImport } from 'mlly'
import { matchWithStringOrRegex } from '../utils' import { matchWithStringOrRegex } from '../utils/plugins'
interface ComposableKeysOptions { interface ComposableKeysOptions {
sourcemap: boolean sourcemap: boolean
@ -22,7 +22,7 @@ const NUXT_LIB_RE = /node_modules\/(?:nuxt|nuxt3|nuxt-nightly)\//
const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/ const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/
const SCRIPT_RE = /(?<=<script[^>]*>)[\s\S]*?(?=<\/script>)/i const SCRIPT_RE = /(?<=<script[^>]*>)[\s\S]*?(?=<\/script>)/i
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => { export const ComposableKeysPlugin = (options: ComposableKeysOptions) => createUnplugin(() => {
const composableMeta: Record<string, any> = {} const composableMeta: Record<string, any> = {}
const composableLengths = new Set<number>() const composableLengths = new Set<number>()
const keyedFunctions = new Set<string>() const keyedFunctions = new Set<string>()

View File

@ -36,7 +36,7 @@ export const createImportProtectionPatterns = (nuxt: { options: NuxtOptions }, o
]) ])
} }
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) { for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
patterns.push([i, `This module cannot be imported in ${context}.`]) patterns.push([i, `This module cannot be imported in ${context}.`])
} }

View File

@ -125,18 +125,26 @@ export const RemovePluginMetadataPlugin = (nuxt: Nuxt) => createUnplugin(() => {
const plugin = nuxt.apps.default?.plugins.find(p => p.src === id) const plugin = nuxt.apps.default?.plugins.find(p => p.src === id)
if (!plugin) { return } if (!plugin) { return }
const s = new MagicString(code) if (!code.trim()) {
logger.warn(`Plugin \`${plugin.src}\` has no content.`)
return {
code: 'export default () => {}',
map: null,
}
}
const exports = findExports(code) const exports = findExports(code)
const defaultExport = exports.find(e => e.type === 'default' || e.name === 'default') const defaultExport = exports.find(e => e.type === 'default' || e.name === 'default')
if (!defaultExport) { if (!defaultExport) {
logger.warn(`Plugin \`${plugin.src}\` has no default export and will be ignored at build time. Add \`export default defineNuxtPlugin(() => {})\` to your plugin.`) logger.warn(`Plugin \`${plugin.src}\` has no default export and will be ignored at build time. Add \`export default defineNuxtPlugin(() => {})\` to your plugin.`)
s.overwrite(0, code.length, 'export default () => {}')
return { return {
code: s.toString(), code: 'export default () => {}',
map: nuxt.options.sourcemap.client || nuxt.options.sourcemap.server ? s.generateMap({ hires: true }) : null, map: null,
} }
} }
const s = new MagicString(code)
let wrapped = false let wrapped = false
const wrapperNames = new Set(['defineNuxtPlugin', 'definePayloadPlugin']) const wrapperNames = new Set(['defineNuxtPlugin', 'definePayloadPlugin'])

View File

@ -285,9 +285,10 @@ export const layoutTemplate: NuxtTemplate = {
filename: 'layouts.mjs', filename: 'layouts.mjs',
getContents ({ app }) { getContents ({ app }) {
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => { const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
return [name, genDynamicImport(file)] return [name, `defineAsyncComponent(${genDynamicImport(file)})`]
})) }))
return [ return [
`import { defineAsyncComponent } from 'vue'`,
`export default ${layoutsObject}`, `export default ${layoutsObject}`,
].join('\n') ].join('\n')
}, },

View File

@ -54,3 +54,13 @@ export function getLoader (id: string): 'vue' | 'ts' | 'tsx' | null {
} }
return ext.endsWith('x') ? 'tsx' : 'ts' return ext.endsWith('x') ? 'tsx' : 'ts'
} }
export function matchWithStringOrRegex (value: string, matcher: string | RegExp) {
if (typeof matcher === 'string') {
return value === matcher
} else if (matcher instanceof RegExp) {
return matcher.test(value)
}
return false
}

View File

@ -107,12 +107,9 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
const priorities = nuxt.options._layers.map((layer, i) => [layer.config.srcDir, -i] as const).sort(([a], [b]) => b.length - a.length) const priorities = nuxt.options._layers.map((layer, i) => [layer.config.srcDir, -i] as const).sort(([a], [b]) => b.length - a.length)
const IMPORTS_TEMPLATE_RE = /\/imports\.(?:d\.ts|mjs)$/
function isImportsTemplate (template: ResolvedNuxtTemplate) { function isImportsTemplate (template: ResolvedNuxtTemplate) {
return [ return IMPORTS_TEMPLATE_RE.test(template.filename)
'/types/imports.d.ts',
'/imports.d.ts',
'/imports.mjs',
].some(i => template.filename.endsWith(i))
} }
const regenerateImports = async () => { const regenerateImports = async () => {

View File

@ -242,6 +242,8 @@ const vueTypesPreset = defineUnimportPreset({
'Component', 'Component',
'ComponentPublicInstance', 'ComponentPublicInstance',
'ComputedRef', 'ComputedRef',
'DirectiveBinding',
'ExtractDefaultPropTypes',
'ExtractPropTypes', 'ExtractPropTypes',
'ExtractPublicPropTypes', 'ExtractPublicPropTypes',
'InjectionKey', 'InjectionKey',
@ -250,6 +252,7 @@ const vueTypesPreset = defineUnimportPreset({
'MaybeRef', 'MaybeRef',
'MaybeRefOrGetter', 'MaybeRefOrGetter',
'VNode', 'VNode',
'WritableComputedRef',
], ],
}) })

View File

@ -77,8 +77,8 @@ export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
} else { } else {
const augmentedPages = await augmentPages(pages, nuxt.vfs) const augmentedPages = await augmentPages(pages, nuxt.vfs)
await nuxt.callHook('pages:extend', pages) await nuxt.callHook('pages:extend', pages)
await augmentPages(pages, nuxt.vfs, augmentedPages) await augmentPages(pages, nuxt.vfs, { pagesToSkip: augmentedPages })
augmentedPages.clear() augmentedPages?.clear()
} }
await nuxt.callHook('pages:resolved', pages) await nuxt.callHook('pages:resolved', pages)
@ -155,9 +155,14 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
return prepareRoutes(routes) return prepareRoutes(routes)
} }
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<string>()) { interface AugmentPagesContext {
pagesToSkip?: Set<string>
augmentedPages?: Set<string>
}
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, ctx: AugmentPagesContext = {}) {
ctx.augmentedPages ??= new Set()
for (const route of routes) { for (const route of routes) {
if (route.file && !augmentedPages.has(route.file)) { if (route.file && !ctx.pagesToSkip?.has(route.file)) {
const fileContent = route.file in vfs ? vfs[route.file]! : fs.readFileSync(await resolvePath(route.file), 'utf-8') const fileContent = route.file in vfs ? vfs[route.file]! : fs.readFileSync(await resolvePath(route.file), 'utf-8')
const routeMeta = await getRouteMeta(fileContent, route.file) const routeMeta = await getRouteMeta(fileContent, route.file)
if (route.meta) { if (route.meta) {
@ -165,14 +170,14 @@ export async function augmentPages (routes: NuxtPage[], vfs: Record<string, stri
} }
Object.assign(route, routeMeta) Object.assign(route, routeMeta)
augmentedPages.add(route.file) ctx.augmentedPages.add(route.file)
} }
if (route.children && route.children.length > 0) { if (route.children && route.children.length > 0) {
await augmentPages(route.children, vfs, augmentedPages) await augmentPages(route.children, vfs, ctx)
} }
} }
return augmentedPages return ctx.augmentedPages
} }
const SFC_SCRIPT_RE = /<script(?<attrs>[^>]*)>(?<content>[\s\S]*?)<\/script[^>]*>/gi const SFC_SCRIPT_RE = /<script(?<attrs>[^>]*)>(?<content>[\s\S]*?)<\/script[^>]*>/gi

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { detectImportNames } from '../src/plugins/composable-keys' import { detectImportNames } from '../src/core/plugins/composable-keys'
describe('detectImportNames', () => { describe('detectImportNames', () => {
const keyedComposables = { const keyedComposables = {

View File

@ -24,6 +24,7 @@ const testsToTriggerOn = [
['some-nuxt-module', 'components/Component.vue', true], ['some-nuxt-module', 'components/Component.vue', true],
['/root/src/server/api/test.ts', 'components/Component.vue', true], ['/root/src/server/api/test.ts', 'components/Component.vue', true],
['src/server/api/test.ts', 'components/Component.vue', true], ['src/server/api/test.ts', 'components/Component.vue', true],
['node_modules/nitropack/node_modules/crossws/dist/adapters/bun.mjs', 'node_modules/nitropack/dist/presets/bun/runtime/bun.mjs', false],
] as const ] as const
describe('import protection', () => { describe('import protection', () => {

View File

@ -31,7 +31,7 @@
"dependencies": { "dependencies": {
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0", "@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@rspack/core": "^1.1.0", "@rspack/core": "^1.1.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0", "css-minimizer-webpack-plugin": "^7.0.0",
@ -39,22 +39,19 @@
"defu": "^6.1.4", "defu": "^6.1.4",
"esbuild-loader": "^4.2.2", "esbuild-loader": "^4.2.2",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.2", "fork-ts-checker-webpack-plugin": "^9.0.2",
"globby": "^14.0.2", "globby": "^14.0.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hash-sum": "^2.0.0",
"jiti": "^2.4.0", "jiti": "^2.4.0",
"knitwork": "^1.1.0", "knitwork": "^1.1.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"magic-string": "^0.30.12", "magic-string": "^0.30.14",
"memfs": "^4.14.0", "memfs": "^4.14.0",
"mlly": "^1.7.2",
"ohash": "^1.1.4", "ohash": "^1.1.4",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"pify": "^6.1.0", "pify": "^6.1.0",
"postcss": "^8.4.47", "postcss": "^8.4.49",
"postcss-import": "^16.1.0", "postcss-import": "^16.1.0",
"postcss-import-resolver": "^2.0.0", "postcss-import-resolver": "^2.0.0",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
@ -64,26 +61,24 @@
"time-fix-plugin": "^2.0.7", "time-fix-plugin": "^2.0.7",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unplugin": "^1.15.0", "unplugin": "^1.16.0",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-bundle-renderer": "^2.1.1", "vue-bundle-renderer": "^2.1.1",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
"webpack-bundle-analyzer": "^4.10.2", "webpack-bundle-analyzer": "^4.10.2",
"webpack-dev-middleware": "^7.4.2", "webpack-dev-middleware": "^7.4.2",
"webpack-hot-middleware": "^2.26.1", "webpack-hot-middleware": "^2.26.1",
"webpack-virtual-modules": "^0.6.2",
"webpackbar": "^7.0.0" "webpackbar": "^7.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@types/hash-sum": "1.0.2",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.12",
"@types/pify": "5.0.4", "@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.24.4", "rollup": "4.27.4",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"vue": "3.5.12" "vue": "3.5.13"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.3.4" "vue": "^3.3.4"

View File

@ -27,7 +27,6 @@ export default defineBuildConfig({
'autoprefixer', 'autoprefixer',
'ofetch', 'ofetch',
'vue-router', 'vue-router',
'@nuxt/telemetry',
'vue-bundle-renderer', 'vue-bundle-renderer',
'@unhead/schema', '@unhead/schema',
'vue', 'vue',
@ -55,7 +54,6 @@ export default defineBuildConfig({
'pug', 'pug',
'sass-loader', 'sass-loader',
'c12', 'c12',
'unenv',
'@vue/language-core', '@vue/language-core',
// Implicit // Implicit
'@vue/compiler-core', '@vue/compiler-core',

View File

@ -34,16 +34,14 @@
"prepack": "unbuild" "prepack": "unbuild"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/telemetry": "2.6.0",
"@nuxt/ui-templates": "1.3.4",
"@types/file-loader": "5.0.4", "@types/file-loader": "5.0.4",
"@types/pug": "2.0.10", "@types/pug": "2.0.10",
"@types/sass-loader": "8.0.9", "@types/sass-loader": "8.0.9",
"@unhead/schema": "1.11.11", "@unhead/schema": "1.11.13",
"@vitejs/plugin-vue": "5.1.4", "@vitejs/plugin-vue": "5.2.1",
"@vitejs/plugin-vue-jsx": "4.0.1", "@vitejs/plugin-vue-jsx": "4.1.1",
"@vue/compiler-core": "3.5.12", "@vue/compiler-core": "3.5.13",
"@vue/compiler-sfc": "3.5.12", "@vue/compiler-sfc": "3.5.13",
"@vue/language-core": "2.1.10", "@vue/language-core": "2.1.10",
"esbuild-loader": "4.2.2", "esbuild-loader": "4.2.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
@ -52,12 +50,11 @@
"ofetch": "1.4.1", "ofetch": "1.4.1",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"unctx": "2.3.1", "unctx": "2.3.1",
"unenv": "1.10.0", "vite": "6.0.0",
"vite": "5.4.10", "vue": "3.5.13",
"vue": "3.5.12",
"vue-bundle-renderer": "2.1.1", "vue-bundle-renderer": "2.1.1",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue-router": "4.4.5", "vue-router": "4.5.0",
"webpack": "5.96.1", "webpack": "5.96.1",
"webpack-dev-middleware": "7.4.2" "webpack-dev-middleware": "7.4.2"
}, },
@ -73,7 +70,7 @@
"std-env": "^3.8.0", "std-env": "^3.8.0",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"uncrypto": "^0.1.3", "uncrypto": "^0.1.3",
"unimport": "^3.13.1", "unimport": "^3.13.3",
"untyped": "^1.5.1" "untyped": "^1.5.1"
}, },
"engines": { "engines": {

View File

@ -64,7 +64,7 @@ export interface ModuleDefinition<
TWith extends boolean, TWith extends boolean,
> { > {
meta?: ModuleMeta meta?: ModuleMeta
defaults?: TOptionsDefaults | ((nuxt: Nuxt) => TOptionsDefaults) defaults?: TOptionsDefaults | ((nuxt: Nuxt) => Awaitable<TOptionsDefaults>)
schema?: TOptions schema?: TOptions
hooks?: Partial<NuxtHooks> hooks?: Partial<NuxtHooks>
setup?: ( setup?: (

View File

@ -14,22 +14,22 @@
"dev": "vite", "dev": "vite",
"optimize-assets": "npx svgo public/assets/**/*.svg", "optimize-assets": "npx svgo public/assets/**/*.svg",
"postinstall": "pnpm build", "postinstall": "pnpm build",
"prerender": "pnpm build && jiti ./lib/prerender", "prerender": "pnpm build && jiti ./lib/prerender"
"test": "pnpm lint && pnpm build"
}, },
"devDependencies": { "devDependencies": {
"@unocss/reset": "0.64.0", "@unocss/reset": "0.64.1",
"beasties": "0.1.0", "beasties": "0.1.0",
"html-validate": "8.24.2", "html-validate": "8.25.1",
"htmlnano": "2.1.1", "htmlnano": "2.1.1",
"jiti": "2.4.0", "jiti": "2.4.0",
"knitwork": "1.1.0", "knitwork": "1.1.0",
"pathe": "1.1.2", "pathe": "1.1.2",
"prettier": "3.3.3", "prettier": "3.4.1",
"scule": "1.3.0", "scule": "1.3.0",
"svgo": "3.3.2",
"tinyexec": "0.3.1", "tinyexec": "0.3.1",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.10",
"unocss": "0.64.0", "unocss": "0.64.1",
"vite": "5.4.10" "vite": "6.0.0"
} }
} }

View File

@ -26,16 +26,15 @@
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@types/clear": "0.1.4", "@types/clear": "0.1.4",
"@types/estree": "1.0.6", "rollup": "4.27.4",
"rollup": "4.24.4",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"vue": "3.5.12" "vue": "3.5.13"
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@rollup/plugin-replace": "^6.0.1", "@rollup/plugin-replace": "^6.0.1",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.0.1", "@vitejs/plugin-vue-jsx": "^4.1.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"clear": "^0.1.0", "clear": "^0.1.0",
"consola": "^3.2.3", "consola": "^3.2.3",
@ -43,27 +42,23 @@
"defu": "^6.1.4", "defu": "^6.1.4",
"esbuild": "^0.24.0", "esbuild": "^0.24.0",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"externality": "^1.0.2", "externality": "^1.0.2",
"get-port-please": "^3.1.2", "get-port-please": "^3.1.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"jiti": "^2.4.0", "jiti": "^2.4.0",
"knitwork": "^1.1.0", "knitwork": "^1.1.0",
"magic-string": "^0.30.12", "magic-string": "^0.30.14",
"mlly": "^1.7.2", "mlly": "^1.7.3",
"ohash": "^1.1.4",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"perfect-debounce": "^1.0.0",
"pkg-types": "^1.2.1", "pkg-types": "^1.2.1",
"postcss": "^8.4.47", "postcss": "^8.4.49",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"std-env": "^3.8.0", "std-env": "^3.8.0",
"strip-literal": "^2.1.0",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unplugin": "^1.15.0", "unplugin": "^1.16.0",
"vite": "^5.4.10", "vite": "^6.0.0",
"vite-node": "^2.1.4", "vite-node": "^2.1.6",
"vite-plugin-checker": "^0.8.0", "vite-plugin-checker": "^0.8.0",
"vue-bundle-renderer": "^2.1.1" "vue-bundle-renderer": "^2.1.1"
}, },

View File

@ -7,16 +7,6 @@ export function isCSS (file: string) {
return IS_CSS_RE.test(file) return IS_CSS_RE.test(file)
} }
export function matchWithStringOrRegex (value: string, matcher: string | RegExp) {
if (typeof matcher === 'string') {
return value === matcher
} else if (matcher instanceof RegExp) {
return matcher.test(value)
}
return false
}
/** @since 3.9.0 */ /** @since 3.9.0 */
export function toArray<T> (value: T | T[]): T[] { export function toArray<T> (value: T | T[]): T[] {
return Array.isArray(value) ? value : [value] return Array.isArray(value) ? value : [value]

View File

@ -3,7 +3,7 @@ import { pathToFileURL } from 'node:url'
import { createApp, createError, defineEventHandler, defineLazyEventHandler, eventHandler, toNodeListener } from 'h3' import { createApp, createError, defineEventHandler, defineLazyEventHandler, eventHandler, toNodeListener } from 'h3'
import { ViteNodeServer } from 'vite-node/server' import { ViteNodeServer } from 'vite-node/server'
import { isAbsolute, join, normalize, resolve } from 'pathe' import { isAbsolute, join, normalize, resolve } from 'pathe'
import { addDevServerHandler } from '@nuxt/kit' // import { addDevServerHandler } from '@nuxt/kit'
import { isFileServingAllowed } from 'vite' import { isFileServingAllowed } from 'vite'
import type { ModuleNode, Plugin as VitePlugin } from 'vite' import type { ModuleNode, Plugin as VitePlugin } from 'vite'
import { getQuery } from 'ufo' import { getQuery } from 'ufo'
@ -16,7 +16,7 @@ import { createIsExternal } from './utils/external'
import { transpile } from './utils/transpile' import { transpile } from './utils/transpile'
// TODO: Remove this in favor of registerViteNodeMiddleware // TODO: Remove this in favor of registerViteNodeMiddleware
// after Nitropack or h3 fixed for adding middlewares after setup // after Nitropack or h3 allows adding middleware after setup
export function viteNodePlugin (ctx: ViteBuildContext): VitePlugin { export function viteNodePlugin (ctx: ViteBuildContext): VitePlugin {
// Store the invalidates for the next rendering // Store the invalidates for the next rendering
const invalidates = new Set<string>() const invalidates = new Set<string>()
@ -45,11 +45,11 @@ export function viteNodePlugin (ctx: ViteBuildContext): VitePlugin {
markInvalidate(mod) markInvalidate(mod)
} }
} }
for (const plugin of ctx.nuxt.options.plugins) {
markInvalidates(server.moduleGraph.getModulesByFile(typeof plugin === 'string' ? plugin : plugin.src)) if (ctx.nuxt.apps.default) {
} for (const template of ctx.nuxt.apps.default.templates) {
for (const template of ctx.nuxt.options.build.templates) { markInvalidates(server.moduleGraph.getModulesByFile(template.dst!))
markInvalidates(server.moduleGraph.getModulesByFile(template.dst!)) }
} }
} }
@ -71,12 +71,13 @@ export function viteNodePlugin (ctx: ViteBuildContext): VitePlugin {
} }
} }
export function registerViteNodeMiddleware (ctx: ViteBuildContext) { // TODO: Use this when Nitropack or h3 allows adding middleware after setup
addDevServerHandler({ // export function registerViteNodeMiddleware (ctx: ViteBuildContext) {
route: '/__nuxt_vite_node__/', // addDevServerHandler({
handler: createViteNodeApp(ctx).handler, // route: '/__nuxt_vite_node__/',
}) // handler: createViteNodeApp(ctx).handler,
} // })
// }
function getManifest (ctx: ViteBuildContext) { function getManifest (ctx: ViteBuildContext) {
const css = new Set<string>() const css = new Set<string>()

View File

@ -14,7 +14,6 @@ import { buildClient } from './client'
import { buildServer } from './server' import { buildServer } from './server'
import { warmupViteServer } from './utils/warmup' import { warmupViteServer } from './utils/warmup'
import { resolveCSSOptions } from './css' import { resolveCSSOptions } from './css'
import { composableKeysPlugin } from './plugins/composable-keys'
import { logLevelMap } from './utils/logger' import { logLevelMap } from './utils/logger'
import { ssrStylesPlugin } from './plugins/ssr-styles' import { ssrStylesPlugin } from './plugins/ssr-styles'
import { VitePublicDirsPlugin } from './plugins/public-dirs' import { VitePublicDirsPlugin } from './plugins/public-dirs'
@ -99,11 +98,6 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
sourcemap: !!nuxt.options.sourcemap.server, sourcemap: !!nuxt.options.sourcemap.server,
baseURL: nuxt.options.app.baseURL, baseURL: nuxt.options.app.baseURL,
}), }),
composableKeysPlugin.vite({
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables,
}),
replace({ preventAssignment: true, ...globalThisReplacements }), replace({ preventAssignment: true, ...globalThisReplacements }),
], ],
server: { server: {

View File

@ -38,23 +38,20 @@
"defu": "^6.1.4", "defu": "^6.1.4",
"esbuild-loader": "^4.2.2", "esbuild-loader": "^4.2.2",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.2", "fork-ts-checker-webpack-plugin": "^9.0.2",
"globby": "^14.0.2", "globby": "^14.0.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hash-sum": "^2.0.0",
"jiti": "^2.4.0", "jiti": "^2.4.0",
"knitwork": "^1.1.0", "knitwork": "^1.1.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"magic-string": "^0.30.12", "magic-string": "^0.30.14",
"memfs": "^4.14.0", "memfs": "^4.14.0",
"mini-css-extract-plugin": "^2.9.2", "mini-css-extract-plugin": "^2.9.2",
"mlly": "^1.7.2",
"ohash": "^1.1.4", "ohash": "^1.1.4",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"pify": "^6.1.0", "pify": "^6.1.0",
"postcss": "^8.4.47", "postcss": "^8.4.49",
"postcss-import": "^16.1.0", "postcss-import": "^16.1.0",
"postcss-import-resolver": "^2.0.0", "postcss-import-resolver": "^2.0.0",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
@ -64,7 +61,7 @@
"time-fix-plugin": "^2.0.7", "time-fix-plugin": "^2.0.7",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unplugin": "^1.15.0", "unplugin": "^1.16.0",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-bundle-renderer": "^2.1.1", "vue-bundle-renderer": "^2.1.1",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
@ -76,15 +73,14 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@rspack/core": "1.1.0", "@rspack/core": "1.1.4",
"@types/hash-sum": "1.0.2",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.12",
"@types/pify": "5.0.4", "@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.24.4", "rollup": "4.27.4",
"unbuild": "3.0.0-rc.11", "unbuild": "3.0.0-rc.11",
"vue": "3.5.12" "vue": "3.5.13"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.3.4" "vue": "^3.3.4"

View File

@ -11,7 +11,6 @@ import { joinURL } from 'ufo'
import { logger, useNitro, useNuxt } from '@nuxt/kit' import { logger, useNitro, useNuxt } from '@nuxt/kit'
import type { InputPluginOption } from 'rollup' import type { InputPluginOption } from 'rollup'
import { composableKeysPlugin } from '../../vite/src/plugins/composable-keys'
import { DynamicBasePlugin } from './plugins/dynamic-base' import { DynamicBasePlugin } from './plugins/dynamic-base'
import { ChunkErrorPlugin } from './plugins/chunk' import { ChunkErrorPlugin } from './plugins/chunk'
import { createMFS } from './utils/mfs' import { createMFS } from './utils/mfs'
@ -67,11 +66,6 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
if (config.name === 'client' && nuxt.options.experimental.emitRouteChunkError && nuxt.options.builder !== '@nuxt/rspack-builder') { if (config.name === 'client' && nuxt.options.experimental.emitRouteChunkError && nuxt.options.builder !== '@nuxt/rspack-builder') {
config.plugins!.push(new ChunkErrorPlugin()) config.plugins!.push(new ChunkErrorPlugin())
} }
config.plugins!.push(composableKeysPlugin.webpack({
sourcemap: !!nuxt.options.sourcemap[config.name as 'client' | 'server'],
rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables,
}))
} }
await nuxt.callHook(`${builder}:configResolved`, webpackConfigs) await nuxt.callHook(`${builder}:configResolved`, webpackConfigs)

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@
"ignoreDeps": [ "ignoreDeps": [
"nitro", "nitro",
"h3", "h3",
"typescript",
"nuxt", "nuxt",
"nuxt3", "nuxt3",
"@nuxt/kit" "@nuxt/kit"

View File

@ -40,7 +40,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`) expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir) const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1394k"`) expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1396k"`)
const packages = modules.files const packages = modules.files
.filter(m => m.endsWith('package.json')) .filter(m => m.endsWith('package.json'))
@ -78,7 +78,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server') const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"557k"`) expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"559k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir) const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"94.4k"`) expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"94.4k"`)

View File

@ -11,6 +11,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vue/devtools-api": "latest", "@vue/devtools-api": "latest",
"defu": "latest",
"ufo": "latest", "ufo": "latest",
"unplugin": "latest", "unplugin": "latest",
"vue": "latest" "vue": "latest"