diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index ad059c3630..7de0a3890f 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -1,6 +1,6 @@
name: "\U0001F41E Bug report"
description: Create a report to help us improve Nuxt
-labels: ["pending triage", "3.x"]
+labels: ["pending triage"]
body:
- type: markdown
attributes:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index ff8f4c2377..3527bf128b 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,11 +1,8 @@
blank_issues_enabled: true
contact_links:
- - name: π Nuxt 3 Documentation
+ - name: π Nuxt Documentation
url: https://nuxt.com/docs
- about: Check the documentation for usage of Nuxt 3
- - name: π Nuxt 2 Documentation
- url: https://v2.nuxt.com
- about: Check the documentation for usage of Nuxt 2
+ about: Check the documentation for usage of Nuxt
- name: π¬ Discussions
url: https://github.com/nuxt/nuxt/discussions
about: Use discussions if you have another issue, an idea for improvement or for asking questions.
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index a5614e9304..b155f19563 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -1,6 +1,6 @@
name: "π Feature request"
description: Suggest a feature that will improve Nuxt
-labels: ["pending triage", "3.x"]
+labels: ["pending triage"]
body:
- type: markdown
attributes:
diff --git a/.github/ISSUE_TEMPLATE/z-bug-report-2.yml b/.github/ISSUE_TEMPLATE/z-bug-report-2.yml
deleted file mode 100644
index d4124367b2..0000000000
--- a/.github/ISSUE_TEMPLATE/z-bug-report-2.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: "\U0001F41E Bug report (Nuxt 2)"
-description: Create a report to help us improve Nuxt
-labels: ["pending triage", "2.x"]
-body:
- - type: markdown
- attributes:
- value: |
- Please carefully read the contribution docs before creating a bug report
- π https://nuxt.com/docs/community/reporting-bugs
-
- Please use a template below to create a minimal reproduction
- π https://stackblitz.com/github/nuxt/starter/tree/v2
- π https://codesandbox.io/s/github/nuxt/starter/v2
- - type: textarea
- id: bug-env
- attributes:
- label: Environment
- description: You can use `npx envinfo --system --npmPackages '{nuxt,@nuxt/*}' --binaries --browsers` to fill this section
- placeholder: Environment
- validations:
- required: true
- - type: textarea
- id: reproduction
- attributes:
- label: Reproduction
- description: Please provide a link to a repo that can reproduce the problem you ran into. A [**minimal reproduction**](https://nuxt.com/docs/community/reporting-bugs#create-a-minimal-reproduction) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided we might close it.
- placeholder: Reproduction
- validations:
- required: true
- - type: textarea
- id: bug-description
- attributes:
- label: Describe the bug
- description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
- placeholder: Bug description
- validations:
- required: true
- - type: textarea
- id: additonal
- attributes:
- label: Additional context
- description: If applicable, add any other context about the problem here
- - type: textarea
- id: logs
- attributes:
- label: Logs
- description: |
- Optional if provided reproduction. Please try not to insert an image but copy paste the log text.
- render: shell-script
diff --git a/.github/workflows/autofix-docs.yml b/.github/workflows/autofix-docs.yml
index 3988622373..9165048727 100644
--- a/.github/workflows/autofix-docs.yml
+++ b/.github/workflows/autofix-docs.yml
@@ -27,7 +27,10 @@ jobs:
- name: Install dependencies
run: pnpm install
+ - name: Build (stub)
+ run: pnpm dev:prepare
+
- name: Lint (docs)
run: pnpm lint:docs:fix
- - uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a
+ - uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944
diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
index ef817dd093..d82ef9100d 100644
--- a/.github/workflows/autofix.yml
+++ b/.github/workflows/autofix.yml
@@ -52,4 +52,4 @@ jobs:
- name: Lint (code)
run: pnpm lint:fix
- - uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a
+ - uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index 50759cfff1..a3ce6dbb55 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -5,7 +5,6 @@ on:
branches:
- main
- 3.x
- - 2.x
permissions:
pull-requests: write
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 818fd6e27d..89d0c7aecd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -57,7 +57,7 @@ jobs:
run: pnpm build
- name: Cache dist
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
retention-days: 3
name: dist
@@ -85,19 +85,19 @@ jobs:
run: pnpm install
- name: Initialize CodeQL
- uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/init@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
with:
languages: javascript
queries: +security-and-quality
- name: Restore dist cache
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dist
path: packages
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/analyze@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
with:
category: "/language:javascript"
@@ -124,7 +124,7 @@ jobs:
run: pnpm install
- name: Restore dist cache
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dist
path: packages
@@ -233,7 +233,7 @@ jobs:
run: pnpm playwright-core install chromium
- name: Restore dist cache
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dist
path: packages
@@ -254,6 +254,8 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
build-release:
+ concurrency:
+ group: release
permissions:
id-token: write
if: |
@@ -283,7 +285,7 @@ jobs:
run: pnpm install
- name: Restore dist cache
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dist
path: packages
@@ -295,6 +297,8 @@ jobs:
NPM_CONFIG_PROVENANCE: true
release-pr:
+ concurrency:
+ group: release
permissions:
id-token: write
pull-requests: write
@@ -322,7 +326,7 @@ jobs:
run: pnpm install
- name: Restore dist cache
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dist
path: packages
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index b6ce923d60..2f948ae911 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -31,6 +31,9 @@ jobs:
- name: Install dependencies
run: pnpm install
+ - name: Build (stub)
+ run: pnpm dev:prepare
+
- name: Lint (docs)
run: pnpm lint:docs
diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml
index f652d7684e..3509c8075b 100644
--- a/.github/workflows/release-pr.yml
+++ b/.github/workflows/release-pr.yml
@@ -14,6 +14,8 @@ permissions:
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
@@ -44,7 +46,7 @@ jobs:
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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d43ba94276..7d3d86ce06 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,8 @@ permissions: {}
jobs:
release:
if: github.repository == 'nuxt/nuxt' && (startsWith(github.event.head_commit.message, 'v3.') || startsWith(github.event.head_commit.message, 'v4.'))
+ concurrency:
+ group: release
permissions:
id-token: write
runs-on: ubuntu-latest
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index fb0b7dc233..79ae716526 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -59,7 +59,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
if: github.repository == 'nuxt/nuxt' && success()
with:
name: SARIF file
@@ -68,7 +68,7 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
if: github.repository == 'nuxt/nuxt' && success()
with:
sarif_file: results.sarif
diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml
index 3bf40386bf..927a2b41a7 100644
--- a/.github/workflows/semantic-pull-requests.yml
+++ b/.github/workflows/semantic-pull-requests.yml
@@ -20,7 +20,7 @@ jobs:
name: Semantic pull request
steps:
- name: Validate PR title
- uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2
+ uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
with:
scopes: |
kit
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000000..fc78d099a7
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @danielroe
diff --git a/README.md b/README.md
index a9df118209..c3eaac4a39 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ It provides a number of features that make it easy to build fast, SEO-friendly,
- π [Local Development](#local-development)
- β°οΈ [Nuxt 2](#nuxt-2)
- π [Professional Support](#professional-support)
-- π [Follow us](#follow-us)
+- π [Follow Us](#follow-us)
- βοΈ [License](#license)
---
@@ -101,18 +101,12 @@ Here are a few ways you can get involved:
Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/docs/community/framework-contribution#setup) to contribute to the framework and documentation.
-## β°οΈ Nuxt 2
-
-You can find the code for Nuxt 2 on the [`2.x` branch](https://github.com/nuxt/nuxt/tree/2.x) and the documentation at [v2.nuxt.com](https://v2.nuxt.com).
-
-If you expect to be using Nuxt 2 beyond the EOL (End of Life) date (June 30, 2024), and still need a maintained version that can satisfy security and browser compatibility requirements, make sure to check out [HeroDevsβ NES (Never-Ending Support) Nuxt 2](https://www.herodevs.com/support/nuxt-nes?utm_source=nuxt-github&utm_medium=nuxt-readme).
-
## π Professional Support
- Technical audit & consulting: [Nuxt Experts](https://nuxt.com/enterprise/support)
- Custom development & more: [Nuxt Agencies Partners](https://nuxt.com/enterprise/agencies)
-## π Follow us
+## π Follow Us
diff --git a/docs/1.getting-started/1.introduction.md b/docs/1.getting-started/1.introduction.md
index 47bfdb0bc4..d092c50579 100644
--- a/docs/1.getting-started/1.introduction.md
+++ b/docs/1.getting-started/1.introduction.md
@@ -76,7 +76,6 @@ Nuxt is composed of different [core packages](https://github.com/nuxt/nuxt/tree/
- Command line interface: [nuxi](https://github.com/nuxt/nuxt/tree/main/packages/nuxi)
- Server engine: [nitro](https://github.com/unjs/nitro)
- Development kit: [@nuxt/kit](https://github.com/nuxt/nuxt/tree/main/packages/kit)
-- Nuxt 2 Bridge: [@nuxt/bridge](https://github.com/nuxt/bridge)
We recommend reading each concept to have a full vision of Nuxt capabilities and the scope of each package.
diff --git a/docs/1.getting-started/10.deployment.md b/docs/1.getting-started/10.deployment.md
index 582a24d14a..9043f0035c 100644
--- a/docs/1.getting-started/10.deployment.md
+++ b/docs/1.getting-started/10.deployment.md
@@ -85,13 +85,13 @@ export default defineNuxtConfig({
## Hosting Providers
-Nuxt 3 can be deployed to several cloud providers with a minimal amount of configuration:
+Nuxt can be deployed to several cloud providers with a minimal amount of configuration:
:read-more{to="/deploy"}
## Presets
-In addition to Node.js servers and static hosting services, a Nuxt 3 project can be deployed with several well-tested presets and minimal amount of configuration.
+In addition to Node.js servers and static hosting services, a Nuxt project can be deployed with several well-tested presets and minimal amount of configuration.
You can explicitly set the desired preset in the [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file:
@@ -125,5 +125,5 @@ Accordingly, you should make sure that the following options are unchecked / dis
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.
+Their location on the Cloudflare dashboard sometimes changes so don't hesitate to look around.
::
diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md
index ab3f5a1dd8..ffeb075ab2 100644
--- a/docs/1.getting-started/12.upgrade.md
+++ b/docs/1.getting-started/12.upgrade.md
@@ -35,11 +35,17 @@ bunx nuxi upgrade
To use the latest Nuxt build and test features before their release, read about the [nightly release channel](/docs/guide/going-further/nightly-release-channel) guide.
+::alert{type="warning"}
+The nightly release channel `latest` tag is currently tracking the Nuxt v4 branch, meaning that it is particularly likely to have breaking changes right now - be careful!
+
+You can opt in to the 3.x branch nightly releases with `"nuxt": "npm:nuxt-nightly@3x"`.
+::
+
## Testing Nuxt 4
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
-Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12 or via the nightly release channel.
+Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12+.
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
@@ -47,7 +53,7 @@ Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking
### Opting in to Nuxt 4
-First, opt in to the nightly release channel [following these steps](/docs/guide/going-further/nightly-release-channel#opting-in).
+First, upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases).
Then you can set your `compatibilityVersion` to match Nuxt 4 behavior:
@@ -103,7 +109,8 @@ Nuxt now defaults to a new directory structure, with backwards compatibility (so
* the new Nuxt default `srcDir` is `app/` by default, and most things are resolved from there.
* `serverDir` now defaults to `/server` rather than `/server`
-* `modules` and `public` are resolved relative to `` by default
+* `layers/`, `modules/` and `public/` are resolved relative to `` by default
+* if using [Nuxt Content v2.13+](https://github.com/nuxt/content/pull/2649), `content/` is resolved relative to ``
* a new `dir.app` is added, which is the directory we look for `router.options.ts` and `spa-loading-template.html` - this defaults to `/`
@@ -125,6 +132,8 @@ app/
app.config.ts
app.vue
router.options.ts
+content/
+layers/
modules/
node_modules/
public/
@@ -150,7 +159,7 @@ nuxt.config.ts
1. Create a new directory called `app/`.
1. Move your `assets/`, `components/`, `composables/`, `layouts/`, `middleware/`, `pages/`, `plugins/` and `utils/` folders under it, as well as `app.vue`, `error.vue`, `app.config.ts`. If you have an `app/router-options.ts` or `app/spa-loading-template.html`, these paths remain the same.
-1. Make sure your `nuxt.config.ts`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
+1. Make sure your `nuxt.config.ts`, `content/`, `layers/`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
However, migration is _not required_. If you wish to keep your current folder structure, Nuxt should auto-detect it. (If it does not, please raise an issue.) The one exception is that if you _already_ have a custom `srcDir`. In this case, you should be aware that your `modules/`, `public/` and `server/` folders will be resolved from your `rootDir` rather than from your custom `srcDir`. You can override this by configuring `dir.modules`, `dir.public` and `serverDir` if you need to.
@@ -502,11 +511,11 @@ These options have been set to their current values for some time and we do not
* `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9)
-## Nuxt 2 vs Nuxt 3
+## Nuxt 2 vs Nuxt 3+
In the table below, there is a quick comparison between 3 versions of Nuxt:
-Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3
+Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3+
-------------------------|-----------------|------------------|---------
Vue | 2 | 2 | 3
Stability | π Stable | π Stable | π Stable
@@ -524,9 +533,9 @@ Vite | β οΈ Partial | π§ Partial | β
Nuxi CLI | β Old | β nuxi | β nuxi
Static sites | β | β | β
-## Nuxt 2 to Nuxt 3
+## Nuxt 2 to Nuxt 3+
-The migration guide provides a step-by-step comparison of Nuxt 2 features to Nuxt 3 features and guidance to adapt your current application.
+The migration guide provides a step-by-step comparison of Nuxt 2 features to Nuxt 3+ features and guidance to adapt your current application.
::read-more{to="/docs/migration/overview"}
Check out the **guide to migrating from Nuxt 2 to Nuxt 3**.
@@ -534,7 +543,7 @@ Check out the **guide to migrating from Nuxt 2 to Nuxt 3**.
## Nuxt 2 to Nuxt Bridge
-If you prefer to progressively migrate your Nuxt 2 application to Nuxt 3, you can use Nuxt Bridge. Nuxt Bridge is a compatibility layer that allows you to use Nuxt 3 features in Nuxt 2 with an opt-in mechanism.
+If you prefer to progressively migrate your Nuxt 2 application to Nuxt 3, you can use Nuxt Bridge. Nuxt Bridge is a compatibility layer that allows you to use Nuxt 3+ features in Nuxt 2 with an opt-in mechanism.
::read-more{to="/docs/bridge/overview"}
**Migrate from Nuxt 2 to Nuxt Bridge**
diff --git a/docs/1.getting-started/2.installation.md b/docs/1.getting-started/2.installation.md
index 79c9ca8ed7..32e8eb7f29 100644
--- a/docs/1.getting-started/2.installation.md
+++ b/docs/1.getting-started/2.installation.md
@@ -100,6 +100,6 @@ Well done! A browser window should automatically open for {
### Known issues
-- View transitions may not work as expected with nested pages/layouts/async components owing to this upstream Vue bug: . If you make use of this pattern, you may need to delay adopting this experimental feature or implement it yourself. Feedback is very welcome.
-
- 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 `` resolves, but in the interim you may want to consider carefully whether to adopt this feature if this describes you.
diff --git a/docs/1.getting-started/6.data-fetching.md b/docs/1.getting-started/6.data-fetching.md
index 61d02693ef..1b6fc9bb34 100644
--- a/docs/1.getting-started/6.data-fetching.md
+++ b/docs/1.getting-started/6.data-fetching.md
@@ -134,7 +134,7 @@ The `useAsyncData` composable is a great way to wrap and wait for multiple `$fet
```vue
-
+
Loading ...
@@ -204,7 +203,7 @@ You can alternatively use [`useLazyFetch`](/docs/api/composables/use-lazy-fetch)
```vue twoslash
```
@@ -227,7 +226,7 @@ Combined with the `lazy` option, this can be useful for data that is not needed
const articles = await useFetch('/api/article')
/* This call will only be performed on the client */
-const { pending, data: comments } = useFetch('/api/comments', {
+const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false
})
@@ -355,7 +354,7 @@ Sometimes you may need to compute an URL from reactive values, and refresh the d
@@ -406,7 +407,7 @@ With that, you will need both the `status` to handle the fetch lifecycle, and `e
```vue
@@ -416,7 +417,7 @@ const { data, error, execute, pending, status } = await useLazyFetch('/api/comme
-
+
Loading comments...
@@ -494,7 +495,7 @@ onMounted(() => console.log(document.cookie))
## Options API support
-Nuxt 3 provides a way to perform `asyncData` fetching within the Options API. You must wrap your component definition within `defineNuxtComponent` for this to work.
+Nuxt provides a way to perform `asyncData` fetching within the Options API. You must wrap your component definition within `defineNuxtComponent` for this to work.
```vue
```
@@ -105,6 +105,11 @@ Nuxt directly auto-imports files created in defined directories:
:link-example{to="/docs/examples/features/auto-imports"}
+::warning
+**Auto-imported `ref` and `computed` won't be unwrapped in a component ``.** :br
+This is due to how Vue works with refs that aren't top-level to the template. You can read more about it [in the Vue documentation](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#caveat-when-unwrapping-in-templates).
+::
+
### Explicit Imports
Nuxt exposes every auto-import with the `#imports` alias that can be used to make the import explicit if needed:
diff --git a/docs/2.guide/1.concepts/2.vuejs-development.md b/docs/2.guide/1.concepts/2.vuejs-development.md
index 2725ffea36..ffabdc6a49 100644
--- a/docs/2.guide/1.concepts/2.vuejs-development.md
+++ b/docs/2.guide/1.concepts/2.vuejs-development.md
@@ -39,7 +39,7 @@ Most applications need multiple pages and a way to navigate between them. This i
## Differences with Nuxt 2 / Vue 2
-Nuxt 3 is based on Vue 3. The new major Vue version introduces several changes that Nuxt takes advantage of:
+Nuxt 3+ is based on Vue 3. The new major Vue version introduces several changes that Nuxt takes advantage of:
- Better performance
- Composition API
@@ -89,15 +89,15 @@ const increment = () => count.value++
```
-The goal of Nuxt 3 is to provide a great developer experience around the Composition API.
+The goal of Nuxt is to provide a great developer experience around the Composition API.
-- Use auto-imported [Reactivity functions](https://vuejs.org/api/reactivity-core.html) from Vue and Nuxt 3 [built-in composables](/docs/api/composables/use-async-data).
+- Use auto-imported [Reactivity functions](https://vuejs.org/api/reactivity-core.html) from Vue and Nuxt [built-in composables](/docs/api/composables/use-async-data).
- Write your own auto-imported reusable functions in the [`composables/` directory](/docs/guide/directory-structure/composables).
### TypeScript Support
-Both Vue 3 and Nuxt 3 are written in TypeScript. A fully typed codebase prevents mistakes and documents APIs usage. This doesnβt mean that you have to write your application in TypeScript to take advantage of it. With Nuxt 3, you can opt-in by renaming your file from `.js` to `.ts` , or add `
@@ -30,14 +30,14 @@ If you're using a custom useFetch wrapper, do not await it in the composable, as
::
::note
-`data`, `pending`, `status` and `error` are Vue refs and they should be accessed with `.value` when used within the `
-
+
Loading ...
diff --git a/docs/3.api/2.composables/use-nuxt-app.md b/docs/3.api/2.composables/use-nuxt-app.md
index 860cb89c22..5ab9289638 100644
--- a/docs/3.api/2.composables/use-nuxt-app.md
+++ b/docs/3.api/2.composables/use-nuxt-app.md
@@ -8,7 +8,7 @@ links:
size: xs
---
-`useNuxtApp` is a built-in composable that provides a way to access shared runtime context of Nuxt, also known as the [Nuxt context](/docs/guide/going-further/nuxt-app#the-nuxt-context), which is available on both client and server side. It helps you access the Vue app instance, runtime hooks, runtime config variables and internal states, such as `ssrContext` and `payload`.
+`useNuxtApp` is a built-in composable that provides a way to access shared runtime context of Nuxt, also known as the [Nuxt context](/docs/guide/going-further/nuxt-app#the-nuxt-context), which is available on both client and server side (but not within Nitro routes). It helps you access the Vue app instance, runtime hooks, runtime config variables and internal states, such as `ssrContext` and `payload`.
```vue [app.vue]
diff --git a/docs/3.api/4.commands/upgrade.md b/docs/3.api/4.commands/upgrade.md
index 187e915635..23958ea72f 100644
--- a/docs/3.api/4.commands/upgrade.md
+++ b/docs/3.api/4.commands/upgrade.md
@@ -1,6 +1,6 @@
---
title: "nuxi upgrade"
-description: The upgrade command upgrades Nuxt 3 to the latest version.
+description: The upgrade command upgrades Nuxt to the latest version.
links:
- label: Source
icon: i-simple-icons-github
@@ -12,7 +12,7 @@ links:
npx nuxi upgrade [--force|-f]
```
-The `upgrade` command upgrades Nuxt 3 to the latest version.
+The `upgrade` command upgrades Nuxt to the latest version.
Option | Default | Description
-------------------------|-----------------|------------------
diff --git a/docs/3.api/5.kit/11.nitro.md b/docs/3.api/5.kit/11.nitro.md
index 65c0c97437..9ca5785754 100644
--- a/docs/3.api/5.kit/11.nitro.md
+++ b/docs/3.api/5.kit/11.nitro.md
@@ -8,7 +8,7 @@ links:
size: xs
---
-Nitro is an open source TypeScript framework to build ultra-fast web servers. Nuxt 3 (and, optionally, Nuxt Bridge) uses Nitro as its server engine. You can use `useNitro` to access the Nitro instance, `addServerHandler` to add a server handler, `addDevServerHandler` to add a server handler to be used only in development mode, `addServerPlugin` to add a plugin to extend Nitro's runtime behavior, and `addPrerenderRoutes` to add routes to be prerendered by Nitro.
+Nitro is an open source TypeScript framework to build ultra-fast web servers. Nuxt uses Nitro as its server engine. You can use `useNitro` to access the Nitro instance, `addServerHandler` to add a server handler, `addDevServerHandler` to add a server handler to be used only in development mode, `addServerPlugin` to add a plugin to extend Nitro's runtime behavior, and `addPrerenderRoutes` to add routes to be prerendered by Nitro.
## `addServerHandler`
diff --git a/docs/3.api/5.kit/7.pages.md b/docs/3.api/5.kit/7.pages.md
index 83b5dc15d3..9e6ffab3bc 100644
--- a/docs/3.api/5.kit/7.pages.md
+++ b/docs/3.api/5.kit/7.pages.md
@@ -10,7 +10,7 @@ links:
## `extendPages`
-In Nuxt 3, routes are automatically generated based on the structure of the files in the `pages` directory. However, there may be scenarios where you'd want to customize these routes. For instance, you might need to add a route for a dynamic page not generated by Nuxt, remove an existing route, or modify the configuration of a route. For such customizations, Nuxt 3 offers the `extendPages` feature, which allows you to extend and alter the pages configuration.
+In Nuxt 3, routes are automatically generated based on the structure of the files in the `pages` directory. However, there may be scenarios where you'd want to customize these routes. For instance, you might need to add a route for a dynamic page not generated by Nuxt, remove an existing route, or modify the configuration of a route. For such customizations, Nuxt offers the `extendPages` feature, which allows you to extend and alter the pages configuration.
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/extend-and-alter-nuxt-pages?friend=nuxt" target="_blank"}
Watch Vue School video about extendPages.
diff --git a/docs/3.api/5.kit/8.layout.md b/docs/3.api/5.kit/8.layout.md
index dd6de0f6aa..9bf3ef78d2 100644
--- a/docs/3.api/5.kit/8.layout.md
+++ b/docs/3.api/5.kit/8.layout.md
@@ -15,7 +15,7 @@ Layouts is used to be a wrapper around your pages. It can be used to wrap your p
Register template as layout and add it to the layouts.
::note
-In Nuxt 2 `error` layout can also be registered using this utility. In Nuxt 3 `error` layout [replaced](/docs/getting-started/error-handling#rendering-an-error-page) with `error.vue` page in project root.
+In Nuxt 2 `error` layout can also be registered using this utility. In Nuxt 3+ `error` layout [replaced](/docs/getting-started/error-handling#rendering-an-error-page) with `error.vue` page in project root.
::
### Type
diff --git a/docs/3.api/6.advanced/1.hooks.md b/docs/3.api/6.advanced/1.hooks.md
index 30204db7e4..0c0436f1a3 100644
--- a/docs/3.api/6.advanced/1.hooks.md
+++ b/docs/3.api/6.advanced/1.hooks.md
@@ -30,7 +30,7 @@ Hook | Arguments | Environment | Description
`page:loading:end` | - | Client | Called after `page:finish`
`page:transition:finish`| `pageComponent?` | Client | After page transition [onAfterLeave](https://vuejs.org/guide/built-ins/transition.html#javascript-hooks) event.
`dev:ssr-logs` | `logs` | Client | Called with an array of server-side logs that have been passed to the client (if `features.devLogs` is enabled).
-`page:view-transition:start` | `transition` | Client | Called after `document.startViewTransition` is called when [experimental viewTransition support is enabled](https://nuxt.com/docs/getting-started/transitions#view-transitions-api-experimental).
+`page:view-transition:start` | `transition` | Client | Called after `document.startViewTransition` is called when [experimental viewTransition support is enabled](/docs/getting-started/transitions#view-transitions-api-experimental).
## Nuxt Hooks (build time)
diff --git a/docs/5.community/2.getting-help.md b/docs/5.community/2.getting-help.md
index 70ab6946fc..8f4e038906 100644
--- a/docs/5.community/2.getting-help.md
+++ b/docs/5.community/2.getting-help.md
@@ -30,4 +30,4 @@ And finally, just ask the question! There's no need to [ask permission to ask a
Something isn't working the way that the docs say that it should. You're not sure if it's a bug. You've searched through the [open issues](https://github.com/nuxt/nuxt/issues) and [discussions](https://github.com/nuxt/nuxt/discussions) but you can't find anything. (if there is a closed issue, please create a new one)
-We recommend taking a look at [how to report bugs](/docs/community/reporting-bugs). Nuxt 3 is still in active development, and every issue helps make it better.
+We recommend taking a look at [how to report bugs](/docs/community/reporting-bugs). Nuxt is still in active development, and every issue helps make it better.
diff --git a/docs/5.community/3.reporting-bugs.md b/docs/5.community/3.reporting-bugs.md
index 815617c2e8..c82a6d8ba6 100644
--- a/docs/5.community/3.reporting-bugs.md
+++ b/docs/5.community/3.reporting-bugs.md
@@ -22,31 +22,25 @@ Search through the [open issues](https://github.com/nuxt/nuxt/issues) and [discu
It's important to be able to reproduce the bug reliably - in a minimal way and apart from the rest of your project. This narrows down what could be causing the issue and makes it possible for someone not only to find the cause, but also to test a potential solution.
-Start with the Nuxt 3 or Nuxt Bridge sandbox and add the **minimum** amount of code necessary to reproduce the bug you're experiencing.
+Start with the Nuxt sandbox and add the **minimum** amount of code necessary to reproduce the bug you're experiencing.
::note
-If your issue concerns Vue 3 or Vite, please try to reproduce it first with the Vue 3 SSR starter.
+If your issue concerns Vue or Vite, please try to reproduce it first with the Vue SSR starter.
::
-**Nuxt 3**:
+**Nuxt**:
::card-group
- :card{title="Nuxt 3 on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v3" target="_blank"}
- :card{title="Nuxt 3 on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v3" target="_blank"}
+ :card{title="Nuxt on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v3" target="_blank"}
+ :card{title="Nuxt on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v3" target="_blank"}
::
-**Nuxt Bridge**:
+**Vue**:
::card-group
- :card{title="Nuxt Bridge on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://codesandbox.io/s/github/nuxt/starter/v2-bridge-codesandbox" target="_blank"}
-::
-
-**Vue 3**:
-
-::card-group
- :card{title="Vue 3 SSR on StackBlitz" icon="i-simple-icons-stackblitz" to="https://stackblitz.com/github/nuxt-contrib/vue3-ssr-starter/tree/main?terminal=dev" target="_blank"}
- :card{title="Vue 3 SSR on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://codesandbox.io/s/github/nuxt-contrib/vue3-ssr-starter/main" target="_blank"}
- :card{title="Vue 3 SSR Template on GitHub" icon="i-simple-icons-github" to="https://github.com/nuxt-contrib/vue3-ssr-starter/generate" target="_blank"}
+ :card{title="Vue SSR on StackBlitz" icon="i-simple-icons-stackblitz" to="https://stackblitz.com/github/nuxt-contrib/vue3-ssr-starter/tree/main?terminal=dev" target="_blank"}
+ :card{title="Vue SSR on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://codesandbox.io/s/github/nuxt-contrib/vue3-ssr-starter/main" target="_blank"}
+ :card{title="Vue SSR Template on GitHub" icon="i-simple-icons-github" to="https://github.com/nuxt-contrib/vue3-ssr-starter/generate" target="_blank"}
::
Once you've reproduced the issue, remove as much code from your reproduction as you can (while still recreating the bug). The time spent making the reproduction as minimal as possible will make a huge difference to whoever sets out to fix the issue.
diff --git a/docs/5.community/6.roadmap.md b/docs/5.community/6.roadmap.md
index f485be077d..b0209259b7 100644
--- a/docs/5.community/6.roadmap.md
+++ b/docs/5.community/6.roadmap.md
@@ -32,7 +32,7 @@ Milestone | Expected date | Notes
-------------|---------------|------------------------------------------------------------------------|-----------------------
SEO & PWA | 2024 | [nuxt/nuxt#18395](https://github.com/nuxt/nuxt/discussions/18395) | Migrating from [nuxt-community/pwa-module](https://github.com/nuxt-community/pwa-module) for built-in SEO utils and service worker support
Assets | 2024 | [nuxt/nuxt#22012](https://github.com/nuxt/nuxt/discussions/22012) | Allow developers and modules to handle loading third-party assets.
-Translations | - | [nuxt/translations#4](https://github.com/nuxt/translations/discussions/4) ([request access](https://github.com/nuxt/nuxt/discussions/16054)) | A collaborative project for a stable translation process for Nuxt 3 docs. Currently pending for ideas and documentation tooling support (content v2 with remote sources).
+Translations | - | [nuxt/translations#4](https://github.com/nuxt/translations/discussions/4) ([request access](https://github.com/nuxt/nuxt/discussions/16054)) | A collaborative project for a stable translation process for Nuxt docs. Currently pending for ideas and documentation tooling support (content v2 with remote sources).
## Core Modules Roadmap
@@ -42,7 +42,7 @@ Module | Status | Nuxt Support | Repos
------------------------------------|---------------------|--------------|------------|-------------------
[Scripts](https://scripts.nuxt.com) | Public Preview | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management.
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 | Nuxt 3 support is planned after session support.
+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.
## Release Cycle
@@ -59,13 +59,13 @@ The current active version of [Nuxt](https://nuxt.com) is **v3** which is availa
Nuxt 2 is in maintenance mode and is available on npm with the `2x` tag. It will reach End of Life (EOL) on June 30, 2024.
-Each active version has its own nightly releases which are generated automatically. For more about enabling the Nuxt 3 nightly release channel, see [the nightly release channel docs](/docs/guide/going-further/nightly-release-channel).
+Each active version has its own nightly releases which are generated automatically. For more about enabling the Nuxt nightly release channel, see [the nightly release channel docs](/docs/guide/going-further/nightly-release-channel).
Release | | Initial release | End Of Life | Docs
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
-**4.x** (scheduled) | | 2024 Q2 | |
+**4.x** (scheduled) | | 2024 Q3 | |
**3.x** (stable) | | 2022-11-16 | TBA | [nuxt.com](/docs)
-**2.x** (maintenance) | | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
+**2.x** (unsupported) | | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
**1.x** (unsupported) | | 2018-01-08 | 2019-09-21 |
### Support Status
diff --git a/docs/6.bridge/4.plugins-and-middleware.md b/docs/6.bridge/4.plugins-and-middleware.md
index 2250b8bad5..de59c68649 100644
--- a/docs/6.bridge/4.plugins-and-middleware.md
+++ b/docs/6.bridge/4.plugins-and-middleware.md
@@ -44,7 +44,7 @@ Use of `defineNuxtRouteMiddleware` is not supported outside of the middleware di
## definePageMeta
-You can also use [`definePageMeta`](https://nuxt.com/docs/api/utils/define-page-meta) in Nuxt Bridge.
+You can also use [`definePageMeta`](/docs/api/utils/define-page-meta) in Nuxt Bridge.
You can be enabled with the `macros.pageMeta` option in your configuration file
diff --git a/examples/README.md b/examples/README.md
index ba22dfb66e..da61500f7c 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,4 +1,4 @@
-# Nuxt 3 Examples
+# Nuxt Examples
- π See examples in your browser at https://nuxt.com/docs/examples
- π View on GitHub at https://github.com/nuxt/examples
diff --git a/package.json b/package.json
index fb0b6086be..ddb5f2795c 100644
--- a/package.json
+++ b/package.json
@@ -34,22 +34,25 @@
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs"
},
"resolutions": {
- "nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
- "typescript": "5.5.2",
- "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/ui-templates": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
+ "c12": "2.0.0-beta.1",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
+ "jiti": "2.0.0-beta.3",
"magic-string": "^0.30.10",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxt": "workspace:*",
"rollup": "^4.18.0",
- "vite": "5.3.1",
- "vue": "3.4.30"
+ "typescript": "5.5.3",
+ "unbuild": "3.0.0-rc.6",
+ "vite": "5.3.3",
+ "vue": "3.4.31"
},
"devDependencies": {
- "@eslint/js": "9.5.0",
+ "@eslint/js": "9.6.0",
"@nuxt/eslint-config": "0.3.13",
"@nuxt/kit": "workspace:*",
"@nuxt/test-utils": "3.13.1",
@@ -58,41 +61,43 @@
"@types/eslint__js": "8.42.3",
"@types/node": "20.14.9",
"@types/semver": "7.5.8",
- "@unhead/schema": "1.9.14",
- "@vitejs/plugin-vue": "5.0.4",
+ "@unhead/schema": "1.9.15",
+ "@vitejs/plugin-vue": "5.0.5",
"@vitest/coverage-v8": "1.6.0",
"@vue/test-utils": "2.4.6",
+ "autoprefixer": "10.4.19",
"case-police": "0.6.1",
"changelogen": "0.5.5",
"consola": "3.2.3",
+ "cssnano": "7.0.4",
"devalue": "5.0.0",
- "eslint": "9.5.0",
+ "eslint": "9.6.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-perfectionist": "2.11.0",
"eslint-typegen": "0.2.4",
"execa": "9.3.0",
- "globby": "14.0.1",
+ "globby": "14.0.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"happy-dom": "14.12.3",
- "jiti": "1.21.6",
+ "jiti": "2.0.0-beta.3",
"markdownlint-cli": "0.41.0",
- "nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxi": "3.12.0",
"nuxt": "workspace:*",
- "nuxt-content-twoslash": "0.0.10",
+ "nuxt-content-twoslash": "0.1.0",
"ofetch": "1.3.4",
"pathe": "1.1.2",
- "playwright-core": "1.45.0",
- "rimraf": "5.0.7",
+ "playwright-core": "1.45.1",
+ "rimraf": "6.0.0",
"semver": "7.6.2",
"std-env": "3.7.0",
- "typescript": "5.5.2",
+ "typescript": "5.5.3",
"ufo": "1.5.3",
"vitest": "1.6.0",
"vitest-environment-nuxt": "1.0.0",
- "vue": "3.4.30",
+ "vue": "3.4.31",
"vue-router": "4.4.0",
- "vue-tsc": "2.0.22"
+ "vue-tsc": "2.0.26"
},
"packageManager": "pnpm@9.4.0",
"engines": {
diff --git a/packages/kit/index.d.ts b/packages/kit/index.d.ts
deleted file mode 100644
index 43b479fd69..0000000000
--- a/packages/kit/index.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable no-var */
-declare global {
- var __NUXT_PREPATHS__: string[] | string | undefined
- var __NUXT_PATHS__: string[] | string | undefined
-}
-
-export {}
diff --git a/packages/kit/package.json b/packages/kit/package.json
index deca72af86..f6bd5f8593 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -27,18 +27,18 @@
},
"dependencies": {
"@nuxt/schema": "workspace:*",
- "c12": "^1.11.1",
+ "c12": "^2.0.0-beta.1",
"consola": "^3.2.3",
"defu": "^6.1.4",
"destr": "^2.0.3",
- "globby": "^14.0.1",
+ "globby": "^14.0.2",
"hash-sum": "^2.0.0",
"ignore": "^5.3.1",
- "jiti": "^1.21.6",
+ "jiti": "^2.0.0-beta.3",
"klona": "^2.0.6",
"mlly": "^1.7.1",
"pathe": "^1.1.2",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.1.3",
"scule": "^1.3.0",
"semver": "^7.6.2",
"ufo": "^1.5.3",
@@ -49,9 +49,9 @@
"devDependencies": {
"@types/hash-sum": "1.0.2",
"@types/semver": "7.5.8",
- "nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
- "unbuild": "latest",
- "vite": "5.3.1",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
+ "unbuild": "3.0.0-rc.6",
+ "vite": "5.3.3",
"vitest": "1.6.0",
"webpack": "5.92.1"
},
diff --git a/packages/kit/src/index.ts b/packages/kit/src/index.ts
index 1f70f0a484..bde038e6fb 100644
--- a/packages/kit/src/index.ts
+++ b/packages/kit/src/index.ts
@@ -1,37 +1,36 @@
// Module
-export * from './module/define'
-export * from './module/install'
-export * from './module/compatibility'
+export { defineNuxtModule } from './module/define'
+export { getDirectory, installModule, loadNuxtModuleInstance, normalizeModuleTranspilePath } from './module/install'
+export { getNuxtModuleVersion, hasNuxtModule, hasNuxtModuleCompatibility } from './module/compatibility'
// Loader
-export * from './loader/config'
-export * from './loader/schema'
-export * from './loader/nuxt'
+export { loadNuxtConfig } from './loader/config'
+export type { LoadNuxtConfigOptions } from './loader/config'
+export { extendNuxtSchema } from './loader/schema'
+export { buildNuxt, loadNuxt } from './loader/nuxt'
+export type { LoadNuxtOptions } from './loader/nuxt'
// Utils
-export * from './imports'
+export { addImports, addImportsDir, addImportsSources } from './imports'
export { updateRuntimeConfig, useRuntimeConfig } from './runtime-config'
-export * from './build'
-export * from './compatibility'
-export * from './components'
-export * from './context'
+export { addBuildPlugin, addVitePlugin, addWebpackPlugin, extendViteConfig, extendWebpackConfig } from './build'
+export type { ExtendConfigOptions, ExtendViteConfigOptions, ExtendWebpackConfigOptions } from './build'
+export { assertNuxtCompatibility, checkNuxtCompatibility, getNuxtVersion, hasNuxtCompatibility, isNuxtMajorVersion, normalizeSemanticVersion, isNuxt2, isNuxt3 } from './compatibility'
+export { addComponent, addComponentsDir } from './components'
+export type { AddComponentOptions } from './components'
+export { nuxtCtx, tryUseNuxt, useNuxt } from './context'
export { isIgnored, resolveIgnorePatterns } from './ignore'
-export * from './layout'
-export * from './pages'
-export * from './plugin'
-export * from './resolve'
-export * from './nitro'
+export { addLayout } from './layout'
+export { addRouteMiddleware, extendPages, extendRouteRules } from './pages'
+export type { AddRouteMiddlewareOptions, ExtendRouteRulesOptions } from './pages'
+export { addPlugin, addPluginTemplate, normalizePlugin } from './plugin'
+export type { AddPluginOptions } from './plugin'
+export { createResolver, findPath, resolveAlias, resolveFiles, resolveNuxtModule, resolvePath } from './resolve'
+export type { ResolvePathOptions, Resolver } from './resolve'
+export { addServerHandler, addDevServerHandler, addServerPlugin, addPrerenderRoutes, useNitro, addServerImports, addServerImportsDir, addServerScanDir } from './nitro'
export { addTemplate, addTypeTemplate, normalizeTemplate, updateTemplates, writeTypes } from './template'
-export * from './logger'
+export { logger, useLogger } from './logger'
// Internal Utils
-// TODO
-export {
- resolveModule,
- requireModule,
- importModule,
- tryImportModule,
- tryRequireModule,
-} from './internal/cjs'
-export type { ResolveModuleOptions, RequireModuleOptions } from './internal/cjs'
-export { tryResolveModule } from './internal/esm'
+export { resolveModule, tryResolveModule, importModule, tryImportModule, requireModule, tryRequireModule } from './internal/esm'
+export type { ImportModuleOptions, ResolveModuleOptions } from './internal/esm'
diff --git a/packages/kit/src/internal/cjs.ts b/packages/kit/src/internal/cjs.ts
deleted file mode 100644
index b2c01e8c69..0000000000
--- a/packages/kit/src/internal/cjs.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { pathToFileURL } from 'node:url'
-import { normalize } from 'pathe'
-import { interopDefault } from 'mlly'
-import jiti from 'jiti'
-
-// TODO: use create-require for jest environment
-const _require = jiti(process.cwd(), { interopDefault: true, esmResolve: true })
-
-/** @deprecated Do not use CJS utils */
-export interface ResolveModuleOptions {
- paths?: string | string[]
-}
-
-/** @deprecated Do not use CJS utils */
-export interface RequireModuleOptions extends ResolveModuleOptions {
- // TODO: use create-require for jest environment
- // native?: boolean
- /** Clear the require cache (force fresh require) but only if not within `node_modules` */
- clearCache?: boolean
-
- /** Automatically de-default the result of requiring the module. */
- interopDefault?: boolean
-}
-
-/** @deprecated Do not use CJS utils */
-function isNodeModules (id: string) {
- // TODO: Follow symlinks
- return /[/\\]node_modules[/\\]/.test(id)
-}
-
-/** @deprecated Do not use CJS utils */
-function clearRequireCache (id: string) {
- if (isNodeModules(id)) {
- return
- }
-
- const entry = getRequireCacheItem(id)
-
- if (!entry) {
- delete _require.cache[id]
- return
- }
-
- if (entry.parent) {
- entry.parent.children = entry.parent.children.filter(e => e.id !== id)
- }
-
- for (const child of entry.children) {
- clearRequireCache(child.id)
- }
-
- delete _require.cache[id]
-}
-
-/** @deprecated Do not use CJS utils */
-function getRequireCacheItem (id: string) {
- try {
- return _require.cache[id]
- } catch (e) {
- // ignore issues accessing require.cache
- }
-}
-
-export function getNodeModulesPaths (paths?: string[] | string) {
- return ([] as Array).concat(
- global.__NUXT_PREPATHS__,
- paths || [],
- process.cwd(),
- global.__NUXT_PATHS__,
- ).filter(Boolean) as string[]
-}
-
-/** @deprecated Do not use CJS utils */
-export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
- return normalize(_require.resolve(id, {
- paths: getNodeModulesPaths(opts.paths),
- }))
-}
-
-/** @deprecated Do not use CJS utils */
-export function requireModule (id: string, opts: RequireModuleOptions = {}) {
- // Resolve id
- const resolvedPath = resolveModule(id, opts)
-
- // Clear require cache if necessary
- if (opts.clearCache && !isNodeModules(id)) {
- clearRequireCache(resolvedPath)
- }
-
- // Try to require
- const requiredModule = _require(resolvedPath)
-
- return requiredModule
-}
-
-/** @deprecated Do not use CJS utils */
-export function importModule (id: string, opts: RequireModuleOptions = {}) {
- const resolvedPath = resolveModule(id, opts)
- if (opts.interopDefault !== false) {
- return import(pathToFileURL(resolvedPath).href).then(interopDefault)
- }
- return import(pathToFileURL(resolvedPath).href)
-}
-
-/** @deprecated Do not use CJS utils */
-export function tryImportModule (id: string, opts: RequireModuleOptions = {}) {
- try {
- return importModule(id, opts).catch(() => undefined)
- } catch {
- // intentionally empty as this is a `try-` function
- }
-}
-
-/** @deprecated Do not use CJS utils */
-export function tryRequireModule (id: string, opts: RequireModuleOptions = {}) {
- try {
- return requireModule(id, opts)
- } catch {
- // intentionally empty as this is a `try-` function
- }
-}
diff --git a/packages/kit/src/internal/esm.ts b/packages/kit/src/internal/esm.ts
index 54e4524a73..36f6843404 100644
--- a/packages/kit/src/internal/esm.ts
+++ b/packages/kit/src/internal/esm.ts
@@ -1,5 +1,10 @@
import { pathToFileURL } from 'node:url'
-import { interopDefault, resolvePath } from 'mlly'
+import { interopDefault, resolvePath, resolvePathSync } from 'mlly'
+import { createJiti } from 'jiti'
+
+export interface ResolveModuleOptions {
+ paths?: string | string[]
+}
/**
* Resolve a module from a given root path using an algorithm patterned on
@@ -15,14 +20,52 @@ export async function tryResolveModule (id: string, url: string | string[] = imp
}
}
-export async function importModule (id: string, url: string | string[] = import.meta.url) {
- const resolvedPath = await resolvePath(id, { url })
- return import(pathToFileURL(resolvedPath).href).then(interopDefault)
+export function resolveModule (id: string, options?: ResolveModuleOptions) {
+ return resolvePathSync(id, { url: options?.paths ?? [import.meta.url] })
}
-export function tryImportModule (id: string, url = import.meta.url) {
+export interface ImportModuleOptions extends ResolveModuleOptions {
+ /** Automatically de-default the result of requiring the module. */
+ interopDefault?: boolean
+}
+
+export async function importModule (id: string, opts?: ImportModuleOptions) {
+ const resolvedPath = await resolveModule(id, opts)
+ return import(pathToFileURL(resolvedPath).href).then(r => opts?.interopDefault !== false ? interopDefault(r) : r) as Promise
+}
+
+export function tryImportModule (id: string, opts?: ImportModuleOptions) {
try {
- return importModule(id, url).catch(() => undefined)
+ return importModule(id, opts).catch(() => undefined)
+ } catch {
+ // intentionally empty as this is a `try-` function
+ }
+}
+
+const warnings = new Set()
+
+/**
+ * @deprecated Please use `importModule` instead.
+ */
+export function requireModule (id: string, opts?: ImportModuleOptions) {
+ if (!warnings.has(id)) {
+ // TODO: add more information on stack trace
+ console.warn('[@nuxt/kit] `requireModule` is deprecated. Please use `importModule` instead.')
+ warnings.add(id)
+ }
+ const resolvedPath = resolveModule(id, opts)
+ const jiti = createJiti(import.meta.url, {
+ interopDefault: opts?.interopDefault !== false,
+ })
+ return jiti(pathToFileURL(resolvedPath).href) as T
+}
+
+/**
+ * @deprecated Please use `tryImportModule` instead.
+ */
+export function tryRequireModule (id: string, opts?: ImportModuleOptions) {
+ try {
+ return requireModule(id, opts)
} catch {
// intentionally empty as this is a `try-` function
}
diff --git a/packages/kit/src/loader/nuxt.ts b/packages/kit/src/loader/nuxt.ts
index e34f8af341..0c4727ffab 100644
--- a/packages/kit/src/loader/nuxt.ts
+++ b/packages/kit/src/loader/nuxt.ts
@@ -31,7 +31,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise {
const rootDir = pathToFileURL(opts.cwd!).href
- const { loadNuxt } = await importModule((pkg as any)._name || pkg.name, rootDir)
+ const { loadNuxt } = await importModule((pkg as any)._name || pkg.name, { paths: rootDir })
const nuxt = await loadNuxt(opts)
return nuxt
}
@@ -39,13 +39,6 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise {
export async function buildNuxt (nuxt: Nuxt): Promise {
const rootDir = pathToFileURL(nuxt.options.rootDir).href
- // Nuxt 3
- if (nuxt.options._majorVersion === 3) {
- const { build } = await tryImportModule('nuxt-nightly', rootDir) || await tryImportModule('nuxt3', rootDir) || await importModule('nuxt', rootDir)
- return build(nuxt)
- }
-
- // Nuxt 2
- const { build } = await tryImportModule('nuxt-edge', rootDir) || await importModule('nuxt', rootDir)
+ const { build } = await tryImportModule('nuxt-nightly', { paths: rootDir }) || await importModule('nuxt', { paths: rootDir })
return build(nuxt)
}
diff --git a/packages/kit/src/module/define.ts b/packages/kit/src/module/define.ts
index e5a771cff5..2069539107 100644
--- a/packages/kit/src/module/define.ts
+++ b/packages/kit/src/module/define.ts
@@ -112,11 +112,10 @@ function _defineNuxtModule<
}
// Call setup
- const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
- const mark = performance.mark(key)
+ const start = performance.now()
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
- const perf = performance.measure(key, mark.name)
- const setupTime = Math.round((perf.duration * 100)) / 100
+ const perf = performance.now() - start
+ const setupTime = Math.round((perf * 100)) / 100
// Measure setup time
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
diff --git a/packages/kit/src/module/install.ts b/packages/kit/src/module/install.ts
index 7a0488258e..536b18ca86 100644
--- a/packages/kit/src/module/install.ts
+++ b/packages/kit/src/module/install.ts
@@ -2,10 +2,9 @@ import { existsSync, promises as fsp, lstatSync } from 'node:fs'
import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema'
import { dirname, isAbsolute, join, resolve } from 'pathe'
import { defu } from 'defu'
+import { createJiti } from 'jiti'
import { useNuxt } from '../context'
-import { requireModule } from '../internal/cjs'
-import { importModule } from '../internal/esm'
-import { resolveAlias, resolvePath } from '../resolve'
+import { resolveAlias } from '../resolve'
import { logger } from '../logger'
const NODE_MODULES_RE = /[/\\]node_modules[/\\]/
@@ -72,15 +71,20 @@ export const normalizeModuleTranspilePath = (p: string) => {
export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) {
let buildTimeModuleMeta: ModuleMeta = {}
+
+ const jiti = createJiti(nuxt.options.rootDir, {
+ interopDefault: true,
+ alias: nuxt.options.alias,
+ })
+
// Import if input is string
if (typeof nuxtModule === 'string') {
const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule]
let error: unknown
for (const path of paths) {
try {
- const src = await resolvePath(path, { fallbackToOriginal: true })
- // Prefer ESM resolution if possible
- nuxtModule = await importModule(src, nuxt.options.modulesDir).catch(() => null) ?? requireModule(src, { paths: nuxt.options.modulesDir })
+ const src = jiti.esmResolve(path)
+ nuxtModule = await jiti.import(src) as NuxtModule
// nuxt-module-builder generates a module.json with metadata including the version
const moduleMetadataPath = join(dirname(src), 'module.json')
@@ -94,7 +98,7 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
}
}
if (typeof nuxtModule !== 'function' && error) {
- logger.error(`Error while requiring module \`${nuxtModule}\`: ${error}`)
+ logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
throw error
}
}
diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts
index 77dc044f9b..8820a73094 100644
--- a/packages/kit/src/template.ts
+++ b/packages/kit/src/template.ts
@@ -11,7 +11,6 @@ import { readPackageJSON } from 'pkg-types'
import { tryResolveModule } from './internal/esm'
import { getDirectory } from './module/install'
import { tryUseNuxt, useNuxt } from './context'
-import { getNodeModulesPaths } from './internal/cjs'
import { resolveNuxtModule } from './resolve'
/**
@@ -191,6 +190,7 @@ export async function _generateTypes (nuxt: Nuxt) {
'ESNext',
'dom',
'dom.iterable',
+ 'webworker',
],
/* JSX support for Vue */
jsx: 'preserve',
@@ -265,7 +265,7 @@ export async function _generateTypes (nuxt: Nuxt) {
await Promise.all([...nuxt.options.modules, ...nuxt.options._modules].map(async (id) => {
if (typeof id !== 'string') { return }
- const pkg = await readPackageJSON(id, { url: getNodeModulesPaths(nuxt.options.modulesDir) }).catch(() => null)
+ const pkg = await readPackageJSON(id, { url: nuxt.options.modulesDir }).catch(() => null)
references.push(({ types: pkg?.name || id }))
}))
diff --git a/packages/nuxt/index.d.ts b/packages/nuxt/index.d.ts
index 2256348248..db90cd08a0 100644
--- a/packages/nuxt/index.d.ts
+++ b/packages/nuxt/index.d.ts
@@ -2,8 +2,6 @@
declare global {
var __NUXT_VERSION__: string
var __NUXT_ASYNC_CONTEXT__: boolean
- var __NUXT_PREPATHS__: string[] | string | undefined
- var __NUXT_PATHS__: string[] | string | undefined
interface Navigator {
connection?: {
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index 57401fa838..b8e5805a8b 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -60,42 +60,44 @@
},
"dependencies": {
"@nuxt/devalue": "^2.0.2",
- "@nuxt/devtools": "^1.3.6",
+ "@nuxt/devtools": "^1.3.9",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.5.4",
"@nuxt/vite-builder": "workspace:*",
- "@unhead/dom": "^1.9.14",
- "@unhead/ssr": "^1.9.14",
- "@unhead/vue": "^1.9.14",
- "@vue/shared": "^3.4.30",
- "acorn": "8.12.0",
- "c12": "^1.11.1",
+ "@unhead/dom": "^1.9.15",
+ "@unhead/ssr": "^1.9.15",
+ "@unhead/vue": "^1.9.15",
+ "@vue/shared": "^3.4.31",
+ "acorn": "8.12.1",
+ "c12": "^2.0.0-beta.1",
"chokidar": "^3.6.0",
+ "compatx": "^0.1.8",
+ "consola": "^3.2.3",
"cookie-es": "^1.1.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"devalue": "^5.0.0",
- "esbuild": "^0.21.5",
+ "esbuild": "^0.23.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
- "globby": "^14.0.1",
+ "globby": "^14.0.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hookable": "^5.5.3",
"ignore": "^5.3.1",
- "jiti": "^1.21.6",
+ "jiti": "^2.0.0-beta.3",
"klona": "^2.0.6",
"knitwork": "^1.1.0",
"magic-string": "^0.30.10",
"mlly": "^1.7.1",
- "nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxi": "^3.12.0",
- "nypm": "^0.3.8",
+ "nypm": "^0.3.9",
"ofetch": "^1.3.4",
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"perfect-debounce": "^1.0.0",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.1.3",
"radix3": "^1.1.2",
"scule": "^1.3.0",
"semver": "^7.6.2",
@@ -107,11 +109,11 @@
"unctx": "^2.3.1",
"unenv": "^1.9.0",
"unimport": "^3.7.2",
- "unplugin": "^1.10.1",
+ "unplugin": "^1.11.0",
"unplugin-vue-router": "^0.10.0",
"unstorage": "^1.10.2",
"untyped": "^1.4.2",
- "vue": "^3.4.30",
+ "vue": "^3.4.31",
"vue-bundle-renderer": "^2.1.0",
"vue-devtools-stub": "^0.1.0",
"vue-router": "^4.4.0"
@@ -121,10 +123,10 @@
"@nuxt/ui-templates": "1.3.4",
"@parcel/watcher": "2.4.1",
"@types/estree": "1.0.5",
- "@vitejs/plugin-vue": "5.0.4",
- "@vue/compiler-sfc": "3.4.30",
- "unbuild": "latest",
- "vite": "5.3.1",
+ "@vitejs/plugin-vue": "5.0.5",
+ "@vue/compiler-sfc": "3.4.31",
+ "unbuild": "3.0.0-rc.6",
+ "vite": "5.3.3",
"vitest": "1.6.0"
},
"peerDependencies": {
diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts
index 5215379e81..01ab2c18ae 100644
--- a/packages/nuxt/src/app/composables/asyncData.ts
+++ b/packages/nuxt/src/app/composables/asyncData.ts
@@ -310,7 +310,7 @@ export function useAsyncData<
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.`)
+ console.warn(`[nuxt] \`${options._functionName || 'useAsyncData'}\` must return a truthy value (for example, it should not be \`undefined\`, \`null\` or empty string) or the request may be duplicated on the client side.`)
}
nuxtApp.payload.data[key] = result
diff --git a/packages/nuxt/src/app/composables/preload.ts b/packages/nuxt/src/app/composables/preload.ts
index 62d75d7edd..c89e82cca1 100644
--- a/packages/nuxt/src/app/composables/preload.ts
+++ b/packages/nuxt/src/app/composables/preload.ts
@@ -23,6 +23,8 @@ export const preloadComponents = async (components: string | string[]) => {
* @since 3.0.0
*/
export const prefetchComponents = (components: string | string[]) => {
+ if (import.meta.server) { return }
+
// TODO
return preloadComponents(components)
}
diff --git a/packages/nuxt/src/app/index.ts b/packages/nuxt/src/app/index.ts
index 9f8fc9103e..363c72d1bf 100644
--- a/packages/nuxt/src/app/index.ts
+++ b/packages/nuxt/src/app/index.ts
@@ -1,13 +1,14 @@
-///
+export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt'
+export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt'
-export * from './nuxt'
+export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index'
+export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index'
-export * from './composables/index'
-
-export * from './components/index'
-export * from './config'
-export * from './compat/idle-callback'
-export * from './types'
+export { defineNuxtLink } from './components/index'
+export type { NuxtLinkOptions, NuxtLinkProps } from './components/index'
+export { _getAppConfig, updateAppConfig, useAppConfig } from './config'
+export { cancelIdleCallback, requestIdleCallback } from './compat/idle-callback'
+export type { NuxtAppLiterals, NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext, PageMeta } from './types'
export const isVue2 = false
export const isVue3 = true
diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts
index dde3f71aac..fd8e33b3e6 100644
--- a/packages/nuxt/src/app/nuxt.ts
+++ b/packages/nuxt/src/app/nuxt.ts
@@ -121,6 +121,9 @@ interface _NuxtApp {
/** @internal */
_asyncData: Record
+ /**
+ * @deprecated This may be removed in a future major version.
+ */
pending: Ref
error: Ref
status: Ref
diff --git a/packages/nuxt/src/app/plugins/preload.server.ts b/packages/nuxt/src/app/plugins/preload.server.ts
index 18c8931f08..44f17795dc 100644
--- a/packages/nuxt/src/app/plugins/preload.server.ts
+++ b/packages/nuxt/src/app/plugins/preload.server.ts
@@ -5,9 +5,9 @@ export default defineNuxtPlugin({
setup (nuxtApp) {
nuxtApp.vueApp.mixin({
beforeCreate () {
- const { _registeredComponents } = this.$nuxt.ssrContext
+ const { modules } = this.$nuxt.ssrContext
const { __moduleIdentifier } = this.$options
- _registeredComponents.add(__moduleIdentifier)
+ modules.add(__moduleIdentifier)
},
})
},
diff --git a/packages/nuxt/src/components/loader.ts b/packages/nuxt/src/components/loader.ts
index 4d58f902de..2b4779e84c 100644
--- a/packages/nuxt/src/components/loader.ts
+++ b/packages/nuxt/src/components/loader.ts
@@ -61,7 +61,7 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
!components.some(c => c.pascalName === component.pascalName && c.mode === 'client')
if (isServerOnly) {
imports.add(genImport(serverComponentRuntime, [{ name: 'createServerComponent' }]))
- imports.add(`const ${identifier} = createServerComponent(${JSON.stringify(name)})`)
+ imports.add(`const ${identifier} = createServerComponent(${JSON.stringify(component.pascalName)})`)
if (!options.experimentalComponentIslands) {
logger.warn(`Standalone server components (\`${name}\`) are not yet supported without enabling \`experimental.componentIslands\`.`)
}
diff --git a/packages/nuxt/src/components/module.ts b/packages/nuxt/src/components/module.ts
index 649d7c3c80..fd88bccbec 100644
--- a/packages/nuxt/src/components/module.ts
+++ b/packages/nuxt/src/components/module.ts
@@ -121,11 +121,7 @@ export default defineNuxtModule({
// component-names.mjs
addTemplate(componentNamesTemplate)
// components.islands.mjs
- if (nuxt.options.experimental.componentIslands) {
- addTemplate({ ...componentsIslandsTemplate, filename: 'components.islands.mjs' })
- } else {
- addTemplate({ filename: 'components.islands.mjs', getContents: () => 'export const islandComponents = {}' })
- }
+ addTemplate({ ...componentsIslandsTemplate, filename: 'components.islands.mjs' })
if (componentOptions.generateMetadata) {
addTemplate(componentsMetadataTemplate)
diff --git a/packages/nuxt/src/components/templates.ts b/packages/nuxt/src/components/templates.ts
index 099a2d5c3d..6a03a25221 100644
--- a/packages/nuxt/src/components/templates.ts
+++ b/packages/nuxt/src/components/templates.ts
@@ -70,7 +70,11 @@ export const componentNamesTemplate: NuxtTemplate = {
export const componentsIslandsTemplate: NuxtTemplate = {
// components.islands.mjs'
- getContents ({ app }) {
+ getContents ({ app, nuxt }) {
+ if (!nuxt.options.experimental.componentIslands) {
+ return 'export const islandComponents = {}'
+ }
+
const components = app.components
const pages = app.pages
const islands = components.filter(component =>
diff --git a/packages/nuxt/src/core/app.ts b/packages/nuxt/src/core/app.ts
index 8e0e7bc2a0..494b30b798 100644
--- a/packages/nuxt/src/core/app.ts
+++ b/packages/nuxt/src/core/app.ts
@@ -60,7 +60,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
async function processTemplate (template: ResolvedNuxtTemplate) {
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
- const mark = performance.mark(fullPath)
+ const start = performance.now()
const oldContents = nuxt.vfs[fullPath]
const contents = await compileTemplate(template, templateContext).catch((e) => {
logger.error(`Could not compile template \`${template.filename}\`.`)
@@ -83,8 +83,8 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
changedTemplates.push(template)
}
- const perf = performance.measure(fullPath, mark.name)
- const setupTime = Math.round((perf.duration * 100)) / 100
+ const perf = performance.now() - start
+ const setupTime = Math.round((perf * 100)) / 100
if (nuxt.options.debug || setupTime > 500) {
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
diff --git a/packages/nuxt/src/core/builder.ts b/packages/nuxt/src/core/builder.ts
index 6292e450db..92bd04270d 100644
--- a/packages/nuxt/src/core/builder.ts
+++ b/packages/nuxt/src/core/builder.ts
@@ -1,9 +1,7 @@
-import { pathToFileURL } from 'node:url'
import type { EventType } from '@parcel/watcher'
import type { FSWatcher } from 'chokidar'
-import chokidar from 'chokidar'
-import { isIgnored, logger, tryResolveModule, useNuxt } from '@nuxt/kit'
-import { interopDefault } from 'mlly'
+import { watch as chokidarWatch } from 'chokidar'
+import { importModule, isIgnored, logger, tryResolveModule, useNuxt } from '@nuxt/kit'
import { debounce } from 'perfect-debounce'
import { normalize, relative, resolve } from 'pathe'
import type { Nuxt, NuxtBuilder } from 'nuxt/schema'
@@ -77,7 +75,7 @@ async function watch (nuxt: Nuxt) {
function createWatcher () {
const nuxt = useNuxt()
- const watcher = chokidar.watch(nuxt.options._layers.map(i => i.config.srcDir as string).filter(Boolean), {
+ const watcher = chokidarWatch(nuxt.options._layers.map(i => i.config.srcDir as string).filter(Boolean), {
...nuxt.options.watchers.chokidar,
ignoreInitial: true,
ignored: [
@@ -110,7 +108,7 @@ function createGranularWatcher () {
}
for (const dir of pathsToWatch) {
pending++
- const watcher = chokidar.watch(dir, { ...nuxt.options.watchers.chokidar, ignoreInitial: false, depth: 0, ignored: [isIgnored, '**/node_modules'] })
+ const watcher = chokidarWatch(dir, { ...nuxt.options.watchers.chokidar, ignoreInitial: false, depth: 0, ignored: [isIgnored, '**/node_modules'] })
const watchers: Record = {}
watcher.on('all', (event, path) => {
@@ -123,7 +121,7 @@ function createGranularWatcher () {
delete watchers[path]
}
if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) {
- watchers[path] = chokidar.watch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
+ watchers[path] = chokidarWatch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
watchers[path].on('all', (event, p) => nuxt.callHook('builder:watch', event, normalize(p)))
nuxt.hook('close', () => watchers[path]?.close())
}
@@ -151,7 +149,7 @@ async function createParcelWatcher () {
return false
}
- const { subscribe } = await import(pathToFileURL(watcherPath).href).then(interopDefault) as typeof import('@parcel/watcher')
+ const { subscribe } = await importModule(watcherPath)
for (const layer of nuxt.options._layers) {
if (!layer.config.srcDir) { continue }
const watcher = subscribe(layer.config.srcDir, (err, events) => {
@@ -201,5 +199,5 @@ async function loadBuilder (nuxt: Nuxt, builder: string): Promise {
if (!builderPath) {
throw new Error(`Loading \`${builder}\` builder failed. You can read more about the nuxt \`builder\` option at: \`https://nuxt.com/docs/api/nuxt-config#builder\``)
}
- return import(pathToFileURL(builderPath).href)
+ return importModule(builderPath)
}
diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts
index 42afa4878a..c8ed9e5bbb 100644
--- a/packages/nuxt/src/core/nitro.ts
+++ b/packages/nuxt/src/core/nitro.ts
@@ -6,7 +6,7 @@ import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from
import { joinURL, withTrailingSlash } from 'ufo'
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, writeTypes } from 'nitro'
import type { Nitro, NitroConfig, NitroOptions } from 'nitro/types'
-import { findPath, logger, resolveAlias, resolveIgnorePatterns, resolveNuxtModule, resolvePath } from '@nuxt/kit'
+import { findPath, logger, resolveAlias, resolveIgnorePatterns, resolveNuxtModule } from '@nuxt/kit'
import escapeRE from 'escape-string-regexp'
import { defu } from 'defu'
import { dynamicEventHandler } from 'h3'
@@ -112,6 +112,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
generateTsConfig: true,
tsconfigPath: 'tsconfig.server.json',
tsConfig: {
+ compilerOptions: {
+ lib: ['esnext', 'webworker', 'dom.iterable'],
+ },
include: [
join(nuxt.options.buildDir, 'types/nitro-nuxt.d.ts'),
...modules.map(m => join(relativeWithDot(nuxt.options.buildDir, m), 'runtime/server')),
@@ -405,7 +408,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
})
const cacheDir = resolve(nuxt.options.buildDir, 'cache/nitro/prerender')
- const cacheDriverPath = await resolvePath(join(distDir, 'core/runtime/nitro/cache-driver'))
+ const cacheDriverPath = join(distDir, 'core/runtime/nitro/cache-driver.js')
await fsp.rm(cacheDir, { recursive: true, force: true }).catch(() => {})
nitro.options._config.storage = defu(nitro.options._config.storage, {
'internal:nuxt:prerender': {
@@ -500,11 +503,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
for (const route of ['/200.html', '/404.html']) {
routes.add(route)
}
- if (nuxt.options.ssr) {
- if (nitro.options.prerender.crawlLinks) {
- routes.add('/')
- }
- } else {
+ if (!nuxt.options.ssr) {
routes.add('/index.html')
}
})
diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts
index 53aa4d6d84..d5c4240f2b 100644
--- a/packages/nuxt/src/core/nuxt.ts
+++ b/packages/nuxt/src/core/nuxt.ts
@@ -1,6 +1,6 @@
import { existsSync } from 'node:fs'
import { rm } from 'node:fs/promises'
-import { dirname, join, normalize, relative, resolve } from 'pathe'
+import { join, normalize, relative, resolve } from 'pathe'
import { createDebugger, createHooks } from 'hookable'
import ignore from 'ignore'
import type { LoadNuxtOptions } from '@nuxt/kit'
@@ -8,14 +8,20 @@ import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerP
import { resolvePath as _resolvePath } from 'mlly'
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema'
import type { PackageJson } from 'pkg-types'
-import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
+import { readPackageJSON } from 'pkg-types'
import { hash } from 'ohash'
+import consola from 'consola'
+import { colorize } from 'consola/utils'
+import { updateConfig } from 'c12/update'
+import { formatDate, resolveCompatibilityDatesFromEnv } from 'compatx'
+import type { DateString } from 'compatx'
import escapeRE from 'escape-string-regexp'
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
import defu from 'defu'
import { gt, satisfies } from 'semver'
+import { hasTTY, isCI } from 'std-env'
import pagesModule from '../pages/module'
import metaModule from '../head/module'
import componentsModule from '../components/module'
@@ -24,6 +30,7 @@ import importsModule from '../imports/module'
import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json'
import { scriptsStubsPreset } from '../imports/presets'
+import { resolveTypePath } from './utils/types'
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
import type { UnctxTransformPluginOptions } from './plugins/unctx'
import { UnctxTransformPlugin } from './plugins/unctx'
@@ -60,6 +67,9 @@ export function createNuxt (options: NuxtOptions): Nuxt {
return nuxt
}
+// TODO: update to nitro import
+const fallbackCompatibilityDate = '2024-04-03' as DateString
+
const nightlies = {
'nitropack': 'nitropack-nightly',
'nitro': 'nitro-nightly',
@@ -74,6 +84,8 @@ const keyDependencies = [
'@nuxt/schema',
]
+let warnedAboutCompatDate = false
+
async function initNuxt (nuxt: Nuxt) {
// Register user hooks
for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) {
@@ -82,6 +94,73 @@ async function initNuxt (nuxt: Nuxt) {
}
}
+ // Prompt to set compatibility date
+ nuxt.options.compatibilityDate = resolveCompatibilityDatesFromEnv(nuxt.options.compatibilityDate)
+
+ if (!nuxt.options.compatibilityDate.default) {
+ const todaysDate = formatDate(new Date())
+ nuxt.options.compatibilityDate.default = fallbackCompatibilityDate
+
+ const shouldShowPrompt = nuxt.options.dev && hasTTY && !isCI
+ if (!shouldShowPrompt) {
+ console.log(`Using \`${fallbackCompatibilityDate}\` as fallback compatibility date.`)
+ }
+
+ async function promptAndUpdate () {
+ const result = await consola.prompt(`Do you want to update your ${colorize('cyan', 'nuxt.config')} to set ${colorize('cyan', `compatibilityDate: '${todaysDate}'`)}?`, {
+ type: 'confirm',
+ default: true,
+ })
+ if (result !== true) {
+ console.log(`Using \`${fallbackCompatibilityDate}\` as fallback compatibility date.`)
+ return
+ }
+
+ try {
+ const res = await updateConfig({
+ configFile: 'nuxt.config',
+ cwd: nuxt.options.rootDir,
+ async onCreate ({ configFile }) {
+ const shallCreate = await consola.prompt(`Do you want to create ${colorize('cyan', relative(nuxt.options.rootDir, configFile))}?`, {
+ type: 'confirm',
+ default: true,
+ })
+ if (shallCreate !== true) {
+ return false
+ }
+ return _getDefaultNuxtConfig()
+ },
+ onUpdate (config) {
+ config.compatibilityDate = todaysDate
+ },
+ })
+
+ if (res?.configFile) {
+ nuxt.options.compatibilityDate = resolveCompatibilityDatesFromEnv(todaysDate)
+ consola.success(`Compatibility date set to \`${todaysDate}\` in \`${relative(nuxt.options.rootDir, res.configFile)}\``)
+ return
+ }
+ } catch (err) {
+ const message = err instanceof Error ? err.message : err
+
+ consola.error(`Failed to update config: ${message}`)
+ }
+
+ console.log(`Using \`${fallbackCompatibilityDate}\` as fallback compatibility date.`)
+ }
+
+ nuxt.hooks.hookOnce('nitro:init', (nitro) => {
+ if (warnedAboutCompatDate) { return }
+
+ nitro.hooks.hookOnce('compiled', () => {
+ warnedAboutCompatDate = true
+ // Print warning
+ console.info(`Nuxt now supports pinning the behavior of provider and deployment presets with a compatibility date. We recommend you specify a \`compatibilityDate\` in your \`nuxt.config\` file, or set an environment variable, such as \`COMPATIBILITY_DATE=${todaysDate}\`.`)
+ if (shouldShowPrompt) { promptAndUpdate() }
+ })
+ })
+ }
+
// Restart Nuxt when layer directories are added or removed
const layersDir = withTrailingSlash(resolve(nuxt.options.rootDir, 'layers'))
nuxt.hook('builder:watch', (event, relativePath) => {
@@ -107,29 +186,16 @@ async function initNuxt (nuxt: Nuxt) {
// ignore packages that exist in `package.json` as these can be resolved by TypeScript
if (dependencies.has(_pkg) && !(_pkg in nightlies)) { return [] }
- async function resolveTypePath (path: string) {
- try {
- const r = await _resolvePath(path, { url: nuxt.options.modulesDir, conditions: ['types', 'import', 'require'] })
- if (subpath) {
- return r.replace(/(?:\.d)?\.[mc]?[jt]s$/, '')
- }
- const rootPath = await resolvePackageJSON(r)
- return dirname(rootPath)
- } catch {
- return null
- }
- }
-
// deduplicate types for nightly releases
if (_pkg in nightlies) {
const nightly = nightlies[_pkg as keyof typeof nightlies]
- const path = await resolveTypePath(nightly + subpath)
+ const path = await resolveTypePath(nightly + subpath, subpath, nuxt.options.modulesDir)
if (path) {
return [[pkg, [path]], [nightly + subpath, [path]]]
}
}
- const path = await resolveTypePath(_pkg + subpath)
+ const path = await resolveTypePath(_pkg + subpath, subpath, nuxt.options.modulesDir)
if (path) {
return [[pkg, [path]]]
}
@@ -420,21 +486,6 @@ async function initNuxt (nuxt: Nuxt) {
})
}
- // Add
- if (nuxt.options.experimental.componentIslands) {
- addComponent({
- name: 'NuxtIsland',
- priority: 10, // built-in that we do not expect the user to override
- filePath: resolve(nuxt.options.appDir, 'components/nuxt-island'),
- })
-
- if (!nuxt.options.ssr && nuxt.options.experimental.componentIslands !== 'auto') {
- nuxt.options.ssr = true
- nuxt.options.nitro.routeRules ||= {}
- nuxt.options.nitro.routeRules['/**'] = defu(nuxt.options.nitro.routeRules['/**'], { ssr: false })
- }
- }
-
// Add stubs for and
for (const name of ['NuxtImg', 'NuxtPicture']) {
addComponent({
@@ -447,38 +498,6 @@ async function initNuxt (nuxt: Nuxt) {
})
}
- // Add prerender payload support
- if (!nuxt.options.dev && nuxt.options.experimental.payloadExtraction) {
- addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
- }
-
- // Add experimental cross-origin prefetch support using Speculation Rules API
- if (nuxt.options.experimental.crossOriginPrefetch) {
- addPlugin(resolve(nuxt.options.appDir, 'plugins/cross-origin-prefetch.client'))
- }
-
- // Add experimental page reload support
- if (nuxt.options.experimental.emitRouteChunkError === 'automatic') {
- addPlugin(resolve(nuxt.options.appDir, 'plugins/chunk-reload.client'))
- }
- // Add experimental session restoration support
- if (nuxt.options.experimental.restoreState) {
- addPlugin(resolve(nuxt.options.appDir, 'plugins/restore-state.client'))
- }
-
- // Add experimental automatic view transition api support
- if (nuxt.options.experimental.viewTransition) {
- addPlugin(resolve(nuxt.options.appDir, 'plugins/view-transitions.client'))
- }
-
- // Add experimental support for custom types in JSON payload
- if (nuxt.options.experimental.renderJsonPayloads) {
- nuxt.hooks.hook('modules:done', () => {
- addPlugin(resolve(nuxt.options.appDir, 'plugins/revive-payload.client'))
- addPlugin(resolve(nuxt.options.appDir, 'plugins/revive-payload.server'))
- })
- }
-
// Track components used to render for webpack
if (nuxt.options.builder === '@nuxt/webpack-builder') {
addPlugin(resolve(nuxt.options.appDir, 'plugins/preload.server'))
@@ -522,6 +541,51 @@ async function initNuxt (nuxt: Nuxt) {
await nuxt.callHook('modules:done')
+ // Add
+ if (nuxt.options.experimental.componentIslands) {
+ addComponent({
+ name: 'NuxtIsland',
+ priority: 10, // built-in that we do not expect the user to override
+ filePath: resolve(nuxt.options.appDir, 'components/nuxt-island'),
+ })
+
+ if (!nuxt.options.ssr && nuxt.options.experimental.componentIslands !== 'auto') {
+ nuxt.options.ssr = true
+ nuxt.options.nitro.routeRules ||= {}
+ nuxt.options.nitro.routeRules['/**'] = defu(nuxt.options.nitro.routeRules['/**'], { ssr: false })
+ }
+ }
+
+ // Add prerender payload support
+ if (!nuxt.options.dev && nuxt.options.experimental.payloadExtraction) {
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
+ }
+
+ // Add experimental cross-origin prefetch support using Speculation Rules API
+ if (nuxt.options.experimental.crossOriginPrefetch) {
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/cross-origin-prefetch.client'))
+ }
+
+ // Add experimental page reload support
+ if (nuxt.options.experimental.emitRouteChunkError === 'automatic') {
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/chunk-reload.client'))
+ }
+ // Add experimental session restoration support
+ if (nuxt.options.experimental.restoreState) {
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/restore-state.client'))
+ }
+
+ // Add experimental automatic view transition api support
+ if (nuxt.options.experimental.viewTransition) {
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/view-transitions.client'))
+ }
+
+ // Add experimental support for custom types in JSON payload
+ if (nuxt.options.experimental.renderJsonPayloads) {
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/revive-payload.client'))
+ addPlugin(resolve(nuxt.options.appDir, 'plugins/revive-payload.server'))
+ }
+
if (nuxt.options.experimental.appManifest) {
addRouteMiddleware({
name: 'manifest-route-rule',
@@ -611,7 +675,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise {
// Temporary until finding better placement for each
options.appDir = options.alias['#app'] = resolve(distDir, 'app')
- options._majorVersion = 3
+ options._majorVersion = 4
// De-duplicate key arrays
for (const key in options.app.head || {}) {
@@ -762,3 +826,10 @@ function createPortalProperties (sourceValue: any, options: NuxtOptions, paths:
})
}
}
+
+const _getDefaultNuxtConfig = () => /* js */
+`// https://nuxt.com/docs/api/configuration/nuxt-config
+export default defineNuxtConfig({
+ devtools: { enabled: true }
+})
+`
diff --git a/packages/nuxt/src/core/runtime/nitro/cache-driver.ts b/packages/nuxt/src/core/runtime/nitro/cache-driver.js
similarity index 80%
rename from packages/nuxt/src/core/runtime/nitro/cache-driver.ts
rename to packages/nuxt/src/core/runtime/nitro/cache-driver.js
index 60587fa21f..59a19e3b07 100644
--- a/packages/nuxt/src/core/runtime/nitro/cache-driver.ts
+++ b/packages/nuxt/src/core/runtime/nitro/cache-driver.js
@@ -1,10 +1,18 @@
+// @ts-check
+
import { defineDriver } from 'unstorage'
import fsDriver from 'unstorage/drivers/fs-lite'
import lruCache from 'unstorage/drivers/lru-cache'
-const normalizeFsKey = (item: string) => item.replaceAll(':', '_')
+/**
+ * @param {string} item
+ */
+const normalizeFsKey = item => item.replaceAll(':', '_')
-export default defineDriver((opts: { base: string }) => {
+/**
+ * @param {{ base: string }} opts
+ */
+export default defineDriver((opts) => {
const fs = fsDriver({ base: opts.base })
const lru = lruCache({ max: 1000 })
diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts
index cd1d8aaa23..ba94ffc673 100644
--- a/packages/nuxt/src/core/runtime/nitro/renderer.ts
+++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts
@@ -311,8 +311,6 @@ export default defineRenderHandler(async (event): Promise(watcherPath)
for (const layer of nuxt.options._layers) {
const subscription = await subscribe(layer.config.rootDir, onChange, {
ignore: ['!nuxt.schema.*'],
@@ -77,7 +74,7 @@ export default defineNuxtModule({
const filesToWatch = await Promise.all(nuxt.options._layers.map(layer =>
resolver.resolve(layer.config.rootDir, 'nuxt.schema.*'),
))
- const watcher = chokidar.watch(filesToWatch, {
+ const watcher = watch(filesToWatch, {
...nuxt.options.watchers.chokidar,
ignoreInitial: true,
})
diff --git a/packages/nuxt/src/core/utils/index.ts b/packages/nuxt/src/core/utils/index.ts
index 8321e2fd01..48daaef37e 100644
--- a/packages/nuxt/src/core/utils/index.ts
+++ b/packages/nuxt/src/core/utils/index.ts
@@ -1,5 +1,5 @@
-export * from './names'
-export * from './plugins'
+export { getNameFromPath, hasSuffix, resolveComponentNameSegments } from './names'
+export { isJS, isVue } from './plugins'
export function uniqueBy (arr: T[], key: K) {
if (arr.length < 2) {
diff --git a/packages/nuxt/src/core/utils/types.ts b/packages/nuxt/src/core/utils/types.ts
new file mode 100644
index 0000000000..23069fd4f9
--- /dev/null
+++ b/packages/nuxt/src/core/utils/types.ts
@@ -0,0 +1,17 @@
+import { resolvePackageJSON } from 'pkg-types'
+import { resolvePath as _resolvePath } from 'mlly'
+import { dirname } from 'pathe'
+import { tryUseNuxt } from '@nuxt/kit'
+
+export async function resolveTypePath (path: string, subpath: string, searchPaths = tryUseNuxt()?.options.modulesDir) {
+ try {
+ const r = await _resolvePath(path, { url: searchPaths, conditions: ['types', 'import', 'require'] })
+ if (subpath) {
+ return r.replace(/(?:\.d)?\.[mc]?[jt]s$/, '')
+ }
+ const rootPath = await resolvePackageJSON(r)
+ return dirname(rootPath)
+ } catch {
+ return null
+ }
+}
diff --git a/packages/nuxt/src/head/module.ts b/packages/nuxt/src/head/module.ts
index e22ee47092..520091428b 100644
--- a/packages/nuxt/src/head/module.ts
+++ b/packages/nuxt/src/head/module.ts
@@ -8,6 +8,7 @@ const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head'
export default defineNuxtModule({
meta: {
name: 'meta',
+ configKey: 'unhead',
},
async setup (options, nuxt) {
const runtimeDir = resolve(distDir, 'head/runtime')
diff --git a/packages/nuxt/src/index.ts b/packages/nuxt/src/index.ts
index 4940b0db94..966e4d8c8c 100644
--- a/packages/nuxt/src/index.ts
+++ b/packages/nuxt/src/index.ts
@@ -1,2 +1,2 @@
-export * from './core/nuxt'
-export * from './core/builder'
+export { createNuxt, loadNuxt } from './core/nuxt'
+export { build } from './core/builder'
diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts
index acdd060ac6..fb27a83963 100644
--- a/packages/nuxt/src/pages/module.ts
+++ b/packages/nuxt/src/pages/module.ts
@@ -3,6 +3,7 @@ import { mkdir, readFile } from 'node:fs/promises'
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, resolvePath, updateTemplates, useNitro } from '@nuxt/kit'
import { dirname, join, relative, resolve } from 'pathe'
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
+import { joinURL } from 'ufo'
import type { Nuxt, NuxtApp, NuxtPage } from 'nuxt/schema'
import { createRoutesContext } from 'unplugin-vue-router'
import { resolveOptions } from 'unplugin-vue-router/options'
@@ -11,12 +12,15 @@ import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-v
import type { NitroRouteConfig } from 'nitro/types'
import { defu } from 'defu'
import { distDir } from '../dirs'
+import { resolveTypePath } from '../core/utils/types'
import { normalizeRoutes, resolvePagesRoutes, resolveRoutePaths } from './utils'
import { extractRouteRules, getMappedPages } from './route-rules'
import type { PageMetaPluginOptions } from './plugins/page-meta'
import { PageMetaPlugin } from './plugins/page-meta'
import { RouteInjectionPlugin } from './plugins/route-injection'
+const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/
+
export default defineNuxtModule({
meta: {
name: 'pages',
@@ -28,6 +32,15 @@ export default defineNuxtModule({
layer => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages'),
)
+ nuxt.options.alias['#vue-router'] = 'vue-router'
+ const routerPath = await resolveTypePath('vue-router', '', nuxt.options.modulesDir) || 'vue-router'
+ nuxt.hook('prepare:types', ({ tsConfig }) => {
+ tsConfig.compilerOptions ||= {}
+ tsConfig.compilerOptions.paths ||= {}
+ tsConfig.compilerOptions.paths['#vue-router'] = [routerPath]
+ delete tsConfig.compilerOptions.paths['#vue-router/*']
+ })
+
async function resolveRouterOptions () {
const context = {
files: [] as Array<{ path: string, optional?: boolean }>,
@@ -210,6 +223,14 @@ export default defineNuxtModule({
references.push({ types: useExperimentalTypedPages ? 'vue-router/auto-routes' : 'vue-router' })
})
+ // Add vue-router route guard imports
+ nuxt.hook('imports:sources', (sources) => {
+ const routerImports = sources.find(s => s.from === '#app/composables/router' && s.imports.includes('onBeforeRouteLeave'))
+ if (routerImports) {
+ routerImports.from = 'vue-router'
+ }
+ })
+
// Regenerate templates when adding or removing pages
const updateTemplatePaths = nuxt.options._layers.flatMap((l) => {
const dir = (l.config.rootDir === nuxt.options.rootDir ? nuxt.options : l.config).dir
@@ -259,6 +280,59 @@ export default defineNuxtModule({
}
})
+ // Record all pages for use in prerendering
+ const prerenderRoutes = new Set()
+
+ function processPages (pages: NuxtPage[], currentPath = '/') {
+ for (const page of pages) {
+ // Add root of optional dynamic paths and catchalls
+ if (OPTIONAL_PARAM_RE.test(page.path) && !page.children?.length) {
+ prerenderRoutes.add(currentPath)
+ }
+
+ // Skip dynamic paths
+ if (page.path.includes(':')) { continue }
+
+ const route = joinURL(currentPath, page.path)
+ prerenderRoutes.add(route)
+
+ if (page.children) {
+ processPages(page.children, route)
+ }
+ }
+ }
+
+ nuxt.hook('pages:extend', (pages) => {
+ if (nuxt.options.dev) { return }
+
+ prerenderRoutes.clear()
+ processPages(pages)
+ })
+
+ // For static sites with ssr: false with crawl, prerender all routes
+ nuxt.hook('nitro:init', (nitro) => {
+ if (nuxt.options.dev || !nitro.options.static || nuxt.options.router.options.hashMode || !nitro.options.prerender.crawlLinks) { return }
+
+ // Only hint the first route when `ssr: true` and no routes are provided
+ if (nuxt.options.ssr) {
+ nitro.hooks.hook('prerender:routes', (routes) => {
+ if ([...routes].every(r => r.match(/(^\/api|\.\w+)/))) {
+ const [firstPage] = [...prerenderRoutes].sort()
+ routes.add(firstPage || '/')
+ }
+ })
+ return
+ }
+
+ // Prerender all non-dynamic page routes when generating `ssr: false` app
+ nuxt.hook('nitro:build:before', (nitro) => {
+ for (const route of nitro.options.prerender.routes || []) {
+ prerenderRoutes.add(route)
+ }
+ nitro.options.prerender.routes = Array.from(prerenderRoutes)
+ })
+ })
+
nuxt.hook('imports:extend', (imports) => {
imports.push(
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
diff --git a/packages/nuxt/src/pages/runtime/index.ts b/packages/nuxt/src/pages/runtime/index.ts
index 8ef8d8b066..2a46da204f 100644
--- a/packages/nuxt/src/pages/runtime/index.ts
+++ b/packages/nuxt/src/pages/runtime/index.ts
@@ -1 +1,2 @@
-export * from './composables'
+export { definePageMeta, defineRouteRules } from './composables'
+export type { PageMeta } from './composables'
diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts
index b89348ae6e..66997c121c 100644
--- a/packages/nuxt/src/pages/utils.ts
+++ b/packages/nuxt/src/pages/utils.ts
@@ -149,7 +149,7 @@ export async function augmentPages (routes: NuxtPage[], vfs: Record 0) {
- await augmentPages(route.children, vfs)
+ await augmentPages(route.children, vfs, augmentedPages)
}
}
return augmentedPages
diff --git a/packages/nuxt/test/__snapshots__/treeshake-client.test.ts.snap b/packages/nuxt/test/__snapshots__/treeshake-client.test.ts.snap
index d34f990205..dc32c7380b 100644
Binary files a/packages/nuxt/test/__snapshots__/treeshake-client.test.ts.snap and b/packages/nuxt/test/__snapshots__/treeshake-client.test.ts.snap differ
diff --git a/packages/nuxt/test/app.test.ts b/packages/nuxt/test/app.test.ts
index c7424e95ad..d43ed86f21 100644
--- a/packages/nuxt/test/app.test.ts
+++ b/packages/nuxt/test/app.test.ts
@@ -59,6 +59,10 @@ describe('resolveApp', () => {
"mode": "client",
"src": "/packages/nuxt/src/app/plugins/revive-payload.client.ts",
},
+ {
+ "mode": "client",
+ "src": "/packages/nuxt/src/app/plugins/chunk-reload.client.ts",
+ },
{
"filename": "components.plugin.mjs",
"getContents": [Function],
@@ -73,10 +77,6 @@ describe('resolveApp', () => {
"mode": "all",
"src": "/packages/nuxt/src/app/plugins/router.ts",
},
- {
- "mode": "client",
- "src": "/packages/nuxt/src/app/plugins/chunk-reload.client.ts",
- },
],
"rootComponent": "/packages/nuxt/src/app/components/nuxt-root.vue",
"templates": [],
diff --git a/packages/nuxt/test/treeshake-client.test.ts b/packages/nuxt/test/treeshake-client.test.ts
index e0b076d950..27a54baf87 100644
--- a/packages/nuxt/test/treeshake-client.test.ts
+++ b/packages/nuxt/test/treeshake-client.test.ts
@@ -67,10 +67,19 @@ const treeshake = async (source: string): Promise => {
}
async function SFCCompile (name: string, source: string, options: Options, ssr = false): Promise {
- const result = await (vuePlugin({
+ const plugin = vuePlugin({
compiler: VueCompilerSFC,
...options,
- }).transform! as Function).call({
+ })
+ // @ts-expect-error Types are not correct as they are too generic
+ plugin.configResolved!({
+ isProduction: options.isProduction,
+ command: 'build',
+ root: process.cwd(),
+ build: { sourcemap: false },
+ define: {},
+ })
+ const result = await (plugin.transform! as Function).call({
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
@@ -84,14 +93,16 @@ async function SFCCompile (name: string, source: string, options: Options, ssr =
return typeof result === 'string' ? result : result?.code
}
-const stateToTest: { name: string, options: Partial }[] = [
+const stateToTest: { index: number, name: string, options: Partial }[] = [
{
+ index: 0,
name: 'prod',
options: {
isProduction: true,
},
},
{
+ index: 1,
name: 'dev',
options: {
isProduction: false,
@@ -107,93 +118,91 @@ const stateToTest: { name: string, options: Partial {
vi.spyOn(process, 'cwd').mockImplementation(() => '')
- for (const [index, state] of stateToTest.entries()) {
- it(`should treeshake ClientOnly correctly in ${state.name}`, async () => {
- // add index to avoid using vite vue plugin cache
- const clientResult = await SFCCompile(`SomeComponent${index}.vue`, WithClientOnly, state.options)
+ it.each(stateToTest)(`should treeshake ClientOnly correctly in $name`, async (state) => {
+ // add index to avoid using vite vue plugin cache
+ const clientResult = await SFCCompile(`SomeComponent${state.index}.vue`, WithClientOnly, state.options)
- const ssrResult = await SFCCompile(`SomeComponent${index}.vue`, WithClientOnly, state.options, true)
+ const ssrResult = await SFCCompile(`SomeComponent${state.index}.vue`, WithClientOnly, state.options, true)
- const treeshaken = await treeshake(ssrResult)
- const [_, scopeId] = clientResult.match(/_pushScopeId\("(.*)"\)/)!
+ const treeshaken = await treeshake(ssrResult)
+ const [_, scopeId] = clientResult.match(/_pushScopeId\("(.*)"\)/)!
- // ensure the id is correctly passed between server and client
- expect(clientResult).toContain(`pushScopeId("${scopeId}")`)
- expect(treeshaken).toContain(`
`)
+ // ensure the id is correctly passed between server and client
+ expect(clientResult).toContain(`pushScopeId("${scopeId}")`)
+ expect(treeshaken).toContain(`
`)
- expect(clientResult).toContain('should-be-treeshaken')
- expect(treeshaken).not.toContain('should-be-treeshaken')
+ expect(clientResult).toContain('should-be-treeshaken')
+ expect(treeshaken).not.toContain('should-be-treeshaken')
- expect(treeshaken).not.toContain('import HelloWorld from \'../HelloWorld.vue\'')
- expect(clientResult).toContain('import HelloWorld from \'../HelloWorld.vue\'')
+ expect(treeshaken).not.toContain('import HelloWorld from \'../HelloWorld.vue\'')
+ expect(clientResult).toContain('import HelloWorld from \'../HelloWorld.vue\'')
- expect(treeshaken).not.toContain('import { Treeshaken } from \'somepath\'')
- expect(clientResult).toContain('import { Treeshaken } from \'somepath\'')
+ expect(treeshaken).not.toContain('import { Treeshaken } from \'somepath\'')
+ expect(clientResult).toContain('import { Treeshaken } from \'somepath\'')
- // remove resolved import
- expect(treeshaken).not.toContain('const _component_ResolvedImport =')
- expect(clientResult).toContain('const _component_ResolvedImport =')
+ // remove resolved import
+ expect(treeshaken).not.toContain('const _component_ResolvedImport =')
+ expect(clientResult).toContain('const _component_ResolvedImport =')
- // treeshake multi line variable declaration
- expect(clientResult).toContain('const SomeIsland = defineAsyncComponent(async () => {')
- expect(treeshaken).not.toContain('const SomeIsland = defineAsyncComponent(async () => {')
- expect(treeshaken).not.toContain('return (await import(\'./../some.island.vue\'))')
- expect(treeshaken).toContain('const NotToBeTreeShaken = defineAsyncComponent(async () => {')
+ // treeshake multi line variable declaration
+ expect(clientResult).toContain('const SomeIsland = defineAsyncComponent(async () => {')
+ expect(treeshaken).not.toContain('const SomeIsland = defineAsyncComponent(async () => {')
+ expect(treeshaken).not.toContain('return (await import(\'./../some.island.vue\'))')
+ expect(treeshaken).toContain('const NotToBeTreeShaken = defineAsyncComponent(async () => {')
- // treeshake object and array declaration
- expect(treeshaken).not.toContain('const { ObjectPattern } = await import(\'nuxt.com\')')
- expect(treeshaken).not.toContain('const { ObjectPattern: ObjectPatternDeclaration } = await import(\'nuxt.com\')')
- expect(treeshaken).toContain('const { ButShouldNotBeTreeShaken } = defineAsyncComponent(async () => {')
- expect(treeshaken).toContain('const [ { Dont, }, That] = defineAsyncComponent(async () => {')
+ // treeshake object and array declaration
+ expect(treeshaken).not.toContain('const { ObjectPattern } = await import(\'nuxt.com\')')
+ expect(treeshaken).not.toContain('const { ObjectPattern: ObjectPatternDeclaration } = await import(\'nuxt.com\')')
+ expect(treeshaken).toContain('const { ButShouldNotBeTreeShaken } = defineAsyncComponent(async () => {')
+ expect(treeshaken).toContain('const [ { Dont, }, That] = defineAsyncComponent(async () => {')
- // treeshake object that has an assignment pattern
- expect(treeshaken).toContain('const { woooooo, } = defineAsyncComponent(async () => {')
- expect(treeshaken).not.toContain('const { Deep, assignment: { Pattern = ofComponent } } = defineAsyncComponent(async () => {')
+ // treeshake object that has an assignment pattern
+ expect(treeshaken).toContain('const { woooooo, } = defineAsyncComponent(async () => {')
+ expect(treeshaken).not.toContain('const { Deep, assignment: { Pattern = ofComponent } } = defineAsyncComponent(async () => {')
- // expect no empty ObjectPattern on treeshaking
- expect(treeshaken).not.toContain('const { } = defineAsyncComponent')
- expect(treeshaken).not.toContain('import { } from')
+ // expect no empty ObjectPattern on treeshaking
+ expect(treeshaken).not.toContain('const { } = defineAsyncComponent')
+ expect(treeshaken).not.toContain('import { } from')
- // expect components used in setup to not be removed
- expect(treeshaken).toContain('import DontRemoveThisSinceItIsUsedInSetup from \'./ComponentWithProps.vue\'')
+ // expect components used in setup to not be removed
+ expect(treeshaken).toContain('import DontRemoveThisSinceItIsUsedInSetup from \'./ComponentWithProps.vue\'')
- // expect import of ClientImport to be treeshaken but not Glob since it is also used outside
- expect(treeshaken).not.toContain('ClientImport')
- expect(treeshaken).toContain('import { Glob } from \'#components\'')
+ // expect import of ClientImport to be treeshaken but not Glob since it is also used outside
+ expect(treeshaken).not.toContain('ClientImport')
+ expect(treeshaken).toContain('import { Glob } from \'#components\'')
- // treeshake .client slot
- expect(treeshaken).not.toContain('ByeBye')
- // don't treeshake variables that has the same name as .client components
- expect(treeshaken).toContain('NotDotClientComponent')
- expect(treeshaken).not.toContain('(DotClientComponent')
+ // treeshake .client slot
+ expect(treeshaken).not.toContain('ByeBye')
+ // don't treeshake variables that has the same name as .client components
+ expect(treeshaken).toContain('NotDotClientComponent')
+ expect(treeshaken).not.toContain('(DotClientComponent')
- expect(treeshaken).not.toContain('AutoImportedComponent')
- expect(treeshaken).toContain('AutoImportedNotTreeShakenComponent')
+ expect(treeshaken).not.toContain('AutoImportedComponent')
+ expect(treeshaken).toContain('AutoImportedNotTreeShakenComponent')
- expect(treeshaken).not.toContain('Both')
- expect(treeshaken).not.toContain('AreTreeshaken')
+ expect(treeshaken).not.toContain('Both')
+ expect(treeshaken).not.toContain('AreTreeshaken')
- if (state.options.isProduction === false) {
- // treeshake at inlined template
- expect(treeshaken).not.toContain('ssrRenderComponent($setup["HelloWorld"]')
- expect(treeshaken).toContain('ssrRenderComponent($setup["Glob"]')
- } else {
- // treeshake unref
- expect(treeshaken).not.toContain('ssrRenderComponent(_unref(HelloWorld')
- expect(treeshaken).toContain('ssrRenderComponent(_unref(Glob')
- }
- expect(treeshaken.replace(/data-v-\w{8}/g, 'data-v-one-hash').replace(/scoped=\w{8}/g, 'scoped=one-hash')).toMatchSnapshot()
- })
- }
+ if (state.options.isProduction === false) {
+ // treeshake at inlined template
+ expect(treeshaken).not.toContain('ssrRenderComponent($setup["HelloWorld"]')
+ expect(treeshaken).toContain('ssrRenderComponent($setup["Glob"]')
+ } else {
+ // treeshake unref
+ expect(treeshaken).not.toContain('ssrRenderComponent(_unref(HelloWorld')
+ expect(treeshaken).toContain('ssrRenderComponent(_unref(Glob')
+ }
+ expect(treeshaken.replace(/data-v-\w{8}/g, 'data-v-one-hash').replace(/scoped=\w{8}/g, 'scoped=one-hash')).toMatchSnapshot()
+ })
it('should not treeshake reused component #26137', async () => {
const treeshaken = await treeshake(`import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from "vue"
import { ssrRenderComponent as _ssrRenderComponent, ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer"
-
+
export function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_AppIcon = _resolveComponent("AppIcon")
const _component_ClientOnly = _resolveComponent("ClientOnly")
-
+
_push(\`
\`)
_push(_ssrRenderComponent(_component_AppIcon, { name: "caret-left" }, null, _parent))
_push(_ssrRenderComponent(_component_ClientOnly, null, {
diff --git a/packages/nuxt/types.d.mts b/packages/nuxt/types.d.mts
index 046d78f817..f027f93b6a 100644
--- a/packages/nuxt/types.d.mts
+++ b/packages/nuxt/types.d.mts
@@ -1,4 +1,5 @@
///
+///
import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
diff --git a/packages/nuxt/types.d.ts b/packages/nuxt/types.d.ts
index cd426f1c78..2ecb9dc72e 100644
--- a/packages/nuxt/types.d.ts
+++ b/packages/nuxt/types.d.ts
@@ -1,4 +1,6 @@
///
+///
+
import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
import type { H3Event } from 'h3'
diff --git a/packages/schema/build.config.ts b/packages/schema/build.config.ts
index d12712e2a0..22f17cbef8 100644
--- a/packages/schema/build.config.ts
+++ b/packages/schema/build.config.ts
@@ -23,6 +23,8 @@ export default defineBuildConfig({
externals: [
// Type imports
'#app/components/nuxt-link',
+ 'cssnano',
+ 'autoprefixer',
'ofetch',
'vue-router',
'@nuxt/telemetry',
diff --git a/packages/schema/package.json b/packages/schema/package.json
index cd0c7d286b..5f7916be41 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -39,23 +39,23 @@
"@types/file-loader": "5.0.4",
"@types/pug": "2.0.10",
"@types/sass-loader": "8.0.8",
- "@unhead/schema": "1.9.14",
- "@vitejs/plugin-vue": "5.0.4",
+ "@unhead/schema": "1.9.15",
+ "@vitejs/plugin-vue": "5.0.5",
"@vitejs/plugin-vue-jsx": "4.0.0",
- "@vue/compiler-core": "3.4.30",
- "@vue/compiler-sfc": "3.4.30",
- "@vue/language-core": "2.0.22",
- "c12": "1.11.1",
+ "@vue/compiler-core": "3.4.31",
+ "@vue/compiler-sfc": "3.4.31",
+ "@vue/language-core": "2.0.26",
+ "c12": "2.0.0-beta.1",
"esbuild-loader": "4.2.0",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"ignore": "5.3.1",
- "nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"ofetch": "1.3.4",
- "unbuild": "latest",
+ "unbuild": "3.0.0-rc.6",
"unctx": "2.3.1",
"unenv": "1.9.0",
- "vite": "5.3.1",
- "vue": "3.4.30",
+ "vite": "5.3.3",
+ "vue": "3.4.31",
"vue-bundle-renderer": "2.1.0",
"vue-loader": "17.4.2",
"vue-router": "4.4.0",
@@ -68,7 +68,7 @@
"defu": "^6.1.4",
"hookable": "^5.5.3",
"pathe": "^1.1.2",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.1.3",
"scule": "^1.3.0",
"std-env": "^3.7.0",
"ufo": "^1.5.3",
diff --git a/packages/schema/src/config/adhoc.ts b/packages/schema/src/config/adhoc.ts
index b6a849b853..4328909a32 100644
--- a/packages/schema/src/config/adhoc.ts
+++ b/packages/schema/src/config/adhoc.ts
@@ -23,7 +23,7 @@ export default defineUntypedSchema({
/**
* Configure how Nuxt auto-imports composables into your application.
- * @see [Nuxt 3 documentation](https://nuxt.com/docs/guide/directory-structure/composables)
+ * @see [Nuxt documentation](https://nuxt.com/docs/guide/directory-structure/composables)
* @type {typeof import('../src/types/imports').ImportsOptions}
*/
imports: {
diff --git a/packages/schema/src/config/common.ts b/packages/schema/src/config/common.ts
index a8d50cde5a..5940f1c2a9 100644
--- a/packages/schema/src/config/common.ts
+++ b/packages/schema/src/config/common.ts
@@ -421,7 +421,7 @@ export default defineUntypedSchema({
'~~': rootDir,
'@@': rootDir,
[basename(assetsDir)]: join(srcDir, assetsDir),
- [basename(publicDir)]: join(srcDir, publicDir),
+ [basename(publicDir)]: resolve(srcDir, publicDir),
...val,
}
},
diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts
index 7ae8f41d3d..d93773f4c2 100644
--- a/packages/schema/src/config/experimental.ts
+++ b/packages/schema/src/config/experimental.ts
@@ -246,8 +246,9 @@ export default defineUntypedSchema({
/**
* Set an alternative watcher that will be used as the watching service for Nuxt.
*
- * Nuxt uses 'chokidar-granular' by default, which will ignore top-level directories
- * (like `node_modules` and `.git`) that are excluded from watching.
+ * Nuxt uses 'chokidar-granular' if your source directory is the same as your root
+ * directory . This will ignore top-level directories (like `node_modules` and `.git`)
+ * that are excluded from watching.
*
* You can set this instead to `parcel` to use `@parcel/watcher`, which may improve
* performance in large projects or on Windows platforms.
@@ -257,7 +258,18 @@ export default defineUntypedSchema({
* @see [Parcel watcher](https://github.com/parcel-bundler/watcher)
* @type {'chokidar' | 'parcel' | 'chokidar-granular'}
*/
- watcher: 'chokidar-granular',
+ watcher: {
+ $resolve: async (val, get) => {
+ if (val) {
+ return val
+ }
+ const [srcDir, rootDir] = await Promise.all([get('srcDir'), get('rootDir')]) as [string, string]
+ if (srcDir === rootDir) {
+ return 'chokidar-granular'
+ }
+ return 'chokidar'
+ },
+ },
/**
* Enable native async context to be accessible for nested composables
diff --git a/packages/schema/src/config/internal.ts b/packages/schema/src/config/internal.ts
index 346beff214..77c79bd5bc 100644
--- a/packages/schema/src/config/internal.ts
+++ b/packages/schema/src/config/internal.ts
@@ -2,7 +2,7 @@ import { defineUntypedSchema } from 'untyped'
export default defineUntypedSchema({
/** @private */
- _majorVersion: 3,
+ _majorVersion: 4,
/** @private */
_legacyGenerate: false,
/** @private */
diff --git a/packages/schema/src/config/postcss.ts b/packages/schema/src/config/postcss.ts
index 83694cfd18..2395c15983 100644
--- a/packages/schema/src/config/postcss.ts
+++ b/packages/schema/src/config/postcss.ts
@@ -1,12 +1,45 @@
import { defineUntypedSchema } from 'untyped'
+const ensureItemIsLast = (item: string) => (arr: string[]) => {
+ const index = arr.indexOf(item)
+ if (index !== -1) {
+ arr.splice(index, 1)
+ arr.push(item)
+ }
+ return arr
+}
+
+const orderPresets = {
+ cssnanoLast: ensureItemIsLast('cssnano'),
+ autoprefixerLast: ensureItemIsLast('autoprefixer'),
+ autoprefixerAndCssnanoLast (names: string[]) {
+ return orderPresets.cssnanoLast(orderPresets.autoprefixerLast(names))
+ },
+}
+
export default defineUntypedSchema({
postcss: {
+ /**
+ * A strategy for ordering PostCSS plugins.
+ *
+ * @type {'cssnanoLast' | 'autoprefixerLast' | 'autoprefixerAndCssnanoLast' | string[] | ((names: string[]) => string[])}
+ */
+ order: {
+ $resolve: (val: string | string[] | ((plugins: string[]) => string[])): string[] | ((plugins: string[]) => string[]) => {
+ if (typeof val === 'string') {
+ if (!(val in orderPresets)) {
+ throw new Error(`[nuxt] Unknown PostCSS order preset: ${val}`)
+ }
+ return orderPresets[val as keyof typeof orderPresets]
+ }
+ return val ?? orderPresets.autoprefixerAndCssnanoLast
+ },
+ },
/**
* Options for configuring PostCSS plugins.
*
* https://postcss.org/
- * @type {Record & { autoprefixer?: any; cssnano?: any }}
+ * @type {Record & { autoprefixer?: typeof import('autoprefixer').Options; cssnano?: typeof import('cssnano').Options }}
*/
plugins: {
/**
diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts
index eebdb6e56b..0eb5c1588f 100644
--- a/packages/schema/src/index.ts
+++ b/packages/schema/src/index.ts
@@ -1,13 +1,13 @@
// Types
-export * from './types/compatibility'
-export * from './types/components'
-export * from './types/config'
-export * from './types/hooks'
-export * from './types/imports'
-export * from './types/head'
-export * from './types/module'
-export * from './types/nuxt'
-export * from './types/router'
+export type { NuxtCompatibility, NuxtCompatibilityIssue, NuxtCompatibilityIssues } from './types/compatibility'
+export type { Component, ComponentMeta, ComponentsDir, ComponentsOptions, ScanDir } from './types/components'
+export type { AppConfig, AppConfigInput, CustomAppConfig, NuxtAppConfig, NuxtBuilder, NuxtConfig, NuxtConfigLayer, NuxtOptions, PublicRuntimeConfig, RuntimeConfig, RuntimeValue, SchemaDefinition, UpperSnakeCase, ViteConfig } from './types/config'
+export type { GenerateAppOptions, HookResult, ImportPresetWithDeprecation, NuxtAnalyzeMeta, NuxtHookName, NuxtHooks, NuxtLayout, NuxtMiddleware, NuxtPage, TSReference, VueTSConfig, WatchEvent } from './types/hooks'
+export type { ImportsOptions } from './types/imports'
+export type { AppHeadMetaObject, MetaObject, MetaObjectRaw, HeadAugmentations } from './types/head'
+export type { ModuleDefinition, ModuleMeta, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, NuxtModule, ResolvedModuleOptions } from './types/module'
+export type { Nuxt, NuxtApp, NuxtPlugin, NuxtPluginTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate } from './types/nuxt'
+export type { RouterConfig, RouterConfigSerializable, RouterOptions } from './types/router'
// Schema
export { default as NuxtConfigSchema } from './config/index'
diff --git a/packages/schema/src/types/config.ts b/packages/schema/src/types/config.ts
index db349cd529..c89e261fe1 100644
--- a/packages/schema/src/types/config.ts
+++ b/packages/schema/src/types/config.ts
@@ -75,9 +75,10 @@ export interface NuxtBuilder {
}
// Normalized Nuxt options available as `nuxt.options.*`
-export interface NuxtOptions extends Omit {
+export interface NuxtOptions extends Omit {
sourcemap: Required>
builder: '@nuxt/vite-builder' | '@nuxt/webpack-builder' | NuxtBuilder
+ postcss: Omit & { order: Exclude }
webpack: ConfigSchema['webpack'] & {
$client: ConfigSchema['webpack']
$server: ConfigSchema['webpack']
diff --git a/packages/ui-templates/lib/render.ts b/packages/ui-templates/lib/render.ts
index ea444ed059..1542f9386f 100644
--- a/packages/ui-templates/lib/render.ts
+++ b/packages/ui-templates/lib/render.ts
@@ -49,6 +49,7 @@ export const RenderPlugin = () => {
// Apply critters to inline styles
html = await critters.process(html)
}
+ html = html.replace(/]*>/, '')
// We no longer need references to external CSS
html = html.replace(/]*>/g, '')
diff --git a/packages/ui-templates/package.json b/packages/ui-templates/package.json
index 21ebba1938..6795a17821 100644
--- a/packages/ui-templates/package.json
+++ b/packages/ui-templates/package.json
@@ -12,7 +12,6 @@
"scripts": {
"build": "vite build",
"dev": "vite",
- "lint": "eslint --ext .ts,.js .",
"optimize-assets": "npx svgo public/assets/**/*.svg",
"postinstall": "pnpm build",
"prerender": "pnpm build && jiti ./lib/prerender",
@@ -20,17 +19,18 @@
},
"devDependencies": {
"@types/html-minifier": "4.0.5",
- "@unocss/reset": "0.61.0",
+ "@unocss/reset": "0.61.2",
"critters": "0.0.24",
"execa": "9.3.0",
- "globby": "14.0.1",
+ "globby": "14.0.2",
"html-minifier": "4.0.0",
- "jiti": "1.21.6",
+ "html-validate": "8.20.1",
+ "jiti": "2.0.0-beta.3",
"knitwork": "1.1.0",
"pathe": "1.1.2",
"prettier": "3.3.2",
"scule": "1.3.0",
- "unocss": "0.61.0",
- "vite": "5.3.1"
+ "unocss": "0.61.2",
+ "vite": "5.3.3"
}
}
diff --git a/packages/ui-templates/templates/welcome/index.html b/packages/ui-templates/templates/welcome/index.html
index 27ada0dcb1..95fcfd383e 100644
--- a/packages/ui-templates/templates/welcome/index.html
+++ b/packages/ui-templates/templates/welcome/index.html
@@ -9,7 +9,7 @@