mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 13:15:12 +00:00
Merge remote-tracking branch 'origin/main' into perf/unusedplugin
This commit is contained in:
commit
3e4d39ecbe
168
.eslintrc
168
.eslintrc
@ -1,168 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/eslintrc",
|
||||
"ignorePatterns": [
|
||||
"dist",
|
||||
"public",
|
||||
"node_modules",
|
||||
"packages/schema/schema"
|
||||
],
|
||||
"globals": {
|
||||
"NodeJS": true,
|
||||
"$fetch": true
|
||||
},
|
||||
"plugins": ["jsdoc", "import", "unicorn", "no-only-tests"],
|
||||
"extends": [
|
||||
"plugin:jsdoc/recommended",
|
||||
"@nuxt/eslint-config",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
"rules": {
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
"ignoreDeclarationSort": true
|
||||
}
|
||||
],
|
||||
"no-only-tests/no-only-tests": "error",
|
||||
"unicorn/prefer-node-protocol": "error",
|
||||
"no-console": "warn",
|
||||
"vue/one-component-per-file": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
|
||||
// Vue stylistic rules from `@antfu/eslint-config`
|
||||
"vue/array-bracket-spacing": ["error", "never"],
|
||||
"vue/arrow-spacing": ["error", { "after": true, "before": true }],
|
||||
"vue/block-spacing": ["error", "always"],
|
||||
"vue/block-tag-newline": [
|
||||
"error",
|
||||
{
|
||||
"multiline": "always",
|
||||
"singleline": "always"
|
||||
}
|
||||
],
|
||||
"vue/brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
"vue/comma-dangle": ["error", "always-multiline"],
|
||||
"vue/comma-spacing": ["error", { "after": true, "before": false }],
|
||||
"vue/comma-style": ["error", "last"],
|
||||
"vue/html-comment-content-spacing": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"exceptions": ["-"]
|
||||
}
|
||||
],
|
||||
"vue/key-spacing": ["error", { "afterColon": true, "beforeColon": false }],
|
||||
"vue/keyword-spacing": ["error", { "after": true, "before": true }],
|
||||
"vue/object-curly-newline": "off",
|
||||
"vue/object-curly-spacing": ["error", "always"],
|
||||
"vue/object-property-newline": [
|
||||
"error",
|
||||
{ "allowMultiplePropertiesPerLine": true }
|
||||
],
|
||||
"vue/operator-linebreak": ["error", "before"],
|
||||
"vue/padding-line-between-blocks": ["error", "always"],
|
||||
"vue/quote-props": ["error", "consistent-as-needed"],
|
||||
"vue/space-in-parens": ["error", "never"],
|
||||
"vue/template-curly-spacing": "error",
|
||||
|
||||
"jsdoc/require-jsdoc": "off",
|
||||
"jsdoc/require-param": "off",
|
||||
"jsdoc/require-returns": "off",
|
||||
"jsdoc/require-param-type": "off",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "#vue-router",
|
||||
"group": "external"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"import/no-restricted-paths": [
|
||||
"error",
|
||||
{
|
||||
"zones": [
|
||||
{
|
||||
"from": "packages/nuxt/src/!(core)/**/*",
|
||||
"target": "packages/nuxt/src/core",
|
||||
"message": "core should not directly import from modules."
|
||||
},
|
||||
{
|
||||
"from": "packages/nuxt/src/!(app)/**/*",
|
||||
"target": "packages/nuxt/src/app",
|
||||
"message": "app should not directly import from modules."
|
||||
},
|
||||
{
|
||||
"from": "packages/nuxt/src/app/**/index.ts",
|
||||
"target": "packages/nuxt/src",
|
||||
"message": "should not import from barrel/index files"
|
||||
},
|
||||
{
|
||||
"from": "packages/nitro",
|
||||
"target": "packages/!(nitro)/**/*",
|
||||
"message": "nitro should not directly import other packages."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
"disallowTypeAnnotations": false
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/ban-ts-comment": [
|
||||
"error",
|
||||
{
|
||||
"ts-expect-error": "allow-with-description",
|
||||
"ts-ignore": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-ts-expect-error": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
],
|
||||
"jsdoc/check-tag-names": [
|
||||
"error",
|
||||
{
|
||||
"definedTags": ["__NO_SIDE_EFFECTS__"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["packages/schema/**"],
|
||||
"rules": {
|
||||
"jsdoc/valid-types": "off",
|
||||
"jsdoc/check-tag-names": [
|
||||
"error",
|
||||
{
|
||||
"definedTags": ["experimental"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["packages/nuxt/src/app/**", "test/**", "**/runtime/**"],
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
"ignoreInternal": true,
|
||||
"tagNamePreference": {
|
||||
"warning": "warning",
|
||||
"note": "note"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,10 +1,10 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 📚 Nuxt 3 Documentation
|
||||
url: https://nuxt.com/docs/
|
||||
url: https://nuxt.com/docs
|
||||
about: Check the documentation for usage of Nuxt 3
|
||||
- name: 📚 Nuxt 2 Documentation
|
||||
url: https://v2.nuxt.com/
|
||||
url: https://v2.nuxt.com
|
||||
about: Check the documentation for usage of Nuxt 2
|
||||
- name: 💬 Discussions
|
||||
url: https://github.com/nuxt/nuxt/discussions
|
||||
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,37 +1,19 @@
|
||||
<!---
|
||||
☝️ PR title should follow conventional commits (https://conventionalcommits.org)
|
||||
|
||||
Please carefully read the contribution docs before creating a pull request
|
||||
👉 https://nuxt.com/docs/community/contribution
|
||||
-->
|
||||
|
||||
### 🔗 Linked issue
|
||||
|
||||
<!-- Please ensure there is an open issue and mention its number as #123 -->
|
||||
|
||||
### ❓ Type of change
|
||||
|
||||
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->
|
||||
|
||||
- [ ] 📖 Documentation (updates to the documentation, readme or JSdoc annotations)
|
||||
- [ ] 🐞 Bug fix (a non-breaking change that fixes an issue)
|
||||
- [ ] 👌 Enhancement (improving an existing functionality like performance)
|
||||
- [ ] ✨ New feature (a non-breaking change that adds functionality)
|
||||
- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)
|
||||
- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)
|
||||
<!-- Please ensure there is an open issue and mention its number. For example, "resolves #123" -->
|
||||
|
||||
### 📚 Description
|
||||
|
||||
<!-- Describe your changes in detail -->
|
||||
<!-- Why is this change required? What problem does it solve? -->
|
||||
<!-- If it resolves an open issue, please link to the issue here. For example "Resolves #1337" -->
|
||||
<!-- Describe your changes in detail. Why is this change required? What problem does it solve? -->
|
||||
|
||||
### 📝 Checklist
|
||||
<!----------------------------------------------------------------------
|
||||
Before creating the pull request, please make sure you do the following:
|
||||
|
||||
<!-- Put an `x` in all the boxes that apply. -->
|
||||
<!-- If your change requires a documentation PR, please link it appropriately -->
|
||||
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- Check that there isn't already a PR that solves the problem the same way. If you find a duplicate, please help us reviewing it.
|
||||
- Read the contribution docs at https://nuxt.com/docs/community/contribution
|
||||
- Ensure that PR title follows conventional commits (https://conventionalcommits.org)
|
||||
- Update the corresponding documentation if needed.
|
||||
- Include relevant tests that fail without this PR but pass with it.
|
||||
|
||||
- [ ] I have linked an issue or discussion.
|
||||
- [ ] I have added tests (if possible).
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
Thank you for contributing to Nuxt!
|
||||
----------------------------------------------------------------------->
|
||||
|
6
.github/workflows/autofix.yml
vendored
6
.github/workflows/autofix.yml
vendored
@ -26,9 +26,6 @@ jobs:
|
||||
- name: Build (stub)
|
||||
run: pnpm dev:prepare
|
||||
|
||||
- name: Lint (code)
|
||||
run: pnpm lint:fix
|
||||
|
||||
- name: Test (unit)
|
||||
run: pnpm test:unit -u
|
||||
|
||||
@ -52,4 +49,7 @@ jobs:
|
||||
if: ${{ !contains(github.head_ref, 'renovate') }}
|
||||
run: pnpm vitest run bundle -u
|
||||
|
||||
- name: Lint (code)
|
||||
run: pnpm lint:fix
|
||||
|
||||
- uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84
|
||||
|
4
.github/workflows/benchmark.yml
vendored
4
.github/workflows/benchmark.yml
vendored
@ -15,8 +15,6 @@ env:
|
||||
# 7 GiB by default on GitHub, setting to 6 GiB
|
||||
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
# install playwright binary manually (because pnpm only runs install script once)
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||
|
||||
# Remove default permissions of GITHUB_TOKEN for security
|
||||
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||
@ -48,7 +46,7 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@2e04019f4572c19684929a755da499f19a00b25b # v2.2.1
|
||||
uses: CodSpeedHQ/action@1dbf41f0ae41cebfe61e084e535aebe533409b4d # v2.3.0
|
||||
with:
|
||||
run: pnpm vitest bench
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
name: Release
|
||||
name: changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -15,7 +15,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.event_name != 'push' }}
|
||||
|
||||
jobs:
|
||||
update-changelog:
|
||||
update:
|
||||
if: github.repository_owner == 'nuxt' && !contains(github.event.head_commit.message, 'v3.')
|
||||
runs-on: ubuntu-latest
|
||||
|
2
.github/workflows/check-links.yml
vendored
2
.github/workflows/check-links.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
# Cache lychee results (e.g. to avoid hitting rate limits)
|
||||
- name: Restore lychee cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
|
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
@ -20,8 +20,6 @@ env:
|
||||
# 7 GiB by default on GitHub, setting to 6 GiB
|
||||
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
# install playwright binary manually (because pnpm only runs install script once)
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||
|
||||
# Remove default permissions of GITHUB_TOKEN for security
|
||||
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||
@ -85,19 +83,19 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
|
||||
uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
|
||||
with:
|
||||
languages: javascript
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
|
||||
uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
|
||||
with:
|
||||
category: "/language:javascript"
|
||||
|
||||
@ -124,7 +122,7 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
@ -220,34 +218,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
# Install playwright's binary under custom directory to cache
|
||||
- name: (non-windows) Set Playwright path and Get playwright version
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV
|
||||
PLAYWRIGHT_VERSION="$(pnpm ls --depth 0 --json -w playwright-core | jq --raw-output '.[0].devDependencies["playwright-core"].version')"
|
||||
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: (windows) Set Playwright path and Get playwright version
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
echo "PLAYWRIGHT_BROWSERS_PATH=$HOME\.cache\playwright-bin" >> $env:GITHUB_ENV
|
||||
$env:PLAYWRIGHT_VERSION="$(pnpm ls --depth 0 --json -w playwright-core | jq --raw-output '.[0].devDependencies["playwright-core"].version')"
|
||||
echo "PLAYWRIGHT_VERSION=$env:PLAYWRIGHT_VERSION" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Cache Playwright's binary
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
|
||||
with:
|
||||
key: ${{ runner.os }}-playwright-bin-v1-${{ env.PLAYWRIGHT_VERSION }}
|
||||
path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-bin-v1-
|
||||
|
||||
- name: Install Playwright
|
||||
run: pnpm playwright-core install chromium
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
@ -261,7 +236,7 @@ jobs:
|
||||
TEST_CONTEXT: ${{ matrix.context }}
|
||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }}
|
||||
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 # v4.0.2
|
||||
- uses: codecov/codecov-action@7afa10ed9b269c561c2336fd862446844e0cbf71 # v4.2.0
|
||||
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@ -296,7 +271,7 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
@ -335,7 +310,7 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
|
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -19,4 +19,4 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.1.3
|
||||
uses: actions/dependency-review-action@5bbc3ba658137598168acb2ab73b21c432dd411b # v4.2.5
|
||||
|
4
.github/workflows/release-pr.yml
vendored
4
.github/workflows/release-pr.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: release
|
||||
name: release-pr
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Ensure action is by maintainer
|
||||
uses: octokit/request-action@89697eb6635e52c6e1e5559f15b5c91ba5100cb0 # v2.1.9
|
||||
uses: octokit/request-action@d69a4d4369a61d4045c56451421ff93ee5e7bea0 # v2.2.0
|
||||
id: check_role
|
||||
with:
|
||||
route: GET /repos/nuxt/nuxt/collaborators/${{ github.event.comment.user.login }}
|
||||
|
40
.github/workflows/release.yml
vendored
Normal file
40
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
# Remove default permissions of GITHUB_TOKEN for security
|
||||
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.repository == 'nuxt/nuxt' && startsWith(github.event.head_commit.message, 'v3.')
|
||||
permissions:
|
||||
id-token: write
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org/"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build (stub)
|
||||
run: pnpm dev:prepare
|
||||
|
||||
- name: Release
|
||||
run: ./scripts/release.sh
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.RELEASE_NODE_AUTH_TOKEN}}
|
||||
NPM_CONFIG_PROVENANCE: true
|
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@ -66,6 +66,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
|
||||
uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
1
.npmrc
1
.npmrc
@ -1,3 +1,2 @@
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
shell-emulator=true
|
||||
|
52
README.md
52
README.md
@ -13,16 +13,32 @@
|
||||
Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.
|
||||
|
||||
It provides a number of features that make it easy to build fast, SEO-friendly, and scalable web applications, including:
|
||||
- Server-side rendering, Static Site Generation or Hybrid Rendering
|
||||
- Automatic routing with code-splitting
|
||||
- State management
|
||||
- SEO Optimization
|
||||
- Auto imports
|
||||
- Extensible with [180+ modules](https://nuxt.com/modules)
|
||||
- Server-side rendering, Static Site Generation, Hybrid Rendering and Edge-Side Rendering
|
||||
- Automatic routing with code-splitting and pre-fetching
|
||||
- Data fetching and state management
|
||||
- SEO Optimization and Meta tags definition
|
||||
- Auto imports of components, composables and utils
|
||||
- TypeScript with zero configuration
|
||||
- Go fullstack with our server/ directory
|
||||
- Extensible with [200+ modules](https://nuxt.com/modules)
|
||||
- Deployment to a variety of [hosting platforms](https://nuxt.com/deploy)
|
||||
- ...[and much more](https://nuxt.com) 🚀
|
||||
|
||||
## Getting Started
|
||||
### Table of Contents
|
||||
|
||||
- 🚀 [Getting Started](#getting-started)
|
||||
- 💻 [ Vue Development](#vue-development)
|
||||
- 📖 [Documentation](#documentation)
|
||||
- 🧩 [Modules](#modules)
|
||||
- ❤️ [Contribute](#contribute)
|
||||
- 🏠 [Local Development](#local-development)
|
||||
- ⛰️ [Nuxt 2](#nuxt-2)
|
||||
- 🔗 [Follow us](#follow-us)
|
||||
- ⚖️ [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## <a name="getting-started">🚀 Getting Started</a>
|
||||
|
||||
Use the following command to create a new starter project. This will create a starter project with all the necessary files and dependencies:
|
||||
|
||||
@ -30,9 +46,10 @@ Use the following command to create a new starter project. This will create a st
|
||||
npx nuxi@latest init <my-project>
|
||||
```
|
||||
|
||||
Discover also [nuxt.new](https://nuxt.new): Open a Nuxt starter on CodeSandbox, StackBlitz or locally to get up and running in a few seconds.
|
||||
> [!TIP]
|
||||
> Discover also [nuxt.new](https://nuxt.new): Open a Nuxt starter on CodeSandbox, StackBlitz or locally to get up and running in a few seconds.
|
||||
|
||||
## Vue Development
|
||||
## <a name="vue-development">💻 Vue Development</a>
|
||||
|
||||
Simple, intuitive and powerful, Nuxt lets you write Vue components in a way that makes sense. Every repetitive task is automated, so you can focus on writing your full-stack Vue application with confidence.
|
||||
|
||||
@ -54,7 +71,7 @@ useSeoMeta({
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
#app {
|
||||
background-color: #020420;
|
||||
color: #00DC82;
|
||||
@ -62,15 +79,15 @@ useSeoMeta({
|
||||
</style>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## <a name="documentation">📖 Documentation</a>
|
||||
|
||||
We highly recommend you take a look at the [Nuxt documentation](https://nuxt.com/docs) to level up. It’s a great resource for learning more about the framework. It covers everything from getting started to advanced topics.
|
||||
|
||||
## Modules
|
||||
## <a name="modules">🧩 Modules</a>
|
||||
|
||||
Discover our [list of modules](https://nuxt.com/modules) to supercharge your Nuxt project, created by the Nuxt team and community.
|
||||
|
||||
## Contribute
|
||||
## <a name="contribute">❤️ Contribute</a>
|
||||
|
||||
We invite you to contribute and help improve Nuxt 💚
|
||||
|
||||
@ -79,21 +96,20 @@ Here are a few ways you can get involved:
|
||||
- **Suggestions:** Have ideas to enhance Nuxt? We'd love to hear them! Check out the [contribution guide](https://nuxt.com/docs/community/contribution#creating-an-issue) to share your suggestions.
|
||||
- **Questions:** If you have questions or need assistance, the [getting help guide](https://nuxt.com/docs/community/getting-help) provides resources to help you out.
|
||||
|
||||
## Local Development
|
||||
## <a name="local-development">🏠 Local Development</a>
|
||||
|
||||
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
|
||||
## <a name="nuxt-2">⛰️ Nuxt 2</a>
|
||||
|
||||
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).
|
||||
|
||||
## Follow us
|
||||
## <a name="follow-us">🔗 Follow us</a>
|
||||
|
||||
<p valign="center">
|
||||
<a href="https://chat.nuxt.dev"><img width="20px" src="./.github/assets/discord.svg" alt="Discord"></a> <a href="https://twitter.nuxt.dev"><img width="20px" src="./.github/assets/twitter.svg" alt="Twitter"></a> <a href="https://github.nuxt.dev"><img width="20px" src="./.github/assets/github.svg" alt="GitHub"></a>
|
||||
</p>
|
||||
|
||||
## License
|
||||
## <a name="license">⚖️ License</a>
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
|
@ -15,8 +15,8 @@ Nuxt offers first-class support for end-to-end and unit testing of your Nuxt app
|
||||
In order to allow you to manage your other testing dependencies, `@nuxt/test-utils` ships with various optional peer dependencies. For example:
|
||||
|
||||
- you can choose between `happy-dom` and `jsdom` for a runtime Nuxt environment
|
||||
- you can choose between `vitest` and `jest` for end-to-end test runners
|
||||
- `playwright-core` is only required if you wish to use the built-in browser testing utilities
|
||||
- you can choose between `vitest`, `cucumber`, `jest` and `playwright` for end-to-end test runners
|
||||
- `playwright-core` is only required if you wish to use the built-in browser testing utilities (and are not using `@playwright/test` as your test runner)
|
||||
|
||||
::code-group
|
||||
```bash [yarn]
|
||||
@ -88,6 +88,7 @@ export default defineVitestConfig({
|
||||
// environmentOptions: {
|
||||
// nuxt: {
|
||||
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
|
||||
// domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
|
||||
// overrides: {
|
||||
// // other Nuxt config you want to pass
|
||||
// }
|
||||
@ -387,7 +388,7 @@ await setup({
|
||||
|
||||
## End-To-End Testing
|
||||
|
||||
For end-to-end testing, we support [Vitest](https://github.com/vitest-dev/vitest) and [Jest](https://jestjs.io) as test runners.
|
||||
For end-to-end testing, we support [Vitest](https://github.com/vitest-dev/vitest), [Jest](https://jestjs.io), [Cucumber](https://cucumber.io/) and [Playwright](https://playwright.dev/) as test runners.
|
||||
|
||||
### Setup
|
||||
|
||||
@ -453,7 +454,7 @@ Please use the options below for the `setup` method.
|
||||
- `type`: The type of browser to launch - either `chromium`, `firefox` or `webkit`
|
||||
- `launch`: `object` of options that will be passed to playwright when launching the browser. See [full API reference](https://playwright.dev/docs/api/class-browsertype#browser-type-launch).
|
||||
- `runner`: Specify the runner for the test suite. Currently, [Vitest](https://vitest.dev) is recommended.
|
||||
- Type: `'vitest' | 'jest'`
|
||||
- Type: `'vitest' | 'jest' | 'cucumber'`
|
||||
- Default: `'vitest'`
|
||||
|
||||
### APIs
|
||||
@ -492,11 +493,11 @@ const pageUrl = url('/page')
|
||||
|
||||
### Testing in a Browser
|
||||
|
||||
We provide built-in support using Playwright within `@nuxt/test-utils`, but you can also use other test runners for end-to-end browser testing.
|
||||
We provide built-in support using Playwright within `@nuxt/test-utils`, either programmatically or via the Playwright test runner.
|
||||
|
||||
#### `createPage(url)`
|
||||
|
||||
You can create a configured Playwright browser instance, and (optionally) point it at a path from the running server. You can find out more about the API methods available from [in the Playwright documentation](https://playwright.dev/docs/api/class-page).
|
||||
Within `vitest`, `jest` or `cucumber`, you can create a configured Playwright browser instance with `createPage`, and (optionally) point it at a path from the running server. You can find out more about the API methods available from [in the Playwright documentation](https://playwright.dev/docs/api/class-page).
|
||||
|
||||
```ts twoslash
|
||||
import { createPage } from '@nuxt/test-utils/e2e'
|
||||
@ -504,3 +505,70 @@ import { createPage } from '@nuxt/test-utils/e2e'
|
||||
const page = await createPage('/page')
|
||||
// you can access all the Playwright APIs from the `page` variable
|
||||
```
|
||||
|
||||
#### Testing with Playwright Test Runner
|
||||
|
||||
We also provide first-class support for testing Nuxt within [the Playwright test runner](https://playwright.dev/docs/intro).
|
||||
|
||||
::code-group
|
||||
```bash [yarn]
|
||||
yarn add --dev @playwright/test @nuxt/test-utils
|
||||
```
|
||||
```bash [npm]
|
||||
npm i --save-dev @playwright/test @nuxt/test-utils
|
||||
```
|
||||
```bash [pnpm]
|
||||
pnpm add -D @playwright/test @nuxt/test-utils
|
||||
```
|
||||
```bash [bun]
|
||||
bun add --dev @playwright/test @nuxt/test-utils
|
||||
```
|
||||
::
|
||||
|
||||
You can provide global Nuxt configuration, with the same configuration details as the `setup()` function mentioned earlier in this section.
|
||||
|
||||
```ts [playwright.config.ts]
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import type { ConfigOptions } from '@nuxt/test-utils/playwright'
|
||||
|
||||
export default defineConfig<ConfigOptions>({
|
||||
use: {
|
||||
nuxt: {
|
||||
rootDir: fileURLToPath(new URL('.', import.meta.url))
|
||||
}
|
||||
},
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
::read-more{title="See full example config" to="https://github.com/nuxt/test-utils/blob/main/examples/app-playwright/playwright.config.ts" target="_blank"}
|
||||
::
|
||||
|
||||
Your test file should then use `expect` and `test` directly from `@nuxt/test-utils/playwright`:
|
||||
|
||||
```ts [tests/example.test.ts]
|
||||
import { expect, test } from '@nuxt/test-utils/playwright'
|
||||
|
||||
test('test', async ({ page, goto }) => {
|
||||
await goto('/', { waitUntil: 'hydration' })
|
||||
await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
|
||||
})
|
||||
```
|
||||
|
||||
You can alternatively configure your Nuxt server directly within your test file:
|
||||
|
||||
```ts [tests/example.test.ts]
|
||||
import { expect, test } from '@nuxt/test-utils/playwright'
|
||||
|
||||
test.use({
|
||||
nuxt: {
|
||||
rootDir: fileURLToPath(new URL('..', import.meta.url))
|
||||
}
|
||||
})
|
||||
|
||||
test('test', async ({ page, goto }) => {
|
||||
await goto('/', { waitUntil: 'hydration' })
|
||||
await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
|
||||
})
|
||||
```
|
||||
|
@ -22,7 +22,7 @@ Start with one of our starters and themes directly by opening [nuxt.new](https:/
|
||||
#### Prerequisites
|
||||
|
||||
- **Node.js** - [`v18.0.0`](https://nodejs.org/en) or newer
|
||||
- **Text editor** - We recommend [Visual Studio Code](https://code.visualstudio.com/) with the [Volar Extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
||||
- **Text editor** - We recommend [Visual Studio Code](https://code.visualstudio.com/) with the [official Vue extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously known as Volar)
|
||||
- **Terminal** - In order to run Nuxt commands
|
||||
|
||||
::note
|
||||
@ -30,17 +30,6 @@ Start with one of our starters and themes directly by opening [nuxt.new](https:/
|
||||
:summary[Additional notes for an optimal setup:]
|
||||
- **Node.js**: Make sure to use an even numbered version (18, 20, etc)
|
||||
- **Nuxtr**: Install the community-developed [Nuxtr extension](https://marketplace.visualstudio.com/items?itemName=Nuxtr.nuxtr-vscode)
|
||||
- **Volar**: Either enable [**Take Over Mode**](https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode) (recommended) or add the [TypeScript Vue Plugin](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin)
|
||||
|
||||
If you have enabled **Take Over Mode** or installed the **TypeScript Vue Plugin (Volar)**, you can disable generating the shim for `*.vue` files in your [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
typescript: {
|
||||
shim: false
|
||||
}
|
||||
})
|
||||
```
|
||||
::
|
||||
::
|
||||
|
||||
|
@ -243,7 +243,7 @@ Nuxt uses Vite by default. If you wish to use webpack instead, refer to each pre
|
||||
|
||||
## Single File Components (SFC) Styling
|
||||
|
||||
One of the best things about Vue and SFC is how great it is at naturally dealing with styling. You can directly write CSS or preprocessor code in the style block of your components file, therefore you will have fantastic developer experience without having to use something like CSS-in-JS. However if you wish to use CSS-in-JS, you can find 3rd party libraries and modules that support it, such as [pinceau](https://pinceau.dev).
|
||||
One of the best things about Vue and SFC is how great it is at naturally dealing with styling. You can directly write CSS or preprocessor code in the style block of your components file, therefore you will have fantastic developer experience without having to use something like CSS-in-JS. However if you wish to use CSS-in-JS, you can find 3rd party libraries and modules that support it, such as [pinceau](https://github.com/Tahul/pinceau).
|
||||
|
||||
You can refer to the [Vue docs](https://vuejs.org/api/sfc-css-features.html) for a comprehensive reference about styling components in SFC.
|
||||
|
||||
@ -421,7 +421,7 @@ For proper syntax highlighting in SFC, you can use the postcss lang attribute.
|
||||
|
||||
```vue
|
||||
<style lang="postcss">
|
||||
/* Write stylus here */
|
||||
/* Write postcss here */
|
||||
</style>
|
||||
```
|
||||
|
||||
@ -430,7 +430,7 @@ By default, Nuxt comes with the following plugins already pre-configured:
|
||||
- [postcss-import](https://github.com/postcss/postcss-import): Improves the `@import` rule
|
||||
- [postcss-url](https://github.com/postcss/postcss-url): Transforms `url()` statements
|
||||
- [autoprefixer](https://github.com/postcss/autoprefixer): Automatically adds vendor prefixes
|
||||
- [cssnano](https://cssnano.co): Minification and purge
|
||||
- [cssnano](https://cssnano.github.io/cssnano): Minification and purge
|
||||
|
||||
## Leveraging Layouts For Multiple Styles
|
||||
|
||||
@ -458,14 +458,14 @@ Use different styles for different layouts.
|
||||
|
||||
Nuxt isn't opinionated when it comes to styling and provides you with a wide variety of options. You can use any styling tool that you want, such as popular libraries like [UnoCSS](https://unocss.dev) or [Tailwind CSS](https://tailwindcss.com).
|
||||
|
||||
The community and the Nuxt team have developed plenty of Nuxt modules to makes the integration easier.
|
||||
The community and the Nuxt team have developed plenty of Nuxt modules to make the integration easier.
|
||||
You can discover them on the [modules section](/modules) of the website.
|
||||
Here are a few modules to help you get started:
|
||||
|
||||
- [UnoCSS](/modules/unocss): Instant on-demand atomic CSS engine
|
||||
- [Tailwind CSS](/modules/tailwindcss): Utility-first CSS framework
|
||||
- [Fontaine](https://github.com/nuxt-modules/fontaine): Font metric fallback
|
||||
- [Pinceau](https://pinceau.dev): Adaptable styling framework
|
||||
- [Pinceau](https://github.com/Tahul/pinceau): Adaptable styling framework
|
||||
- [Nuxt UI](https://ui.nuxt.com): A UI Library for Modern Web Apps
|
||||
- [Panda CSS](https://panda-css.com/docs/installation/nuxt): CSS-in-JS engine that generates atomic CSS at build time
|
||||
|
||||
|
@ -6,7 +6,7 @@ navigation.icon: i-ph-file-search-duotone
|
||||
|
||||
## Defaults
|
||||
|
||||
Out-of-the-box, Nuxt provides sane defaults, which you can override if needed.
|
||||
Out-of-the-box, Nuxt provides sensible defaults, which you can override if needed.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
@ -174,7 +174,7 @@ You can use the `titleTemplate` option to provide a dynamic template for customi
|
||||
|
||||
The `titleTemplate` can either be a string, where `%s` is replaced with the title, or a function.
|
||||
|
||||
If you want to use a function (for full control), then this cannot be set in your `nuxt.config`, and it is recommended instead to set it within your `app.vue` file, where it will apply to all pages on your site:
|
||||
If you want to use a function (for full control), then this cannot be set in your `nuxt.config`. It is recommended instead to set it within your `app.vue` file where it will apply to all pages on your site:
|
||||
|
||||
::code-group
|
||||
|
||||
|
@ -60,7 +60,7 @@ This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/u
|
||||
|
||||
## `$fetch`
|
||||
|
||||
Nuxt includes the `ofetch` library, and is auto-imported as the `$fetch` alias globally across your application. It's what `useFetch` uses behind the scenes.
|
||||
Nuxt includes the [ofetch](https://github.com/unjs/ofetch) library, and is auto-imported as the `$fetch` alias globally across your application. It's what `useFetch` uses behind the scenes.
|
||||
|
||||
```vue twoslash [pages/todos.vue]
|
||||
<script setup lang="ts">
|
||||
@ -150,6 +150,7 @@ Read more about `useAsyncData`.
|
||||
- `data`: the result of the asynchronous function that is passed in.
|
||||
- `pending`: a boolean indicating whether the data is still being fetched.
|
||||
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
||||
- `clear`: a function that can be used to set `data` to undefined, set `error` to `null`, set `pending` to `false`, set `status` to `idle`, and mark any currently pending requests as cancelled.
|
||||
- `error`: an error object if the data fetching failed.
|
||||
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
|
||||
|
||||
@ -294,6 +295,21 @@ The `execute` function is an alias for `refresh` that works in exactly the same
|
||||
To globally refetch or invalidate cached data, see [`clearNuxtData`](/docs/api/utils/clear-nuxt-data) and [`refreshNuxtData`](/docs/api/utils/refresh-nuxt-data).
|
||||
::
|
||||
|
||||
#### Clear
|
||||
|
||||
If you want to clear the data provided, for whatever reason, without needing to know the specific key to pass to `clearNuxtData`, you can use the `clear` function provided by the composables.
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
const { data, clear } = await useFetch('/api/users')
|
||||
|
||||
const route = useRoute()
|
||||
watch(() => route.path, (path) => {
|
||||
if (path === '/') clear()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Watch
|
||||
|
||||
To re-run your fetching function each time other reactive values in your application change, use the `watch` option. You can use it for one or multiple _watchable_ elements.
|
||||
|
@ -130,12 +130,12 @@ export const useLocale = () => {
|
||||
|
||||
export const useDefaultLocale = (fallback = 'en-US') => {
|
||||
const locale = ref(fallback)
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
|
||||
if (reqLocale) {
|
||||
locale.value = reqLocale
|
||||
}
|
||||
} else if (process.client) {
|
||||
} else if (import.meta.client) {
|
||||
const navLang = navigator.language
|
||||
if (navLang) {
|
||||
locale.value = navLang
|
||||
@ -192,7 +192,6 @@ const date = useLocaleDate(new Date('2016-10-26'))
|
||||
By using [auto-imported composables](/docs/guide/directory-structure/composables) we can define global type-safe states and import them across the app.
|
||||
|
||||
```ts twoslash [composables/states.ts]
|
||||
export const useCounter = () => useState<number>('counter', () => 0)
|
||||
export const useColor = () => useState<string>('color', () => 'pink')
|
||||
```
|
||||
|
||||
|
@ -7,15 +7,15 @@ navigation.icon: i-ph-bug-beetle-duotone
|
||||
Nuxt 3 is a full-stack framework, which means there are several sources of unpreventable user runtime errors that can happen in different contexts:
|
||||
|
||||
- Errors during the Vue rendering lifecycle (SSR & CSR)
|
||||
- Errors during Nitro server lifecycle ([`server/`](/docs/guide/directory-structure/server) directory)
|
||||
- Server and client startup errors (SSR + CSR)
|
||||
- Errors during Nitro server lifecycle ([`server/`](/docs/guide/directory-structure/server) directory)
|
||||
- Errors downloading JS chunks
|
||||
|
||||
::tip
|
||||
**SSR** stands for **Server-Side Rendering** and **CSR** for **Client-Side Rendering**.
|
||||
::
|
||||
|
||||
## Vue Rendering Lifecycle
|
||||
## Vue Errors
|
||||
|
||||
You can hook into Vue errors using [`onErrorCaptured`](https://vuejs.org/api/composition-api-lifecycle.html#onerrorcaptured).
|
||||
|
||||
@ -51,7 +51,7 @@ This includes:
|
||||
- mounting the app (on client-side), though you should handle this case with `onErrorCaptured` or with `vue:error`
|
||||
- processing the `app:mounted` hook
|
||||
|
||||
## Nitro Server Lifecycle
|
||||
## Nitro Server Errors
|
||||
|
||||
You cannot currently define a server-side handler for these errors, but can render an error page, see the [Render an Error Page](#error-page) section.
|
||||
|
||||
|
@ -83,9 +83,9 @@ export default defineNuxtConfig({
|
||||
Learn about all available route rules are available to customize the rendering mode of your routes.
|
||||
::
|
||||
|
||||
In addition, there are some route rules (for example, `ssr` and `experimentalNoScripts`) that are Nuxt specific to change the behavior when rendering your pages to HTML.
|
||||
In addition, there are some route rules (for example, `ssr`, `appMiddleware`, and `experimentalNoScripts`) that are Nuxt specific to change the behavior when rendering your pages to HTML.
|
||||
|
||||
Some route rules (`redirect` and `prerender`) also affect client-side behavior.
|
||||
Some route rules (`appMiddleware`, `redirect` and `prerender`) also affect client-side behavior.
|
||||
|
||||
Nitro is used to build the app for server side rendering, as well as pre-rendering.
|
||||
|
||||
|
@ -14,12 +14,13 @@ One of the core features of Nuxt 3 is the layers and extending support. You can
|
||||
- Create Nuxt module presets
|
||||
- Share standard setup across projects
|
||||
- Create Nuxt themes
|
||||
- Enhance code organization by implementing a modular architecture and support Domain-Driven Design (DDD) pattern in large scale projects.
|
||||
|
||||
## Usage
|
||||
|
||||
You can extend a layer by adding the [extends](/docs/api/nuxt-config#extends) property to the [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
extends: [
|
||||
'../base', // Extend from a local layer
|
||||
@ -29,6 +30,19 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
You can also pass an authentication token if you are extending from a private GitHub repository:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
extends: [
|
||||
// per layer configuration
|
||||
['github:my-themes/private-awesome', { auth: process.env.GITHUB_TOKEN }]
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
Nuxt uses [unjs/c12](https://c12.unjs.io) and [unjs/giget](https://giget.unjs.io) for extending remote layers. Check the documentation for more information and all available options.
|
||||
|
||||
::read-more{to="/docs/guide/going-further/layers"}
|
||||
Read more about layers in the **Layer Author Guide**.
|
||||
::
|
||||
|
@ -84,7 +84,7 @@ export const useMyComposable = () => {
|
||||
```ts twoslash [composables/example.ts]
|
||||
export const useMyComposable = () => {
|
||||
// Because your composable is called in the right place in the lifecycle,
|
||||
// useRuntimeConfig will also work
|
||||
// useRuntimeConfig will work here
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
// ...
|
||||
|
@ -109,10 +109,11 @@ The different properties you can use are the following:
|
||||
- `ssr: boolean`{lang=ts} - Disables server-side rendering for sections of your app and make them SPA-only with `ssr: false`
|
||||
- `cors: boolean`{lang=ts} - Automatically adds cors headers with `cors: true` - you can customize the output by overriding with `headers`
|
||||
- `headers: object`{lang=ts} - Add specific headers to sections of your site - for example, your assets
|
||||
- `swr: number|boolean`{lang=ts} - Add cache headers to the server response and cache it on the server or reverse proxy for a configurable TTL (time to live). The `node-server` preset of Nitro is able to cache the full response. When the TTL expired, the cached response will be sent while the page will be regenerated in the background. If true is used, a `stale-while-revalidate` header is added without a MaxAge.
|
||||
- `isr: number|boolean`{lang=ts} - The behavior is the same as `swr` except that we are able to add the response to the CDN cache on platforms that support this (currently Netlify or Vercel). If `true` is used, the content persists until the next deploy inside the CDN.
|
||||
- `prerender:boolean`{lang=ts} - Prerenders routes at build time and includes them in your build as static assets
|
||||
- `swr: number | boolean`{lang=ts} - Add cache headers to the server response and cache it on the server or reverse proxy for a configurable TTL (time to live). The `node-server` preset of Nitro is able to cache the full response. When the TTL expired, the cached response will be sent while the page will be regenerated in the background. If true is used, a `stale-while-revalidate` header is added without a MaxAge.
|
||||
- `isr: number | boolean`{lang=ts} - The behavior is the same as `swr` except that we are able to add the response to the CDN cache on platforms that support this (currently Netlify or Vercel). If `true` is used, the content persists until the next deploy inside the CDN.
|
||||
- `prerender: boolean`{lang=ts} - Prerenders routes at build time and includes them in your build as static assets
|
||||
- `experimentalNoScripts: boolean`{lang=ts} - Disables rendering of Nuxt scripts and JS resource hints for sections of your site.
|
||||
- `appMiddleware: string | string[] | Record<string, boolean>`{lang=ts} - Allows you to define middleware that should or should not run for page paths within the Vue app part of your application (that is, not your Nitro routes)
|
||||
|
||||
Whenever possible, route rules will be automatically applied to the deployment platform's native rules for optimal performances (Netlify and Vercel are currently supported).
|
||||
|
||||
|
@ -9,22 +9,26 @@ By default, Nuxt doesn't check types when you run [`nuxi dev`](/docs/api/command
|
||||
|
||||
To enable type-checking at build or development time, install `vue-tsc` and `typescript` as development dependency:
|
||||
|
||||
::alert{type="warning"}
|
||||
You may experience issues with the latest `vue-tsc` and `vite-plugin-checker`, used internally when type checking. For now, you may need to stay on v1 of `vue-tsc`, and follow these upstream issues for updates: [fi3ework/vite-plugin-checker#306](https://github.com/fi3ework/vite-plugin-checker/issues/306) and [vuejs/language-tools#3969](https://github.com/vuejs/language-tools/issues/3969).
|
||||
::
|
||||
|
||||
::code-group
|
||||
|
||||
```bash [yarn]
|
||||
yarn add --dev vue-tsc typescript
|
||||
yarn add --dev vue-tsc@^1 typescript
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install --save-dev vue-tsc typescript
|
||||
npm install --save-dev vue-tsc@^1 typescript
|
||||
```
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add -D vue-tsc typescript
|
||||
pnpm add -D vue-tsc@^1 typescript
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add -D vue-tsc typescript
|
||||
bun add -D vue-tsc@^1 typescript
|
||||
```
|
||||
|
||||
::
|
||||
|
@ -244,6 +244,10 @@ This feature only works with Nuxt auto-imports and `#components` imports. Explic
|
||||
`.client` components are rendered only after being mounted. To access the rendered template using `onMounted()`, add `await nextTick()` in the callback of the `onMounted()` hook.
|
||||
::
|
||||
|
||||
::read-more{to="/docs/api/components/client-only"}
|
||||
You can also achieve a similar result with the `<ClientOnly>` component.
|
||||
::
|
||||
|
||||
## Server Components
|
||||
|
||||
Server components allow server-rendering individual components within your client-side apps. It's possible to use server components within Nuxt, even if you are generating a static site. That makes it possible to build complex sites that mix dynamic components, server-rendered HTML and even static chunks of markup.
|
||||
@ -295,6 +299,14 @@ Now you can register server-only components with the `.server` suffix and use th
|
||||
|
||||
Server-only components use [`<NuxtIsland>`](/docs/api/components/nuxt-island) under the hood, meaning that `lazy` prop and `#fallback` slot are both passed down to it.
|
||||
|
||||
::alert{type=warning}
|
||||
Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)
|
||||
::
|
||||
|
||||
::alert{type=warning}
|
||||
Most features for server-only components and island components, such as slots and client components, are only available for single file components.
|
||||
::
|
||||
|
||||
#### Client components within server components
|
||||
|
||||
::alert{type=info}
|
||||
@ -314,7 +326,7 @@ You can partially hydrate a component by setting a `nuxt-client` attribute on th
|
||||
```
|
||||
|
||||
::alert{type=info}
|
||||
This only works within a server component. Slots for client components are not available yet.
|
||||
This only works within a server component. Slots for client components are working only with `experimental.componentIsland.selectiveClient` set to `'deep'` and since they are rendered server-side, they are not interactive once client-side.
|
||||
::
|
||||
|
||||
#### Server Component Context
|
||||
@ -353,89 +365,12 @@ In this case, the `.server` + `.client` components are two 'halves' of a compone
|
||||
</template>
|
||||
```
|
||||
|
||||
## `<ClientOnly>` Component
|
||||
## Built-In Nuxt Components
|
||||
|
||||
Nuxt provides the [`<ClientOnly>`](/docs/api/components/client-only) component for purposely rendering a component only on client side.
|
||||
There are a number of components that Nuxt provides, including `<ClientOnly>` and `<DevOnly>`. You can read more about them in the API documentation.
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<ClientOnly>
|
||||
<!-- this component will only be rendered on client-side -->
|
||||
<Comments />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Use a slot as fallback until `<ClientOnly>` is mounted on client side.
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<!-- This renders the "span" element on the server side -->
|
||||
<ClientOnly fallbackTag="span">
|
||||
<!-- this component will only be rendered on client side -->
|
||||
<Comments />
|
||||
<template #fallback>
|
||||
<!-- this will be rendered on server side -->
|
||||
<p>Loading comments...</p>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
<!-- TODO: Add back after passing treeshakeClientOnly experiment -->
|
||||
<!--
|
||||
::note
|
||||
Make sure not to _nest_ `<ClientOnly>` components or other client-only components. Nuxt performs an optimization to remove the contents of these components from the server-side render, which can break in this case.
|
||||
::read-more{to="/docs/api"}
|
||||
::
|
||||
-->
|
||||
|
||||
## `<DevOnly>` Component
|
||||
|
||||
Nuxt provides the `<DevOnly>` component to render a component only during development.
|
||||
|
||||
The content will not be included in production builds and tree-shaken.
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<DevOnly>
|
||||
<!-- this component will only be rendered during development -->
|
||||
<LazyDebugBar />
|
||||
|
||||
<!-- if you ever require to have a replacement during production -->
|
||||
<!-- be sure to test these using `nuxt preview` -->
|
||||
<template #fallback>
|
||||
<div><!-- empty div for flex.justify-between --></div>
|
||||
</template>
|
||||
</DevOnly>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## `<NuxtClientFallback>` Component
|
||||
|
||||
Nuxt provides the `<NuxtClientFallback>` component to render its content on the client if any of its children trigger an error in SSR.
|
||||
You can specify a `fallbackTag` to make it render a specific tag if it fails to render on the server.
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<!-- this component will be rendered on client-side -->
|
||||
<NuxtClientFallback fallback-tag="span">
|
||||
<Comments />
|
||||
<BrokeInSSR />
|
||||
</NuxtClientFallback>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Library Authors
|
||||
|
||||
|
@ -125,12 +125,12 @@ However, if you want to avoid this behaviour you can do so:
|
||||
```ts twoslash [middleware/example.ts]
|
||||
export default defineNuxtRouteMiddleware(to => {
|
||||
// skip middleware on server
|
||||
if (process.server) return
|
||||
if (import.meta.server) return
|
||||
// skip middleware on client side entirely
|
||||
if (process.client) return
|
||||
if (import.meta.client) return
|
||||
// or only skip middleware on initial client load
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (process.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return
|
||||
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return
|
||||
})
|
||||
```
|
||||
|
||||
|
@ -56,3 +56,7 @@ modules/
|
||||
```
|
||||
|
||||
:read-more{to="/docs/guide/going-further/modules"}
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/creating-your-first-module-from-scratch" target="_blank"}
|
||||
Watch Vue School video about Nuxt private modules.
|
||||
::
|
||||
|
@ -57,7 +57,7 @@ If you are using [`app.vue`](/docs/guide/directory-structure/app), make sure to
|
||||
</template>
|
||||
```
|
||||
|
||||
Pages **must have a single root element** to allow [route transitions](/docs/getting-started/transitions) between pages, HTML comments are considered elements as well.
|
||||
Pages **must have a single root element** to allow [route transitions](/docs/getting-started/transitions) between pages. HTML comments are considered elements as well.
|
||||
|
||||
This means that when the route is server-rendered, or statically generated, you will be able to see its contents correctly, but when you navigate towards that route during client-side navigation the transition between routes will fail and you'll see that the route will not be rendered.
|
||||
|
||||
@ -357,12 +357,16 @@ function navigate(){
|
||||
</script>
|
||||
```
|
||||
|
||||
## Client-Only Pages
|
||||
|
||||
You can define a page as [client only](/docs/guide/directory-structure/components#client-components) by giving it a `.client.vue` suffix. None of the content of this page will be rendered on the server.
|
||||
|
||||
## Server-Only Pages
|
||||
|
||||
You can define a page as [server only](/docs/guide/directory-structure/components#server-components) by giving it a `.server.vue` suffix. While you will be able to navigate to the page using client-side navigation, controlled by `vue-router`, it will be rendered with a server component automatically, meaning the code required to render the page will not be in your client-side bundle.
|
||||
|
||||
::note
|
||||
You will also need to enable `experimental.componentIslands` in order to make this possible.
|
||||
::alert{type=warning}
|
||||
Server-only pages must have a single root element. (HTML comments are considered elements as well.)
|
||||
::
|
||||
|
||||
## Custom Routing
|
||||
|
@ -78,7 +78,7 @@ export default defineNuxtPlugin({
|
||||
|
||||
::note
|
||||
If you are using the object-syntax, the properties may be statically analyzed in future to produce a more optimized build. So you should not define them at runtime. :br
|
||||
For example, setting `enforce: process.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
|
||||
For example, setting `enforce: import.meta.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
|
||||
::
|
||||
|
||||
## Registration Order
|
||||
|
@ -99,7 +99,7 @@ Nuxt uses a custom merging strategy for the `AppConfig` within [the layers](/doc
|
||||
This strategy is implemented using a [Function Merger](https://github.com/unjs/defu#function-merger), which allows defining a custom merging strategy for every key in `app.config` that has an array as value.
|
||||
|
||||
::note
|
||||
The Function Merger should only be used in the base `app.config` of your application.
|
||||
The function merger can only be used in the extended layers and not the main `app.config` in project.
|
||||
::
|
||||
|
||||
Here's an example of how you can use:
|
||||
|
@ -98,7 +98,7 @@ The behavior is different between the client-side and server-side:
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
console.log('Runtime config:', config)
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
console.log('API secret:', config.apiSecret)
|
||||
}
|
||||
</script>
|
||||
@ -164,3 +164,7 @@ declare module 'nuxt/schema' {
|
||||
// It is always important to ensure you import/export something when augmenting a type
|
||||
export {}
|
||||
```
|
||||
|
||||
::note
|
||||
`nuxt/schema` is provided as a convenience for end-users to access the version of the schema used by Nuxt in their project. Module authors should instead augment `@nuxt/schema`.
|
||||
::
|
||||
|
@ -30,6 +30,10 @@ This will create a `my-module` project with all the boilerplate necessary to dev
|
||||
|
||||
Learn how to perform basic tasks with the module starter.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/navigating-the-official-starter-template" target="_blank"}
|
||||
Watch Vue School video about Nuxt module starter template.
|
||||
::
|
||||
|
||||
#### How to Develop
|
||||
|
||||
While your module source code lives inside the `src` directory, in most cases, to develop a module, you need a Nuxt application. That's what the `playground` directory is about. It's a Nuxt application you can tinker with that is already configured to run with your module.
|
||||
@ -255,6 +259,10 @@ export default defineNuxtModule({
|
||||
|
||||
When you need to handle more complex configuration alterations, you should consider using [defu](https://github.com/unjs/defu).
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/extending-and-altering-nuxt-configuration-and-options" target="_blank"}
|
||||
Watch Vue School video about altering Nuxt configuration.
|
||||
::
|
||||
|
||||
#### Exposing Options to Runtime
|
||||
|
||||
Because modules aren't part of the application runtime, their options aren't either. However, in many cases, you might need access to some of these module options within your runtime code. We recommend exposing the needed config using Nuxt's [`runtimeConfig`](/docs/api/nuxt-config#runtimeconfig).
|
||||
@ -288,6 +296,10 @@ Be careful not to expose any sensitive module configuration on the public runtim
|
||||
|
||||
:read-more{to="/docs/guide/going-further/runtime-config"}
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/passing-and-exposing-module-options" target="_blank"}
|
||||
Watch Vue School video about passing and exposing Nuxt module options.
|
||||
::
|
||||
|
||||
#### Injecting Plugins With `addPlugin`
|
||||
|
||||
Plugins are a common way for a module to add runtime logic. You can use the `addPlugin` utility to register them from your module.
|
||||
@ -511,6 +523,10 @@ export default defineNuxtModule({
|
||||
|
||||
:read-more{to="/docs/api/advanced/hooks"}
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/nuxt-lifecycle-hooks" target="_blank"}
|
||||
Watch Vue School video about using Nuxt lifecycle hooks in modules.
|
||||
::
|
||||
|
||||
::note
|
||||
**Module cleanup**
|
||||
:br
|
||||
@ -733,6 +749,10 @@ The module starter comes with a default set of tools and configurations (e.g. ES
|
||||
|
||||
[Nuxt Module ecosystem](/modules) represents more than 15 million monthly NPM downloads and provides extended functionalities and integrations with all sort of tools. You can be part of this ecosystem!
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/exploring-nuxt-modules-ecosystem-and-module-types" target="_blank"}
|
||||
Watch Vue School video about Nuxt module types.
|
||||
::
|
||||
|
||||
### Module Types
|
||||
|
||||
**Official modules** are modules prefixed (scoped) with `@nuxt/` (e.g. [`@nuxt/content`](https://content.nuxtjs.org)). They are made and maintained actively by the Nuxt team. Like with the framework, contributions from the community are more than welcome to help make them better!
|
||||
|
@ -105,7 +105,7 @@ export default defineNuxtConfig({
|
||||
const resolver = createResolver(import.meta.url)
|
||||
// add a route
|
||||
files.push({
|
||||
path: resolver.resolve('./runtime/app/router-options')
|
||||
path: resolver.resolve('./runtime/app/router-options'),
|
||||
optional: true
|
||||
})
|
||||
}
|
||||
@ -172,6 +172,6 @@ import { createMemoryHistory } from 'vue-router'
|
||||
|
||||
export default <RouterConfig> {
|
||||
// https://router.vuejs.org/api/interfaces/routeroptions.html
|
||||
history: base => process.client ? createMemoryHistory(base) : null /* default */
|
||||
history: base => import.meta.client ? createMemoryHistory(base) : null /* default */
|
||||
}
|
||||
```
|
||||
|
@ -38,6 +38,10 @@ It is possible to debug your Nuxt app in your IDE while you are developing it.
|
||||
|
||||
You may need to update the config below with a path to your web browser. For more information, visit the [VS Code documentation about debug configuration](https://go.microsoft.com/fwlink/?linkid=830387).
|
||||
|
||||
::important
|
||||
If you use `pnpm`, you will need to have `nuxi` installed as a devDependency for the configuration below to work.
|
||||
::
|
||||
|
||||
```json5
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
|
@ -8,7 +8,11 @@ links:
|
||||
size: xs
|
||||
---
|
||||
|
||||
The `<ClientOnly>` component renders its slot only in client-side. To import a component only on the client, register the component in a client-side only plugin.
|
||||
The `<ClientOnly>` component is used for purposely rendering a component only on client side.
|
||||
|
||||
::note
|
||||
The content of the default slot will be tree-shaken out of the server build. (This does mean that any CSS used by components within it may not be inlined when rendering the initial HTML.)
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
@ -19,6 +23,7 @@ The `<ClientOnly>` component renders its slot only in client-side. To import a c
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<!-- The <Comment> component will only be rendered on client-side -->
|
||||
<ClientOnly fallback-tag="span" fallback="Loading comments...">
|
||||
<Comment />
|
||||
</ClientOnly>
|
||||
@ -28,14 +33,16 @@ The `<ClientOnly>` component renders its slot only in client-side. To import a c
|
||||
|
||||
## Slots
|
||||
|
||||
- `#fallback`: specify a content to be displayed server-side.
|
||||
- `#fallback`: specify a content to be rendered on the server and displayed until `<ClientOnly>` is mounted in the browser.
|
||||
|
||||
```vue
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<ClientOnly>
|
||||
<!-- ... -->
|
||||
<!-- This renders the "span" element on the server side -->
|
||||
<ClientOnly fallbackTag="span">
|
||||
<!-- this component will only be rendered on client side -->
|
||||
<Comments />
|
||||
<template #fallback>
|
||||
<!-- this will be rendered on server side -->
|
||||
<p>Loading comments...</p>
|
||||
|
51
docs/3.api/1.components/1.dev-only.md
Normal file
51
docs/3.api/1.components/1.dev-only.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: '<DevOnly>'
|
||||
description: Render components only during development with the <DevOnly> component.
|
||||
links:
|
||||
- label: Source
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/components/dev-only.ts
|
||||
size: xs
|
||||
---
|
||||
|
||||
Nuxt provides the `<DevOnly>` component to render a component only during development.
|
||||
|
||||
The content will not be included in production builds.
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<DevOnly>
|
||||
<!-- this component will only be rendered during development -->
|
||||
<LazyDebugBar />
|
||||
|
||||
<!-- if you ever require to have a replacement during production -->
|
||||
<!-- be sure to test these using `nuxt preview` -->
|
||||
<template #fallback>
|
||||
<div><!-- empty div for flex.justify-between --></div>
|
||||
</template>
|
||||
</DevOnly>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Slots
|
||||
|
||||
- `#fallback`: if you ever require to have a replacement during production.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<DevOnly>
|
||||
<!-- this component will only be rendered during development -->
|
||||
<LazyDebugBar />
|
||||
<!-- be sure to test these using `nuxt preview` -->
|
||||
<template #fallback>
|
||||
<div><!-- empty div for flex.justify-between --></div>
|
||||
</template>
|
||||
</DevOnly>
|
||||
</div>
|
||||
</template>
|
||||
```
|
@ -12,10 +12,25 @@ links:
|
||||
size: xs
|
||||
---
|
||||
|
||||
Nuxt provides the `<NuxtClientFallback>` component to render its content on the client if any of its children trigger an error in SSR.
|
||||
|
||||
::note{to="/docs/guide/going-further/experimental-features#clientfallback"}
|
||||
This component is experimental and in order to use it you must enable the `experimental.clientFallback` option in your `nuxt.config`.
|
||||
::
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<Sidebar />
|
||||
<!-- this component will be rendered on client-side -->
|
||||
<NuxtClientFallback fallback-tag="span">
|
||||
<Comments />
|
||||
<BrokeInSSR />
|
||||
</NuxtClientFallback>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
- `@ssr-error`: Event emitted when a child triggers an error in SSR. Note that this will only be triggered on the server.
|
||||
@ -30,7 +45,7 @@ This component is experimental and in order to use it you must enable the `exper
|
||||
|
||||
## Props
|
||||
|
||||
- `placeholderTag` | `fallbackTag`: Specify a fallback tag to be rendered if the slot fails to render.
|
||||
- `placeholderTag` | `fallbackTag`: Specify a fallback tag to be rendered if the slot fails to render on the server.
|
||||
- **type**: `string`
|
||||
- **default**: `div`
|
||||
- `placeholder` | `fallback`: Specify fallback content to be rendered if the slot fails to render.
|
||||
|
@ -55,6 +55,10 @@ Please note the layout name is normalized to kebab-case, so if your layout file
|
||||
Read more about dynamic layouts.
|
||||
::
|
||||
|
||||
- `fallback`: If an invalid layout is passed to the `name` prop, no layout will be rendered. Specify a `fallback` layout to be rendered in this scenario. It **must** match the name of the corresponding layout file in the [`layouts/`](/docs/guide/directory-structure/layouts) directory.
|
||||
- **type**: `string`
|
||||
- **default**: `null`
|
||||
|
||||
## Additional Props
|
||||
|
||||
`NuxtLayout` also accepts any additional props that you may need to pass to the layout. These custom props are then made accessible as attributes.
|
||||
@ -83,6 +87,8 @@ console.log(layoutCustomProps.title) // I am a custom layout
|
||||
|
||||
`<NuxtLayout />` renders incoming content via `<slot />`, which is then wrapped around Vue’s `<Transition />` component to activate layout transition. For this to work as expected, it is recommended that `<NuxtLayout />` is **not** the root element of the page component.
|
||||
|
||||
::code-group
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
@ -93,13 +99,27 @@ console.log(layoutCustomProps.title) // I am a custom layout
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue [layouts/custom.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!-- named slot -->
|
||||
<slot name="header" />
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
:read-more{to="/docs/getting-started/transitions"}
|
||||
|
||||
## Layout's Ref
|
||||
|
||||
To get the ref of a layout component, access it through `ref.value.layoutRef`.
|
||||
|
||||
````vue [app.vue]
|
||||
::code-group
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const layout = ref()
|
||||
|
||||
@ -109,8 +129,28 @@ function logFoo () {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLayout ref="layout" />
|
||||
<NuxtLayout ref="layout">
|
||||
default layout
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
````
|
||||
```
|
||||
|
||||
```vue [layouts/default.vue]
|
||||
<script setup lang="ts">
|
||||
const foo = () => console.log('foo')
|
||||
defineExpose({
|
||||
foo
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
default layout
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
:read-more{to="/docs/guide/directory-structure/layouts"}
|
||||
|
@ -12,10 +12,6 @@ When rendering an island component, the content of the island component is stati
|
||||
|
||||
Changing the island component props triggers a refetch of the island component to re-render it again.
|
||||
|
||||
::read-more{to="/docs/guide/going-further/experimental-features#componentislands" icon="i-ph-star-duotone"}
|
||||
This component is experimental and in order to use it you must enable the `experimental.componentIslands` option in your `nuxt.config`.
|
||||
::
|
||||
|
||||
::note
|
||||
Global styles of your application are sent with the response.
|
||||
::
|
||||
@ -60,3 +56,11 @@ Some slots are reserved to `NuxtIsland` for special cases.
|
||||
- `refresh()`
|
||||
- **type**: `() => Promise<void>`
|
||||
- **description**: force refetch the server component by refetching it.
|
||||
|
||||
## Events
|
||||
|
||||
- `error`
|
||||
- **parameters**:
|
||||
- **error**:
|
||||
- **type**: `unknown`
|
||||
- **description**: emitted when when `NuxtIsland` fails to fetch the new island.
|
||||
|
@ -119,10 +119,10 @@ type AsyncDataOptions<DataT> = {
|
||||
deep?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
default?: () => DataT | Ref<DataT> | null
|
||||
transform?: (input: DataT) => DataT
|
||||
transform?: (input: DataT) => DataT | Promise<DataT>
|
||||
pick?: string[]
|
||||
watch?: WatchSource[]
|
||||
getCachedData?: (key: string) => DataT
|
||||
getCachedData?: (key: string, nuxtApp: NuxtApp) => DataT
|
||||
}
|
||||
|
||||
type AsyncData<DataT, ErrorT> = {
|
||||
@ -130,6 +130,7 @@ type AsyncData<DataT, ErrorT> = {
|
||||
pending: Ref<boolean>
|
||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
clear: () => void
|
||||
error: Ref<ErrorT | null>
|
||||
status: Ref<AsyncDataRequestStatus>
|
||||
};
|
||||
|
@ -144,11 +144,11 @@ type UseFetchOptions<DataT> = {
|
||||
server?: boolean
|
||||
lazy?: boolean
|
||||
immediate?: boolean
|
||||
getCachedData?: (key: string) => DataT
|
||||
getCachedData?: (key: string, nuxtApp: NuxtApp) => DataT
|
||||
deep?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
default?: () => DataT
|
||||
transform?: (input: DataT) => DataT
|
||||
transform?: (input: DataT) => DataT | Promise<DataT>
|
||||
pick?: string[]
|
||||
watch?: WatchSource[] | false
|
||||
}
|
||||
@ -158,6 +158,7 @@ type AsyncData<DataT, ErrorT> = {
|
||||
pending: Ref<boolean>
|
||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
clear: () => void
|
||||
error: Ref<ErrorT | null>
|
||||
status: Ref<AsyncDataRequestStatus>
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ description: Generate an SSR-friendly unique identifier that can be passed to ac
|
||||
---
|
||||
|
||||
::important
|
||||
This composable is available since [Nuxt v3.10](/v3-10#ssr-safe-accessible-unique-id-creation).
|
||||
This composable is available since [Nuxt v3.10](/blog/v3-10#ssr-safe-accessible-unique-id-creation).
|
||||
::
|
||||
|
||||
`useId` generates an SSR-friendly unique identifier that can be passed to accessibility attributes.
|
||||
@ -19,11 +19,15 @@ const id = useId()
|
||||
<template>
|
||||
<div>
|
||||
<label :for="id">Email</label>
|
||||
<input :id="id" name="email" type="email"/>
|
||||
<input :id="id" name="email" type="email" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
`useId` must be used in a component with a single root element, as it uses this root element's attributes to pass the id from server to client.
|
||||
::
|
||||
|
||||
## Parameters
|
||||
|
||||
`useId` does not take any parameters.
|
||||
|
@ -39,7 +39,7 @@ Set `isLoading` to true and start to increase the `progress` value.
|
||||
|
||||
### `finish()`
|
||||
|
||||
Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later.
|
||||
Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset.
|
||||
|
||||
### `clear()`
|
||||
|
||||
|
@ -51,7 +51,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
})
|
||||
nuxtApp.hook('vue:error', (..._args) => {
|
||||
console.log('vue:error')
|
||||
// if (process.client) {
|
||||
// if (import.meta.client) {
|
||||
// console.log(..._args)
|
||||
// }
|
||||
})
|
||||
@ -120,7 +120,7 @@ Nuxt exposes the following properties through `ssrContext`:
|
||||
export const useColor = () => useState<string>('color', () => 'pink')
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
const color = useColor()
|
||||
}
|
||||
})
|
||||
@ -128,9 +128,9 @@ Nuxt exposes the following properties through `ssrContext`:
|
||||
|
||||
It is also possible to use more advanced types, such as `ref`, `reactive`, `shallowRef`, `shallowReactive` and `NuxtError`.
|
||||
|
||||
Since [Nuxt v3.4](https://nuxt.com/blog/v3-4#payload-enhancements), it is possible to define your own serializer/deserializer for types that are not supported by Nuxt.
|
||||
Since [Nuxt v3.4](https://nuxt.com/blog/v3-4#payload-enhancements), it is possible to define your own reducer/reviver for types that are not supported by Nuxt.
|
||||
|
||||
In the example below, we define a serializer for the [Luxon](https://moment.github.io/luxon/#/) DateTime class.
|
||||
In the example below, we define a reducer (or a serializer) and a reviver (or deserializer) for the [Luxon](https://moment.github.io/luxon/#/) DateTime class, using a payload plugin.
|
||||
|
||||
```ts [plugins/date-time-payload.ts]
|
||||
/**
|
||||
@ -159,7 +159,7 @@ export default defineComponent({
|
||||
setup (_props, { slots, emit }) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
onErrorCaptured((err) => {
|
||||
if (process.client && !nuxtApp.isHydrating) {
|
||||
if (import.meta.client && !nuxtApp.isHydrating) {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
|
82
docs/3.api/2.composables/use-preview-mode.md
Normal file
82
docs/3.api/2.composables/use-preview-mode.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "usePreviewMode"
|
||||
description: "Use usePreviewMode to check and control preview mode in Nuxt"
|
||||
---
|
||||
|
||||
# `usePreviewMode`
|
||||
|
||||
You can use the built-in `usePreviewMode` composable to access and control preview state in Nuxt. If the composable detects preview mode it will automatically force any updates necessary for [`useAsyncData`](/docs/api/composables/use-async-data) and [`useFetch`](/docs/api/composables/use-fetch) to rerender preview content.
|
||||
|
||||
```js
|
||||
const { enabled, state } = usePreviewMode()
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Custom `enable` check
|
||||
|
||||
You can specify a custom way to enable preview mode. By default the `usePreviewMode` composable will enable preview mode if there is a `preview` param in url that is equal to `true` (for example, `http://localhost:3000?preview=true`). You can wrap the `usePreviewMode` into custom composable, to keep options consistent across usages and prevent any errors.
|
||||
|
||||
```js
|
||||
export function useMyPreviewMode () {
|
||||
return usePreviewMode({
|
||||
shouldEnable: () => {
|
||||
return !!route.query.customPreview
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Modify default state
|
||||
|
||||
`usePreviewMode` will try to store the value of a `token` param from url in state. You can modify this state and it will be available for all [`usePreviewMode`](/docs/api/composables/use-preview-mode) calls.
|
||||
|
||||
```js
|
||||
const data1 = ref('data1')
|
||||
|
||||
const { enabled, state } = usePreviewMode({
|
||||
getState: (currentState) => {
|
||||
return { data1, data2: 'data2' }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::note
|
||||
The `getState` function will append returned values to current state, so be careful not to accidentally overwrite important state.
|
||||
::
|
||||
|
||||
## Example
|
||||
|
||||
```vue [pages/some-page.vue]
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
|
||||
const { enabled, state } = usePreviewMode({
|
||||
shouldEnable: () => {
|
||||
return route.query.customPreview === 'true'
|
||||
},
|
||||
})
|
||||
|
||||
const { data } = await useFetch('/api/preview', {
|
||||
query: {
|
||||
apiKey: state.token
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
Some base content
|
||||
|
||||
<p v-if="enabled">
|
||||
Only preview content: {{ state.token }}
|
||||
|
||||
<br>
|
||||
|
||||
<button @click="enabled = false">
|
||||
disable preview mode
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
@ -10,6 +10,12 @@ links:
|
||||
|
||||
`useRequestURL` is a helper function that returns an [URL object](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) working on both server-side and client-side.
|
||||
|
||||
::important
|
||||
When utilizing [Hybrid Rendering](/docs/guide/concepts/rendering#hybrid-rendering) with cache strategies, all incoming request headers are dropped when handling the cached responses via the [Nitro caching layer](https://nitro.unjs.io/guide/cache) (meaning `useRequestURL` will return `localhost` for the `host`).
|
||||
|
||||
You can define the [`cache.varies` option](https://nitro.unjs.io/guide/cache#options) to specify headers that will be considered when caching and serving the responses, such as `host` and `x-forwarded-host` for multi-tenant environments.
|
||||
::
|
||||
|
||||
::code-group
|
||||
|
||||
```vue [pages/about.vue]
|
||||
|
@ -46,6 +46,6 @@ useSeoMeta({
|
||||
|
||||
## Parameters
|
||||
|
||||
There are over 100 parameters. See the [full list of parameters in the source code](https://github.com/harlan-zw/zhead/blob/main/src/metaFlat.ts).
|
||||
There are over 100 parameters. See the [full list of parameters in the source code](https://github.com/harlan-zw/zhead/blob/main/packages/zhead/src/metaFlat.ts#L1035).
|
||||
|
||||
:read-more{to="/docs/getting-started/seo-meta"}
|
||||
|
@ -200,7 +200,7 @@ The two routes "/test-category" and "/1234-post" match both `[postId]-[postSlug]
|
||||
|
||||
To make sure that we are only matching digits (`\d+`) for `postId` in the `[postId]-[postSlug]` route, we can add the following to the `[postId]-[postSlug].vue` page template:
|
||||
|
||||
```vue [pages/[postId]-[postSlug].vue]
|
||||
```vue [pages/[postId\\]-[postSlug\\].vue]
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
path: '/:postId(\\d+)-:postSlug'
|
||||
|
@ -14,6 +14,10 @@ When prerendering, you can hint to Nitro to prerender additional paths, even if
|
||||
`prerenderRoutes` can only be called within the [Nuxt context](/docs/guide/going-further/nuxt-app#the-nuxt-context).
|
||||
::
|
||||
|
||||
::note
|
||||
`prerenderRoutes` has to be executed during prerendering. If the `prerenderRoutes` is used in dynamic pages/routes which are not prerendered, then it will not be executed.
|
||||
::
|
||||
|
||||
```js
|
||||
const route = useRoute()
|
||||
|
||||
|
@ -117,7 +117,7 @@ import { defineNuxtPlugin } from '#imports'
|
||||
import metaConfig from '#build/meta.config.mjs'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const createHead = process.server ? createServerHead : createClientHead
|
||||
const createHead = import.meta.server ? createServerHead : createClientHead
|
||||
const head = createHead()
|
||||
head.push(metaConfig.globalMeta)
|
||||
|
||||
|
@ -211,6 +211,10 @@ Type of path to resolve. If set to `'file'`, the function will try to resolve a
|
||||
|
||||
Creates resolver relative to base path.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/resolving-paths-and-injecting-assets-to-the-app" target="_blank"}
|
||||
Watch Vue School video about createResolver.
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
```ts
|
||||
|
@ -18,6 +18,10 @@ These functions are designed for registering your own utils, composables and Vue
|
||||
|
||||
Nuxt auto-imports helper functions, composables and Vue APIs to use across your application without explicitly importing them. Based on the directory structure, every Nuxt application can also use auto-imports for its own composables and plugins. Composables or plugins can use these functions.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/expanding-nuxt-s-auto-imports" target="_blank"}
|
||||
Watch Vue School video about Auto-imports Nuxt Kit utilities.
|
||||
::
|
||||
|
||||
## `addImports`
|
||||
|
||||
Add imports to the Nuxt application. It makes your imports available in the Nuxt application without the need to import them manually.
|
||||
|
@ -10,6 +10,10 @@ links:
|
||||
|
||||
Components are the building blocks of your Nuxt application. They are reusable Vue instances that can be used to create a user interface. In Nuxt, components from the components directory are automatically imported by default. However, if you need to import components from an alternative directory or wish to selectively import them as needed, `@nuxt/kit` provides the `addComponentsDir` and `addComponent` methods. These utils allow you to customize the component configuration to better suit your needs.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/injecting-components-and-component-directories" target="_blank"}
|
||||
Watch Vue School video about injecting components.
|
||||
::
|
||||
|
||||
## `addComponentsDir`
|
||||
|
||||
Register a directory to be scanned for components and imported only when used. Keep in mind, that this does not register components globally, until you specify `global: true` option.
|
||||
@ -37,6 +41,11 @@ interface ComponentsDir {
|
||||
transpile?: 'auto' | boolean
|
||||
}
|
||||
|
||||
// You can augment this interface (exported from `@nuxt/schema`) if needed
|
||||
interface ComponentMeta {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Component {
|
||||
pascalName: string
|
||||
kebabName: string
|
||||
@ -50,6 +59,7 @@ interface Component {
|
||||
island?: boolean
|
||||
mode?: 'client' | 'server' | 'all'
|
||||
priority?: number
|
||||
meta?: ComponentMeta
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -12,6 +12,10 @@ links:
|
||||
|
||||
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.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/extend-and-alter-nuxt-pages" target="_blank"}
|
||||
Watch Vue School video about extendPages.
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
```ts
|
||||
@ -67,6 +71,10 @@ Nuxt is powered by the [Nitro](https://nitro.unjs.io) server engine. With Nitro,
|
||||
You can read more about Nitro route rules in the [Nitro documentation](https://nitro.unjs.io/guide/routing#route-rules).
|
||||
::
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares" target="_blank"}
|
||||
Watch Vue School video about adding route rules and route middelwares.
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
```ts
|
||||
@ -184,6 +192,10 @@ Route middlewares can be also defined in plugins via [`addRouteMiddleware`](/doc
|
||||
Read more about route middlewares in the [Route middleware documentation](/docs/getting-started/routing#route-middleware).
|
||||
::
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares" target="_blank"}
|
||||
Watch Vue School video about adding route rules and route middelwares.
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
```ts
|
||||
|
@ -14,6 +14,10 @@ Plugins are self-contained code that usually add app-level functionality to Vue.
|
||||
|
||||
Registers a Nuxt plugin and to the plugins array.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/injecting-plugins" target="_blank"}
|
||||
Watch Vue School video about addPlugin.
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
```ts
|
||||
@ -110,6 +114,10 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
|
||||
Adds a template and registers as a nuxt plugin. This is useful for plugins that need to generate code at build time.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/injecting-plugin-templates" target="_blank"}
|
||||
Watch Vue School video about addPluginTemplate.
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
```ts
|
||||
@ -243,7 +251,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(VueFire, { firebaseApp })
|
||||
|
||||
<% if(options.ssr) { %>
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
nuxtApp.payload.vuefire = useSSRInitialState(undefined, firebaseApp)
|
||||
} else if (nuxtApp.payload?.vuefire) {
|
||||
useSSRInitialState(nuxtApp.payload.vuefire, firebaseApp)
|
||||
|
@ -22,12 +22,15 @@ Hook | Arguments | Environment | Description
|
||||
`app:beforeMount` | `vueApp` | Client | Called before mounting the app, called only on client side.
|
||||
`app:mounted` | `vueApp` | Client | Called when Vue app is initialized and mounted in browser.
|
||||
`app:suspense:resolve` | `appComponent` | Client | On [Suspense](https://vuejs.org/guide/built-ins/suspense.html#suspense) resolved event.
|
||||
`app:manifest:update` | `{ id, timestamp }` | Client | Called when there is a newer version of your app detected.
|
||||
`link:prefetch` | `to` | Client | Called when a `<NuxtLink>` is observed to be prefetched.
|
||||
`page:start` | `pageComponent?` | Client | Called on [Suspense](https://vuejs.org/guide/built-ins/suspense.html#suspense) pending event.
|
||||
`page:finish` | `pageComponent?` | Client | Called on [Suspense](https://vuejs.org/guide/built-ins/suspense.html#suspense) resolved event.
|
||||
`page:loading:start` | - | Client | Called when the `setup()` of the new page is running.
|
||||
`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).
|
||||
|
||||
## Nuxt Hooks (build time)
|
||||
|
||||
@ -90,6 +93,7 @@ See [Nitro](https://nitro.unjs.io/guide/plugins#available-hooks) for all availab
|
||||
|
||||
Hook | Arguments | Description | Types
|
||||
-----------------------|-----------------------|--------------------------------------|------------------
|
||||
`dev:ssr-logs` | `{ path, logs }` | Server | Called at the end of a request cycle with an array of server-side logs.
|
||||
`render:response` | `response, { event }` | Called before sending the response. | [response](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/renderer.ts#L24), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`render:html` | `html, { event }` | Called before constructing the HTML. | [html](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/renderer.ts#L15), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`render:island` | `islandResponse, { event, islandContext }` | Called before constructing the island HTML. | [islandResponse](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L28), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), [islandContext](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L38)
|
||||
|
@ -146,8 +146,8 @@ We recommend using [VS Code](https://code.visualstudio.com) along with the [ESLi
|
||||
```json [settings.json]
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": false,
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll": "never",
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -30,23 +30,20 @@ Check [Discussions](https://github.com/nuxt/nuxt/discussions) and [RFCs](https:/
|
||||
|
||||
Milestone | Expected date | Notes | Description
|
||||
-------------|---------------|------------------------------------------------------------------------|-----------------------
|
||||
SEO & PWA | 2023 | [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
|
||||
DevTools | 2023 | - | Integrated and modular devtools experience for Nuxt
|
||||
Scripts | 2023 | [nuxt/nuxt#22016](https://github.com/nuxt/nuxt/discussions/22016) | Easy 3rd party script management.
|
||||
Fonts | 2023 | [nuxt/nuxt#22014](https://github.com/nuxt/nuxt/discussions/22014) | Allow developers to easily configure fonts in their Nuxt apps.
|
||||
Assets | 2023 | [nuxt/nuxt#22012](https://github.com/nuxt/nuxt/discussions/22012) | Allow developers and modules to handle loading third-party assets.
|
||||
A11y | 2023 | [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255) | Accessibility hinting and utilities
|
||||
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).
|
||||
|
||||
## Core Modules
|
||||
## Core Modules Roadmap
|
||||
|
||||
In addition to the Nuxt framework, there are modules that are vital for the ecosystem. Their status will be updated below.
|
||||
|
||||
Module | Status | Nuxt Support | Repository | Description
|
||||
---------------|---------------------|--------------|------------|-------------------
|
||||
Scripts | April 2024 | 3.x | `nuxt/scripts` to be announced | Easy 3rd party script management. [nuxt/nuxt#22016](https://github.com/nuxt/nuxt/discussions/22016)
|
||||
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
|
||||
Image | Active | 2.x and 3.x | [nuxt/image](https://github.com/nuxt/image) | Nuxt 3 support is in progress: [nuxt/image#548](https://github.com/nuxt/image/discussions/548)
|
||||
I18n | Active | 2.x and 3.x | [nuxt-modules/i18n](https://github.com/nuxt-modules/i18n) | See [nuxt-modules/i18n#1287](https://github.com/nuxt-modules/i18n/discussions/1287) for Nuxt 3 support
|
||||
Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices
|
||||
|
||||
## Release Cycle
|
||||
|
||||
|
@ -48,6 +48,16 @@ navigation.icon: i-ph-notification-duotone
|
||||
::card
|
||||
---
|
||||
icon: i-simple-icons-github
|
||||
title: nuxt/fonts
|
||||
to: https://github.com/nuxt/fonts/releases
|
||||
target: _blank
|
||||
ui.icon.base: text-black dark:text-white
|
||||
---
|
||||
Nuxt Fonts releases.
|
||||
::
|
||||
::card
|
||||
---
|
||||
icon: i-simple-icons-github
|
||||
title: nuxt/image
|
||||
to: https://github.com/nuxt/image/releases
|
||||
target: _blank
|
||||
|
@ -22,6 +22,16 @@ export default defineNuxtConfig({
|
||||
// Enable Nuxt 3 compatible useHead
|
||||
// meta: true,
|
||||
|
||||
// Enable definePageMeta macro
|
||||
// macros: {
|
||||
// pageMeta: true
|
||||
// },
|
||||
|
||||
// Enable transpiling TypeScript with esbuild
|
||||
// typescript: {
|
||||
// esbuild: true
|
||||
// },
|
||||
|
||||
// -- Default features --
|
||||
|
||||
// Use legacy server instead of Nitro
|
||||
|
@ -42,7 +42,24 @@ export default defineNuxtRouteMiddleware((to) => {
|
||||
Use of `defineNuxtRouteMiddleware` is not supported outside of the middleware directory.
|
||||
::
|
||||
|
||||
::note
|
||||
## definePageMeta
|
||||
|
||||
You can also use [`definePageMeta`](https://nuxt.com/docs/api/utils/define-page-meta) in Nuxt Bridge.
|
||||
|
||||
You can be enabled with the `macros.pageMeta` option in your configuration file
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
import { defineNuxtConfig } from '@nuxt/bridge'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
bridge: {
|
||||
macros: {
|
||||
pageMeta: true
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::note
|
||||
But only for `middleware` and `layout`.
|
||||
::
|
||||
|
@ -57,6 +57,45 @@ Nuxt configuration will be loaded using [`unjs/jiti`](https://github.com/unjs/ji
|
||||
|
||||
::
|
||||
|
||||
1. If you were using `router.routeNameSplitter` you can achieve same result by updating route name generation logic in the new `pages:extend` hook:
|
||||
|
||||
::code-group
|
||||
|
||||
```ts [Nuxt 2]
|
||||
export default {
|
||||
router: {
|
||||
routeNameSplitter: '/'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts [Nuxt 3]
|
||||
import { createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
hooks: {
|
||||
'pages:extend' (routes) {
|
||||
const routeNameSplitter = '/'
|
||||
const root = createResolver(import.meta.url).resolve('./pages')
|
||||
|
||||
function updateName(routes) {
|
||||
if (!routes) return
|
||||
|
||||
for (const route of routes) {
|
||||
const relativePath = route.file.substring(root.length + 1)
|
||||
route.name = relativePath.slice(0, -4).replace(/\/index$/, '').replace(/\//g, routeNameSplitter)
|
||||
|
||||
updateName(route.children)
|
||||
}
|
||||
}
|
||||
updateName(routes)
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
#### ESM Syntax
|
||||
|
||||
Nuxt 3 is an [ESM native framework](/docs/guide/concepts/esm). Although [`unjs/jiti`](https://github.com/unjs/jiti) provides semi compatibility when loading `nuxt.config` file, avoid any usage of `require` and `module.exports` in this file.
|
||||
|
280
eslint.config.mjs
Normal file
280
eslint.config.mjs
Normal file
@ -0,0 +1,280 @@
|
||||
// Configs
|
||||
// import standard from "eslint-config-standard"
|
||||
// import nuxt from '@nuxt/eslint-config'
|
||||
|
||||
// Plugins
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import jsdoc from 'eslint-plugin-jsdoc'
|
||||
import esImport from 'eslint-plugin-import'
|
||||
import unicorn from 'eslint-plugin-unicorn'
|
||||
import noOnlyTests from 'eslint-plugin-no-only-tests'
|
||||
|
||||
/**
|
||||
* eslintrc compatibility
|
||||
* @see https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config
|
||||
* @see https://github.com/eslint/eslintrc#usage-esm
|
||||
*/
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
import js from '@eslint/js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended
|
||||
})
|
||||
|
||||
// TODO: Type definition?
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'**/dist/**',
|
||||
'**/.nuxt/**',
|
||||
'**/.nuxt-*/**',
|
||||
'**/.output/**',
|
||||
'**/.output-*/**',
|
||||
'**/public/**',
|
||||
'**/node_modules/**',
|
||||
'packages/schema/schema',
|
||||
|
||||
// TODO: remove when fully migrated to flat config
|
||||
'**/*.d.mts'
|
||||
]
|
||||
},
|
||||
// standard,
|
||||
...compat.extends('eslint-config-standard'),
|
||||
jsdoc.configs['flat/recommended'],
|
||||
// nuxt,
|
||||
...compat.extends('@nuxt/eslint-config'),
|
||||
esImport.configs.typescript,
|
||||
{
|
||||
rules: {
|
||||
'import/export': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.vue', '**/*.ts', '**/*.mts', '**/*.js', '**/*.cjs', '**/*.mjs'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
NodeJS: 'readonly',
|
||||
$fetch: 'readonly'
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
jsdoc,
|
||||
import: esImport,
|
||||
unicorn,
|
||||
'no-only-tests': noOnlyTests
|
||||
},
|
||||
rules: {
|
||||
// Imports should come first
|
||||
'import/first': 'error',
|
||||
// Other import rules
|
||||
'import/no-mutable-exports': 'error',
|
||||
// Allow unresolved imports
|
||||
'import/no-unresolved': 'off',
|
||||
// Allow paren-less arrow functions only when there's no braces
|
||||
'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
|
||||
// Allow async-await
|
||||
'generator-star-spacing': 'off',
|
||||
// Prefer const over let
|
||||
'prefer-const': ['error', { destructuring: 'any', ignoreReadBeforeAssign: false }],
|
||||
// No single if in an "else" block
|
||||
'no-lonely-if': 'error',
|
||||
// Force curly braces for control flow,
|
||||
// including if blocks with a single statement
|
||||
curly: ['error', 'all'],
|
||||
// No async function without await
|
||||
'require-await': 'error',
|
||||
// Force dot notation when possible
|
||||
'dot-notation': 'error',
|
||||
|
||||
'no-var': 'error',
|
||||
// Force object shorthand where possible
|
||||
'object-shorthand': 'error',
|
||||
// No useless destructuring/importing/exporting renames
|
||||
'no-useless-rename': 'error',
|
||||
/**********************/
|
||||
/* Unicorn Rules */
|
||||
/**********************/
|
||||
// Pass error message when throwing errors
|
||||
'unicorn/error-message': 'error',
|
||||
// Uppercase regex escapes
|
||||
'unicorn/escape-case': 'error',
|
||||
// Array.isArray instead of instanceof
|
||||
'unicorn/no-array-instanceof': 'error',
|
||||
// Prevent deprecated `new Buffer()`
|
||||
'unicorn/no-new-buffer': 'error',
|
||||
// Keep regex literals safe!
|
||||
'unicorn/no-unsafe-regex': 'off',
|
||||
// Lowercase number formatting for octal, hex, binary (0x12 instead of 0X12)
|
||||
'unicorn/number-literal-case': 'error',
|
||||
// ** instead of Math.pow()
|
||||
'unicorn/prefer-exponentiation-operator': 'error',
|
||||
// includes over indexOf when checking for existence
|
||||
'unicorn/prefer-includes': 'error',
|
||||
// String methods startsWith/endsWith instead of more complicated stuff
|
||||
'unicorn/prefer-starts-ends-with': 'error',
|
||||
// textContent instead of innerText
|
||||
'unicorn/prefer-text-content': 'error',
|
||||
// Enforce throwing type error when throwing error while checking typeof
|
||||
'unicorn/prefer-type-error': 'error',
|
||||
// Use new when throwing error
|
||||
'unicorn/throw-new-error': 'error',
|
||||
'sort-imports': [
|
||||
'error',
|
||||
{
|
||||
ignoreDeclarationSort: true
|
||||
}
|
||||
],
|
||||
'no-only-tests/no-only-tests': 'error',
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
'no-console': ['warn', { allow: ['warn', 'error', 'debug'] }],
|
||||
'vue/one-component-per-file': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
|
||||
// Vue stylistic rules from `@antfu/eslint-config`
|
||||
'vue/array-bracket-spacing': ['error', 'never'],
|
||||
'vue/arrow-spacing': ['error', { after: true, before: true }],
|
||||
'vue/block-spacing': ['error', 'always'],
|
||||
'vue/block-tag-newline': [
|
||||
'error',
|
||||
{
|
||||
multiline: 'always',
|
||||
singleline: 'always'
|
||||
}
|
||||
],
|
||||
'vue/brace-style': ['error', 'stroustrup', { allowSingleLine: true }],
|
||||
'vue/comma-dangle': ['error', 'always-multiline'],
|
||||
'vue/comma-spacing': ['error', { after: true, before: false }],
|
||||
'vue/comma-style': ['error', 'last'],
|
||||
'vue/html-comment-content-spacing': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
exceptions: ['-']
|
||||
}
|
||||
],
|
||||
'vue/key-spacing': ['error', { afterColon: true, beforeColon: false }],
|
||||
'vue/keyword-spacing': ['error', { after: true, before: true }],
|
||||
'vue/object-curly-newline': 'off',
|
||||
'vue/object-curly-spacing': ['error', 'always'],
|
||||
'vue/object-property-newline': [
|
||||
'error',
|
||||
{ allowMultiplePropertiesPerLine: true }
|
||||
],
|
||||
'vue/operator-linebreak': ['error', 'before'],
|
||||
'vue/padding-line-between-blocks': ['error', 'always'],
|
||||
'vue/quote-props': ['error', 'consistent-as-needed'],
|
||||
'vue/space-in-parens': ['error', 'never'],
|
||||
'vue/template-curly-spacing': 'error',
|
||||
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
'jsdoc/require-param': 'off',
|
||||
'jsdoc/require-returns': 'off',
|
||||
'jsdoc/require-param-type': 'off',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: '#vue-router',
|
||||
group: 'external'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
'import/no-restricted-paths': [
|
||||
'error',
|
||||
{
|
||||
zones: [
|
||||
{
|
||||
from: 'packages/nuxt/src/!(core)/**/*',
|
||||
target: 'packages/nuxt/src/core',
|
||||
message: 'core should not directly import from modules.'
|
||||
},
|
||||
{
|
||||
from: 'packages/nuxt/src/!(app)/**/*',
|
||||
target: 'packages/nuxt/src/app',
|
||||
message: 'app should not directly import from modules.'
|
||||
},
|
||||
{
|
||||
from: 'packages/nuxt/src/app/**/index.ts',
|
||||
target: 'packages/nuxt/src',
|
||||
message: 'should not import from barrel/index files'
|
||||
},
|
||||
{
|
||||
from: 'packages/nitro',
|
||||
target: 'packages/!(nitro)/**/*',
|
||||
message: 'nitro should not directly import other packages.'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
disallowTypeAnnotations: false
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
'error',
|
||||
{
|
||||
'ts-expect-error': 'allow-with-description',
|
||||
'ts-ignore': true
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true
|
||||
}
|
||||
],
|
||||
'jsdoc/check-tag-names': [
|
||||
'error',
|
||||
{
|
||||
definedTags: ['__NO_SIDE_EFFECTS__']
|
||||
}
|
||||
]
|
||||
},
|
||||
settings: {
|
||||
jsdoc: {
|
||||
ignoreInternal: true,
|
||||
tagNamePreference: {
|
||||
warning: 'warning',
|
||||
note: 'note'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['packages/schema/**'],
|
||||
rules: {
|
||||
'jsdoc/valid-types': 'off',
|
||||
'jsdoc/check-tag-names': [
|
||||
'error',
|
||||
{
|
||||
definedTags: ['experimental']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['packages/nuxt/src/app/**', 'test/**', '**/runtime/**', '**/*.test.ts'],
|
||||
rules: {
|
||||
'no-console': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['test/fixtures/**'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'vue/valid-v-for': 'off'
|
||||
}
|
||||
}
|
||||
]
|
@ -1,9 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@2/schema.json",
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"workspaces": {
|
||||
".": {
|
||||
"entry": [
|
||||
"scripts/*"
|
||||
"scripts/*",
|
||||
"test/*",
|
||||
"test/fixtures/*"
|
||||
]
|
||||
},
|
||||
"packages/*": {
|
||||
|
@ -11,5 +11,6 @@ exclude = [
|
||||
"https://twitter.nuxt.dev/",
|
||||
"https://github.com/nuxt/translations/discussions/4",
|
||||
"https://stackoverflow.com/help/minimal-reproducible-example",
|
||||
"(https?:\/\/github\.com\/)(.*\/)(generate)",
|
||||
# single-quotes are required for regexp
|
||||
'(https?:\/\/github\.com\/)(.*\/)(generate)',
|
||||
]
|
||||
|
@ -3,13 +3,14 @@
|
||||
import { addPluginTemplate } from 'nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' },
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
modules: [
|
||||
function () {
|
||||
addPluginTemplate({
|
||||
filename: 'plugins/my-plugin.mjs',
|
||||
getContents: () => `export default defineNuxtPlugin({ name: 'my-plugin' })`
|
||||
getContents: () => 'export default defineNuxtPlugin({ name: \'my-plugin\' })'
|
||||
})
|
||||
}
|
||||
],
|
||||
]
|
||||
})
|
||||
|
53
package.json
53
package.json
@ -13,8 +13,8 @@
|
||||
"cleanup": "rimraf 'packages/**/node_modules' 'playground/node_modules' 'node_modules'",
|
||||
"dev": "pnpm play",
|
||||
"dev:prepare": "pnpm --filter './packages/**' prepack --stub",
|
||||
"lint": "eslint --ext .vue,.ts,.js,.mjs .",
|
||||
"lint:fix": "eslint --ext .vue,.ts,.js,.mjs . --fix",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md",
|
||||
"lint:docs:fix": "markdownlint ./docs --fix && case-police 'docs/**/*.md' *.md --fix",
|
||||
"lint:knip": "pnpx knip",
|
||||
@ -37,60 +37,61 @@
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"rollup": "^4.12.0",
|
||||
"rollup": "^4.14.0",
|
||||
"nuxt": "workspace:*",
|
||||
"vite": "5.1.4",
|
||||
"vue": "3.4.20",
|
||||
"magic-string": "^0.30.7"
|
||||
"vite": "5.2.8",
|
||||
"vue": "3.4.21",
|
||||
"magic-string": "^0.30.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codspeed/vitest-plugin": "3.1.0",
|
||||
"@eslint/eslintrc": "3.0.2",
|
||||
"@eslint/js": "8.57.0",
|
||||
"@nuxt/eslint-config": "0.2.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/test-utils": "3.11.0",
|
||||
"@nuxt/test-utils": "3.12.0",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@testing-library/vue": "8.0.2",
|
||||
"@testing-library/vue": "8.0.3",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/node": "20.11.20",
|
||||
"@types/node": "20.12.4",
|
||||
"@types/semver": "7.5.8",
|
||||
"@vitest/coverage-v8": "1.3.1",
|
||||
"@vue/test-utils": "2.4.4",
|
||||
"@vitest/coverage-v8": "1.4.0",
|
||||
"@vue/test-utils": "2.4.5",
|
||||
"case-police": "0.6.1",
|
||||
"changelogen": "0.5.5",
|
||||
"consola": "3.2.3",
|
||||
"devalue": "4.3.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-standard": "17.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "48.2.0",
|
||||
"eslint-plugin-jsdoc": "48.2.2",
|
||||
"eslint-plugin-no-only-tests": "3.1.0",
|
||||
"eslint-plugin-unicorn": "51.0.1",
|
||||
"eslint-plugin-unicorn": "52.0.0",
|
||||
"execa": "8.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"globby": "14.0.1",
|
||||
"h3": "1.11.1",
|
||||
"happy-dom": "13.6.2",
|
||||
"happy-dom": "14.4.0",
|
||||
"jiti": "1.21.0",
|
||||
"markdownlint-cli": "0.39.0",
|
||||
"nitropack": "2.8.1",
|
||||
"nuxi": "3.10.1",
|
||||
"nitropack": "2.9.6",
|
||||
"nuxi": "3.11.1",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.0.10",
|
||||
"ofetch": "1.3.3",
|
||||
"ofetch": "1.3.4",
|
||||
"pathe": "1.1.2",
|
||||
"playwright-core": "1.41.2",
|
||||
"playwright-core": "1.42.1",
|
||||
"rimraf": "5.0.5",
|
||||
"semver": "7.6.0",
|
||||
"std-env": "3.7.0",
|
||||
"typescript": "5.3.3",
|
||||
"ufo": "1.4.0",
|
||||
"vitest": "1.3.1",
|
||||
"typescript": "5.4.3",
|
||||
"ufo": "1.5.3",
|
||||
"vitest": "1.4.0",
|
||||
"vitest-environment-nuxt": "1.0.0",
|
||||
"vue": "3.4.20",
|
||||
"vue-eslint-parser": "9.4.2",
|
||||
"vue": "3.4.21",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-tsc": "1.8.27"
|
||||
"vue-tsc": "2.0.7"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.4",
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/kit",
|
||||
"version": "3.10.3",
|
||||
"version": "3.11.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
@ -27,20 +27,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"c12": "^1.9.0",
|
||||
"c12": "^1.10.0",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"globby": "^14.0.1",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^5.3.1",
|
||||
"jiti": "^1.21.0",
|
||||
"knitwork": "^1.0.0",
|
||||
"knitwork": "^1.1.0",
|
||||
"mlly": "^1.6.1",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.0.3",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.6.0",
|
||||
"ufo": "^1.4.0",
|
||||
"ufo": "^1.5.3",
|
||||
"unctx": "^2.3.1",
|
||||
"unimport": "^3.7.1",
|
||||
"untyped": "^1.4.2"
|
||||
@ -50,11 +50,11 @@
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/semver": "7.5.8",
|
||||
"lodash-es": "4.17.21",
|
||||
"nitropack": "2.8.1",
|
||||
"nitropack": "2.9.6",
|
||||
"unbuild": "latest",
|
||||
"vite": "5.1.4",
|
||||
"vitest": "1.3.1",
|
||||
"webpack": "5.90.3"
|
||||
"vite": "5.2.8",
|
||||
"vitest": "1.4.0",
|
||||
"webpack": "5.91.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
|
@ -48,6 +48,7 @@ export async function addComponent (opts: AddComponentOptions) {
|
||||
mode: 'all',
|
||||
shortPath: opts.filePath,
|
||||
priority: 0,
|
||||
meta: {},
|
||||
...opts
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ export * from './pages'
|
||||
export * from './plugin'
|
||||
export * from './resolve'
|
||||
export * from './nitro'
|
||||
export * from './template'
|
||||
export { addTemplate, addTypeTemplate, normalizeTemplate, updateTemplates, writeTypes } from './template'
|
||||
export * from './logger'
|
||||
|
||||
// Internal Utils
|
||||
|
@ -9,7 +9,7 @@ import { toArray } from '../utils'
|
||||
|
||||
/** @deprecated */
|
||||
// TODO: Remove support for compiling ejs templates in v4
|
||||
export async function compileTemplate <T>(template: NuxtTemplate<T>, ctx: any) {
|
||||
export async function compileTemplate <T> (template: NuxtTemplate<T>, ctx: any) {
|
||||
const data = { ...ctx, options: template.options }
|
||||
if (template.src) {
|
||||
try {
|
||||
|
@ -3,13 +3,13 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { consola } from 'consola'
|
||||
import { logger, useLogger } from './logger'
|
||||
|
||||
vi.mock("consola", () => {
|
||||
const logger = {} as any;
|
||||
vi.mock('consola', () => {
|
||||
const logger = {} as any
|
||||
|
||||
logger.create = vi.fn(() => ({...logger}));
|
||||
logger.withTag = vi.fn(() => ({...logger}));
|
||||
|
||||
return { consola: logger };
|
||||
logger.create = vi.fn(() => ({ ...logger }))
|
||||
logger.withTag = vi.fn(() => ({ ...logger }))
|
||||
|
||||
return { consola: logger }
|
||||
})
|
||||
|
||||
describe('logger', () => {
|
||||
@ -20,29 +20,28 @@ describe('logger', () => {
|
||||
|
||||
describe('useLogger', () => {
|
||||
it('should expose consola when not passing a tag', () => {
|
||||
expect(useLogger()).toBe(consola);
|
||||
});
|
||||
expect(useLogger()).toBe(consola)
|
||||
})
|
||||
|
||||
it('should create a new instance when passing a tag', () => {
|
||||
const logger = vi.mocked(consola);
|
||||
|
||||
const instance = useLogger("tag");
|
||||
const logger = vi.mocked(consola)
|
||||
|
||||
expect(instance).toEqual(logger);
|
||||
expect(instance).not.toBe(logger);
|
||||
expect(logger.create).toBeCalledWith({});
|
||||
expect(logger.withTag).toBeCalledWith("tag");
|
||||
});
|
||||
const instance = useLogger('tag')
|
||||
|
||||
expect(instance).toEqual(logger)
|
||||
expect(instance).not.toBe(logger)
|
||||
expect(logger.create).toBeCalledWith({})
|
||||
expect(logger.withTag).toBeCalledWith('tag')
|
||||
})
|
||||
|
||||
it('should create a new instance when passing a tag and options', () => {
|
||||
const logger = vi.mocked(consola);
|
||||
|
||||
const instance = useLogger("tag", { level: 0 });
|
||||
const logger = vi.mocked(consola)
|
||||
|
||||
expect(instance).toEqual(logger);
|
||||
expect(instance).not.toBe(logger);
|
||||
expect(logger.create).toBeCalledWith({ level: 0 });
|
||||
expect(logger.withTag).toBeCalledWith("tag");
|
||||
});
|
||||
const instance = useLogger('tag', { level: 0 })
|
||||
|
||||
expect(instance).toEqual(logger)
|
||||
expect(instance).not.toBe(logger)
|
||||
expect(logger.create).toBeCalledWith({ level: 0 })
|
||||
expect(logger.withTag).toBeCalledWith('tag')
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { consola } from 'consola'
|
||||
import type { ConsolaOptions } from 'consola';
|
||||
import type { ConsolaOptions } from 'consola'
|
||||
|
||||
export const logger = consola
|
||||
|
||||
|
@ -38,7 +38,7 @@ export async function installModule (moduleToInstall: string | NuxtModule, inlin
|
||||
nuxt.options.build.transpile.push(normalizeModuleTranspilePath(moduleToInstall))
|
||||
const directory = getDirectory(moduleToInstall)
|
||||
if (directory !== moduleToInstall && !localLayerModuleDirs.has(directory)) {
|
||||
nuxt.options.modulesDir.push(directory)
|
||||
nuxt.options.modulesDir.push(resolve(directory, 'node_modules'))
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,9 +74,9 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
||||
const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule]
|
||||
let error: unknown
|
||||
for (const path of paths) {
|
||||
const src = await resolvePath(path)
|
||||
// Prefer ESM resolution if possible
|
||||
try {
|
||||
const src = await resolvePath(path)
|
||||
// Prefer ESM resolution if possible
|
||||
nuxtModule = await importModule(src, nuxt.options.modulesDir).catch(() => null) ?? requireModule(src, { paths: nuxt.options.modulesDir })
|
||||
|
||||
// nuxt-module-builder generates a module.json with metadata including the version
|
||||
|
@ -53,12 +53,12 @@ export function addRouteMiddleware (input: NuxtMiddleware | NuxtMiddleware[], op
|
||||
if (find >= 0) {
|
||||
if (app.middleware[find].path === middleware.path) { continue }
|
||||
if (options.override === true) {
|
||||
app.middleware[find] = middleware
|
||||
app.middleware[find] = { ...middleware }
|
||||
} else {
|
||||
logger.warn(`'${middleware.name}' middleware already exists at '${app.middleware[find].path}'. You can set \`override: true\` to replace it.`)
|
||||
}
|
||||
} else {
|
||||
app.middleware.push(middleware)
|
||||
app.middleware.push({ ...middleware })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -16,7 +16,7 @@ import { resolveNuxtModule } from './resolve'
|
||||
/**
|
||||
* Renders given template using lodash template during build into the project buildDir
|
||||
*/
|
||||
export function addTemplate <T>(_template: NuxtTemplate<T> | string) {
|
||||
export function addTemplate<T> (_template: NuxtTemplate<T> | string) {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
// Normalize template
|
||||
@ -36,7 +36,7 @@ export function addTemplate <T>(_template: NuxtTemplate<T> | string) {
|
||||
* Renders given types using lodash template during build into the project buildDir
|
||||
* and register them as types.
|
||||
*/
|
||||
export function addTypeTemplate <T>(_template: NuxtTypeTemplate<T>) {
|
||||
export function addTypeTemplate<T> (_template: NuxtTypeTemplate<T>) {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
const template = addTemplate(_template)
|
||||
@ -56,7 +56,7 @@ export function addTypeTemplate <T>(_template: NuxtTypeTemplate<T>) {
|
||||
/**
|
||||
* Normalize a nuxt template object
|
||||
*/
|
||||
export function normalizeTemplate <T>(template: NuxtTemplate<T> | string): ResolvedNuxtTemplate<T> {
|
||||
export function normalizeTemplate<T> (template: NuxtTemplate<T> | string): ResolvedNuxtTemplate<T> {
|
||||
if (!template) {
|
||||
throw new Error('Invalid template: ' + JSON.stringify(template))
|
||||
}
|
||||
@ -110,7 +110,8 @@ export function normalizeTemplate <T>(template: NuxtTemplate<T> | string): Resol
|
||||
export async function updateTemplates (options?: { filter?: (template: ResolvedNuxtTemplate<any>) => boolean }) {
|
||||
return await tryUseNuxt()?.hooks.callHook('builder:generateApp', options)
|
||||
}
|
||||
export async function writeTypes (nuxt: Nuxt) {
|
||||
|
||||
export async function _generateTypes (nuxt: Nuxt) {
|
||||
const nodeModulePaths = getModulePaths(nuxt.options.modulesDir)
|
||||
|
||||
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
|
||||
@ -248,6 +249,15 @@ export async function writeTypes (nuxt: Nuxt) {
|
||||
''
|
||||
].join('\n')
|
||||
|
||||
return {
|
||||
declaration,
|
||||
tsConfig
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeTypes (nuxt: Nuxt) {
|
||||
const { tsConfig, declaration } = await _generateTypes(nuxt)
|
||||
|
||||
async function writeFile () {
|
||||
const GeneratedBy = '// Generated by nuxi'
|
||||
|
||||
|
65
packages/kit/test/generate-types.spec.ts
Normal file
65
packages/kit/test/generate-types.spec.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Nuxt, NuxtConfig } from '@nuxt/schema'
|
||||
import { defu } from 'defu'
|
||||
|
||||
import { _generateTypes } from '../src/template'
|
||||
|
||||
type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends Record<string, any> ? DeepPartial<T[P]> : T[P]
|
||||
}
|
||||
|
||||
const mockNuxt = {
|
||||
options: {
|
||||
rootDir: '/my-app',
|
||||
srcDir: '/my-app',
|
||||
alias: {
|
||||
'~': '/my-app',
|
||||
'some-custom-alias': '/my-app/some-alias'
|
||||
},
|
||||
typescript: { includeWorkspace: false },
|
||||
buildDir: '/my-app/.nuxt',
|
||||
modulesDir: ['/my-app/node_modules', '/node_modules'],
|
||||
modules: [],
|
||||
_layers: [{ config: { srcDir: '/my-app' } }],
|
||||
_installedModules: [],
|
||||
_modules: []
|
||||
},
|
||||
callHook: () => {}
|
||||
} satisfies DeepPartial<Nuxt> as unknown as Nuxt
|
||||
|
||||
const mockNuxtWithOptions = (options: NuxtConfig) => defu({ options }, mockNuxt) as Nuxt
|
||||
|
||||
describe('tsConfig generation', () => {
|
||||
it('should add correct relative paths for aliases', async () => {
|
||||
const { tsConfig } = await _generateTypes(mockNuxt)
|
||||
expect(tsConfig.compilerOptions?.paths).toMatchInlineSnapshot(`
|
||||
{
|
||||
"#build": [
|
||||
".",
|
||||
],
|
||||
"some-custom-alias": [
|
||||
"../some-alias",
|
||||
],
|
||||
"~": [
|
||||
"..",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('should add exclude for module paths', async () => {
|
||||
const { tsConfig } = await _generateTypes(mockNuxtWithOptions({
|
||||
modulesDir: ['/my-app/modules/test/node_modules', '/my-app/modules/node_modules', '/my-app/node_modules/@some/module/node_modules']
|
||||
}))
|
||||
expect(tsConfig.exclude).toMatchInlineSnapshot(`
|
||||
[
|
||||
"../modules/test/node_modules",
|
||||
"../modules/node_modules",
|
||||
"../node_modules/@some/module/node_modules",
|
||||
"../node_modules",
|
||||
"../../node_modules",
|
||||
"../dist",
|
||||
]
|
||||
`)
|
||||
})
|
||||
})
|
@ -9,7 +9,7 @@ const fixtures = {
|
||||
'basic test fixture': 'test/fixtures/basic',
|
||||
'basic test fixture (types)': 'test/fixtures/basic-types',
|
||||
'minimal test fixture': 'test/fixtures/minimal',
|
||||
'minimal test fixture (types)': 'test/fixtures/minimal-types',
|
||||
'minimal test fixture (types)': 'test/fixtures/minimal-types'
|
||||
}
|
||||
|
||||
describe('loadNuxtConfig', () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
function defineNuxtConfig (config) {
|
||||
return config
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nuxt",
|
||||
"version": "3.10.3",
|
||||
"version": "3.11.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
@ -60,24 +60,24 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/devalue": "^2.0.2",
|
||||
"@nuxt/devtools": "^1.0.8",
|
||||
"@nuxt/devtools": "^1.1.5",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/telemetry": "^2.5.3",
|
||||
"@nuxt/ui-templates": "^1.3.1",
|
||||
"@nuxt/ui-templates": "^1.3.2",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@unhead/dom": "^1.8.10",
|
||||
"@unhead/ssr": "^1.8.10",
|
||||
"@unhead/vue": "^1.8.10",
|
||||
"@vue/shared": "^3.4.20",
|
||||
"@unhead/dom": "^1.9.4",
|
||||
"@unhead/ssr": "^1.9.4",
|
||||
"@unhead/vue": "^1.9.4",
|
||||
"@vue/shared": "^3.4.21",
|
||||
"acorn": "8.11.3",
|
||||
"c12": "^1.9.0",
|
||||
"c12": "^1.10.0",
|
||||
"chokidar": "^3.6.0",
|
||||
"cookie-es": "^1.0.0",
|
||||
"cookie-es": "^1.1.0",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
"devalue": "^4.3.2",
|
||||
"esbuild": "^0.20.1",
|
||||
"esbuild": "^0.20.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
@ -86,31 +86,32 @@
|
||||
"hookable": "^5.5.3",
|
||||
"jiti": "^1.21.0",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.0.0",
|
||||
"magic-string": "^0.30.7",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.9",
|
||||
"mlly": "^1.6.1",
|
||||
"nitropack": "^2.8.1",
|
||||
"nuxi": "^3.10.1",
|
||||
"nypm": "^0.3.6",
|
||||
"ofetch": "^1.3.3",
|
||||
"nitropack": "^2.9.6",
|
||||
"nuxi": "^3.11.1",
|
||||
"nypm": "^0.3.8",
|
||||
"ofetch": "^1.3.4",
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.2",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^1.0.3",
|
||||
"radix3": "^1.1.0",
|
||||
"radix3": "^1.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"std-env": "^3.7.0",
|
||||
"strip-literal": "^2.0.0",
|
||||
"ufo": "^1.4.0",
|
||||
"strip-literal": "^2.1.0",
|
||||
"ufo": "^1.5.3",
|
||||
"ultrahtml": "^1.5.3",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unctx": "^2.3.1",
|
||||
"unenv": "^1.9.0",
|
||||
"unimport": "^3.7.1",
|
||||
"unplugin": "^1.7.1",
|
||||
"unplugin": "^1.10.1",
|
||||
"unplugin-vue-router": "^0.7.0",
|
||||
"unstorage": "^1.10.2",
|
||||
"untyped": "^1.4.2",
|
||||
"vue": "^3.4.20",
|
||||
"vue": "^3.4.21",
|
||||
"vue-bundle-renderer": "^2.0.0",
|
||||
"vue-devtools-stub": "^0.1.0",
|
||||
"vue-router": "^4.3.0"
|
||||
@ -121,8 +122,8 @@
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"unbuild": "latest",
|
||||
"vite": "5.1.4",
|
||||
"vitest": "1.3.1"
|
||||
"vite": "5.2.8",
|
||||
"vitest": "1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@parcel/watcher": "^2.1.0",
|
||||
|
@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/export
|
||||
export * from 'vue'
|
||||
|
||||
export const install = () => {}
|
||||
|
@ -2,13 +2,15 @@ import { createError } from '../composables/error'
|
||||
|
||||
const intervalError = '[nuxt] `setInterval` should not be used on the server. Consider wrapping it with an `onNuxtReady`, `onBeforeMount` or `onMounted` lifecycle hook, or ensure you only call it in the browser by checking `import.meta.client`.'
|
||||
|
||||
export const setInterval = import.meta.client ? window.setInterval : () => {
|
||||
if (import.meta.dev) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: intervalError
|
||||
})
|
||||
}
|
||||
export const setInterval = import.meta.client
|
||||
? window.setInterval
|
||||
: () => {
|
||||
if (import.meta.dev) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: intervalError
|
||||
})
|
||||
}
|
||||
|
||||
console.error(intervalError)
|
||||
}
|
||||
console.error(intervalError)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export default defineComponent({
|
||||
|
||||
const cache = new WeakMap()
|
||||
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
export function createClientOnly<T extends ComponentOptions> (component: T) {
|
||||
if (cache.has(component)) {
|
||||
return cache.get(component)
|
||||
|
@ -9,14 +9,13 @@ import { joinURL, withQuery } from 'ufo'
|
||||
import type { FetchResponse } from 'ofetch'
|
||||
import { join } from 'pathe'
|
||||
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
||||
import type { NuxtIslandResponse } from '../types'
|
||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||
import { prerenderRoutes, useRequestEvent } from '../composables/ssr'
|
||||
import { getFragmentHTML } from './utils'
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
import { remoteComponentIslands, selectiveClient } from '#build/nuxt.config.mjs'
|
||||
import { appBaseURL, remoteComponentIslands, selectiveClient } from '#build/nuxt.config.mjs'
|
||||
|
||||
const pKey = '_islandPromises'
|
||||
const SSR_UID_RE = /data-island-uid="([^"]*)"/
|
||||
@ -29,7 +28,7 @@ const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
||||
|
||||
const components = import.meta.client ? new Map<string, Component>() : undefined
|
||||
|
||||
async function loadComponents (source = '/', paths: NuxtIslandResponse['components']) {
|
||||
async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) {
|
||||
const promises = []
|
||||
|
||||
for (const component in paths) {
|
||||
@ -69,7 +68,8 @@ export default defineComponent({
|
||||
default: false
|
||||
}
|
||||
},
|
||||
async setup (props, { slots, expose }) {
|
||||
emits: ['error'],
|
||||
async setup (props, { slots, expose, emit }) {
|
||||
let canTeleport = import.meta.server
|
||||
const teleportKey = ref(0)
|
||||
const key = ref(0)
|
||||
@ -88,37 +88,41 @@ export default defineComponent({
|
||||
onMounted(() => { mounted.value = true; teleportKey.value++ })
|
||||
|
||||
function setPayload (key: string, result: NuxtIslandResponse) {
|
||||
const toRevive: Partial<NuxtIslandResponse> = {}
|
||||
if (result.props) { toRevive.props = result.props }
|
||||
if (result.slots) { toRevive.slots = result.slots }
|
||||
if (result.components) { toRevive.components = result.components }
|
||||
|
||||
nuxtApp.payload.data[key] = {
|
||||
__nuxt_island: {
|
||||
key,
|
||||
...(import.meta.server && import.meta.prerender)
|
||||
? {}
|
||||
: { params: { ...props.context, props: props.props ? JSON.stringify(props.props) : undefined } },
|
||||
result: {
|
||||
props: result.props,
|
||||
slots: result.slots,
|
||||
components: result.components
|
||||
}
|
||||
result: toRevive
|
||||
},
|
||||
...result
|
||||
}
|
||||
}
|
||||
|
||||
const payloads: Required<Pick<NuxtIslandResponse, 'slots' | 'components'>> = {
|
||||
slots: {},
|
||||
components: {}
|
||||
}
|
||||
const payloads: Partial<Pick<NuxtIslandResponse, 'slots' | 'components'>> = {}
|
||||
|
||||
|
||||
if (nuxtApp.isHydrating) {
|
||||
payloads.slots = toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.slots ?? {}
|
||||
payloads.components = toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.components ?? {}
|
||||
if (instance.vnode.el) {
|
||||
const slots = toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.slots
|
||||
if (slots) { payloads.slots = slots }
|
||||
if (selectiveClient) {
|
||||
const components = toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.components
|
||||
if (components) { payloads.components = components }
|
||||
}
|
||||
}
|
||||
|
||||
const ssrHTML = ref<string>('')
|
||||
|
||||
if (import.meta.client && nuxtApp.isHydrating) {
|
||||
ssrHTML.value = getFragmentHTML(instance.vnode?.el ?? null, true)?.join('') || ''
|
||||
if (import.meta.client && instance.vnode?.el) {
|
||||
ssrHTML.value = getFragmentHTML(instance.vnode.el, true)?.join('') || ''
|
||||
const key = `${props.name}_${hashId.value}`
|
||||
nuxtApp.payload.data[key] ||= {}
|
||||
nuxtApp.payload.data[key].html = ssrHTML.value
|
||||
}
|
||||
|
||||
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId())
|
||||
@ -135,12 +139,15 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
return html.replaceAll(SLOT_FALLBACK_RE, (full, slotName) => {
|
||||
if (!currentSlots.includes(slotName)) {
|
||||
return full + (payloads.slots[slotName]?.fallback || '')
|
||||
}
|
||||
return full
|
||||
})
|
||||
if (payloads.slots) {
|
||||
return html.replaceAll(SLOT_FALLBACK_RE, (full, slotName) => {
|
||||
if (!currentSlots.includes(slotName)) {
|
||||
return full + (payloads.slots?.[slotName]?.fallback || '')
|
||||
}
|
||||
return full
|
||||
})
|
||||
}
|
||||
return html
|
||||
})
|
||||
|
||||
const cHead = ref<Record<'link' | 'style', Array<Record<string, string>>>>({ link: [], style: [] })
|
||||
@ -149,7 +156,7 @@ export default defineComponent({
|
||||
async function _fetchComponent (force = false) {
|
||||
const key = `${props.name}_${hashId.value}`
|
||||
|
||||
if (nuxtApp.payload.data[key]?.html && !force) { return nuxtApp.payload.data[key] }
|
||||
if (!force && nuxtApp.payload.data[key]?.html) { return nuxtApp.payload.data[key] }
|
||||
|
||||
const url = remoteComponentIslands && props.source ? new URL(`/__nuxt_island/${key}.json`, props.source).href : `/__nuxt_island/${key}.json`
|
||||
|
||||
@ -207,6 +214,7 @@ export default defineComponent({
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = e
|
||||
emit('error', e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,35 +255,43 @@ export default defineComponent({
|
||||
// this is used to force trigger Teleport when vue makes the diff between old and new node
|
||||
const isKeyOdd = teleportKey.value === 0 || !!(teleportKey.value && !(teleportKey.value % 2))
|
||||
|
||||
if (uid.value && html.value && (import.meta.server || props.lazy ? canTeleport : mounted.value || nuxtApp.isHydrating)) {
|
||||
if (uid.value && html.value && (import.meta.server || props.lazy ? canTeleport : (mounted.value || instance.vnode?.el))) {
|
||||
for (const slot in slots) {
|
||||
if (availableSlots.value.includes(slot)) {
|
||||
teleports.push(createVNode(Teleport,
|
||||
// use different selectors for even and odd teleportKey to force trigger the teleport
|
||||
{ to: import.meta.client ? `${isKeyOdd ? 'div' : ''}[data-island-uid="${uid.value}"][data-island-slot="${slot}"]` : `uid=${uid.value};slot=${slot}` },
|
||||
{ default: () => (payloads.slots[slot].props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) })
|
||||
{ default: () => (payloads.slots?.[slot].props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) })
|
||||
)
|
||||
}
|
||||
}
|
||||
if (import.meta.server) {
|
||||
for (const [id, info] of Object.entries(payloads.components ?? {})) {
|
||||
const { html } = info
|
||||
teleports.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, {
|
||||
default: () => [createStaticVNode(html, 1)]
|
||||
}))
|
||||
}
|
||||
}
|
||||
if (selectiveClient && import.meta.client && canLoadClientComponent.value) {
|
||||
for (const [id, info] of Object.entries(payloads.components ?? {})) {
|
||||
const { props } = info
|
||||
const component = components!.get(id)!
|
||||
// use different selectors for even and odd teleportKey to force trigger the teleport
|
||||
const vnode = createVNode(Teleport, { to: `${isKeyOdd ? 'div' : ''}[data-island-uid='${uid.value}'][data-island-component="${id}"]` }, {
|
||||
default: () => {
|
||||
return [h(component, props)]
|
||||
if (selectiveClient) {
|
||||
if (import.meta.server) {
|
||||
if (payloads.components) {
|
||||
for (const [id, info] of Object.entries(payloads.components)) {
|
||||
const { html, slots } = info
|
||||
let replaced = html.replaceAll('data-island-uid', `data-island-uid="${uid.value}"`)
|
||||
for (const slot in slots) {
|
||||
replaced = replaced.replaceAll(`data-island-slot="${slot}">`, full => full + slots[slot])
|
||||
}
|
||||
teleports.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, {
|
||||
default: () => [createStaticVNode(replaced, 1)]
|
||||
}))
|
||||
}
|
||||
})
|
||||
teleports.push(vnode)
|
||||
}
|
||||
} else if (canLoadClientComponent.value && payloads.components) {
|
||||
for (const [id, info] of Object.entries(payloads.components)) {
|
||||
const { props, slots } = info
|
||||
const component = components!.get(id)!
|
||||
// use different selectors for even and odd teleportKey to force trigger the teleport
|
||||
const vnode = createVNode(Teleport, { to: `${isKeyOdd ? 'div' : ''}[data-island-uid='${uid.value}'][data-island-component="${id}"]` }, {
|
||||
default: () => {
|
||||
return [h(component, props, Object.fromEntries(Object.entries(slots || {}).map(([k, v]) => ([k, () => createStaticVNode(`<div style="display: contents" data-island-uid data-island-slot="${k}">${v}</div>`, 1)
|
||||
]))))]
|
||||
}
|
||||
})
|
||||
teleports.push(vnode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,30 +23,7 @@ const firstNonUndefined = <T> (...args: (T | undefined)[]) => args.find(arg => a
|
||||
const NuxtLinkDevKeySymbol: InjectionKey<boolean> = Symbol('nuxt-link-dev-key')
|
||||
|
||||
/**
|
||||
* Create a NuxtLink component with given options as defaults.
|
||||
* @see https://nuxt.com/docs/api/components/nuxt-link
|
||||
*/
|
||||
export interface NuxtLinkOptions extends
|
||||
Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>,
|
||||
Pick<NuxtLinkProps, 'prefetchedClass'> {
|
||||
/**
|
||||
* The name of the component.
|
||||
* @default "NuxtLink"
|
||||
*/
|
||||
componentName?: string
|
||||
/**
|
||||
* A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable.
|
||||
*/
|
||||
externalRelAttribute?: string | null
|
||||
/**
|
||||
* An option to either add or remove trailing slashes in the `href`.
|
||||
* If unset or not matching the valid values `append` or `remove`, it will be ignored.
|
||||
*/
|
||||
trailingSlash?: 'append' | 'remove'
|
||||
}
|
||||
|
||||
/**
|
||||
* <NuxtLink> is a drop-in replacement for both Vue Router's <RouterLink> component and HTML's <a> tag.
|
||||
* `<NuxtLink>` is a drop-in replacement for both Vue Router's `<RouterLink>` component and HTML's `<a>` tag.
|
||||
* @see https://nuxt.com/docs/api/components/nuxt-link
|
||||
*/
|
||||
export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
|
||||
@ -88,7 +65,30 @@ export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
|
||||
noPrefetch?: boolean
|
||||
}
|
||||
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
/**
|
||||
* Create a NuxtLink component with given options as defaults.
|
||||
* @see https://nuxt.com/docs/api/components/nuxt-link
|
||||
*/
|
||||
export interface NuxtLinkOptions extends
|
||||
Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>,
|
||||
Pick<NuxtLinkProps, 'prefetchedClass'> {
|
||||
/**
|
||||
* The name of the component.
|
||||
* @default "NuxtLink"
|
||||
*/
|
||||
componentName?: string
|
||||
/**
|
||||
* A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable.
|
||||
*/
|
||||
externalRelAttribute?: string | null
|
||||
/**
|
||||
* An option to either add or remove trailing slashes in the `href`.
|
||||
* If unset or not matching the valid values `append` or `remove`, it will be ignored.
|
||||
*/
|
||||
trailingSlash?: 'append' | 'remove'
|
||||
}
|
||||
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
export function defineNuxtLink (options: NuxtLinkOptions) {
|
||||
const componentName = options.componentName || 'NuxtLink'
|
||||
|
||||
@ -113,14 +113,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
||||
|
||||
const resolvedPath = {
|
||||
...to,
|
||||
name: undefined, // named routes would otherwise always override trailing slash behavior
|
||||
path: applyTrailingSlashBehavior(path, options.trailingSlash)
|
||||
}
|
||||
|
||||
// named routes would otherwise always override trailing slash behavior
|
||||
if ('name' in resolvedPath) {
|
||||
delete resolvedPath.name
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,13 @@ export default defineComponent({
|
||||
estimatedProgress: {
|
||||
type: Function as unknown as () => (duration: number, elapsed: number) => number,
|
||||
required: false
|
||||
},
|
||||
}
|
||||
},
|
||||
setup (props, { slots, expose }) {
|
||||
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
|
||||
duration: props.duration,
|
||||
throttle: props.throttle,
|
||||
estimatedProgress: props.estimatedProgress,
|
||||
estimatedProgress: props.estimatedProgress
|
||||
})
|
||||
|
||||
expose({
|
||||
|
@ -24,8 +24,10 @@ import { useRoute, useRouter } from '../composables/router'
|
||||
import { PageRouteSymbol } from '../components/injections'
|
||||
import AppComponent from '#build/app-component.mjs'
|
||||
import ErrorComponent from '#build/error-component.mjs'
|
||||
// @ts-expect-error virtual file
|
||||
import { componentIslands } from '#build/nuxt.config.mjs'
|
||||
|
||||
const IslandRenderer = import.meta.server
|
||||
const IslandRenderer = import.meta.server && componentIslands
|
||||
? defineAsyncComponent(() => import('./island-renderer').then(r => r.default || r))
|
||||
: () => null
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Component } from 'vue'
|
||||
import { Teleport, defineComponent, h } from 'vue'
|
||||
import type { Component, InjectionKey } from 'vue'
|
||||
import { Teleport, defineComponent, h, inject, provide } from 'vue'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
// @ts-expect-error virtual file
|
||||
import { paths } from '#build/components-chunk'
|
||||
@ -9,6 +9,8 @@ type ExtendedComponent = Component & {
|
||||
__name: string
|
||||
}
|
||||
|
||||
export const NuxtTeleportIslandSymbol = Symbol('NuxtTeleportIslandComponent') as InjectionKey<false | string>
|
||||
|
||||
/**
|
||||
* component only used with componentsIsland
|
||||
* this teleport the component in SSR only if it needs to be hydrated on client
|
||||
@ -37,8 +39,10 @@ export default defineComponent({
|
||||
setup (props, { slots }) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
if (!nuxtApp.ssrContext?.islandContext || !props.nuxtClient) { return () => slots.default!() }
|
||||
// if there's already a teleport parent, we don't need to teleport or to render the wrapped component client side
|
||||
if (!nuxtApp.ssrContext?.islandContext || !props.nuxtClient || inject(NuxtTeleportIslandSymbol, false)) { return () => slots.default?.() }
|
||||
|
||||
provide(NuxtTeleportIslandSymbol, props.to)
|
||||
const islandContext = nuxtApp.ssrContext!.islandContext!
|
||||
|
||||
return () => {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Teleport, defineComponent, h } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { Teleport, createVNode, defineComponent, h, inject } from 'vue'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
|
||||
import { NuxtTeleportIslandSymbol } from './nuxt-teleport-island-component'
|
||||
|
||||
/**
|
||||
* component only used within islands for slot teleport
|
||||
*/
|
||||
@ -9,37 +11,50 @@ export default defineComponent({
|
||||
name: 'NuxtTeleportIslandSlot',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* must be an array to handle v-for
|
||||
*/
|
||||
props: {
|
||||
type: Object as () => Array<any>
|
||||
type: Object as () => Array<any>
|
||||
}
|
||||
},
|
||||
setup (props, { slots }) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const islandContext = nuxtApp.ssrContext?.islandContext
|
||||
|
||||
if(!islandContext) {
|
||||
return () => slots.default?.()
|
||||
if (!islandContext) {
|
||||
return () => slots.default?.()[0]
|
||||
}
|
||||
|
||||
const componentName = inject(NuxtTeleportIslandSymbol, false)
|
||||
islandContext.slots[props.name] = {
|
||||
props: (props.props || []) as unknown[]
|
||||
props: (props.props || []) as unknown[]
|
||||
}
|
||||
|
||||
return () => {
|
||||
const vnodes = [h('div', {
|
||||
style: 'display: contents;',
|
||||
'data-island-uid': '',
|
||||
'data-island-slot': props.name,
|
||||
})]
|
||||
const vnodes: VNode[] = []
|
||||
|
||||
if (nuxtApp.ssrContext?.islandContext && slots.default) {
|
||||
vnodes.push(h('div', {
|
||||
style: 'display: contents;',
|
||||
'data-island-uid': '',
|
||||
'data-island-slot': props.name
|
||||
}, {
|
||||
// Teleport in slot to not be hydrated client-side with the staticVNode
|
||||
default: () => [createVNode(Teleport, { to: `island-slot=${componentName};${props.name}` }, slots.default?.())]
|
||||
}))
|
||||
} else {
|
||||
vnodes.push(h('div', {
|
||||
style: 'display: contents;',
|
||||
'data-island-uid': '',
|
||||
'data-island-slot': props.name
|
||||
}))
|
||||
}
|
||||
|
||||
if (slots.fallback) {
|
||||
vnodes.push(h(Teleport, { to: `island-fallback=${props.name}`}, slots.fallback()))
|
||||
vnodes.push(h(Teleport, { to: `island-fallback=${props.name}` }, slots.fallback()))
|
||||
}
|
||||
|
||||
return vnodes
|
||||
|
@ -75,24 +75,6 @@ export function createBuffer () {
|
||||
}
|
||||
}
|
||||
|
||||
const TRANSLATE_RE = /&(nbsp|amp|quot|lt|gt);/g
|
||||
const NUMSTR_RE = /&#(\d+);/gi
|
||||
export function decodeHtmlEntities (html: string) {
|
||||
const translateDict = {
|
||||
nbsp: ' ',
|
||||
amp: '&',
|
||||
quot: '"',
|
||||
lt: '<',
|
||||
gt: '>'
|
||||
} as const
|
||||
return html.replace(TRANSLATE_RE, function (_, entity: keyof typeof translateDict) {
|
||||
return translateDict[entity]
|
||||
}).replace(NUMSTR_RE, function (_, numStr: string) {
|
||||
const num = parseInt(numStr, 10)
|
||||
return String.fromCharCode(num)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* helper for NuxtIsland to generate a correct array for scoped data
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getCurrentInstance, onBeforeMount, onServerPrefetch, onUnmounted, ref, shallowRef, toRef, unref, watch } from 'vue'
|
||||
import { computed, getCurrentInstance, getCurrentScope, onBeforeMount, onScopeDispose, onServerPrefetch, onUnmounted, ref, shallowRef, toRef, unref, watch } from 'vue'
|
||||
import type { Ref, WatchSource } from 'vue'
|
||||
import type { NuxtApp } from '../nuxt'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
@ -12,7 +12,7 @@ import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
||||
|
||||
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||
|
||||
export type _Transform<Input = any, Output = any> = (input: Input) => Output
|
||||
export type _Transform<Input = any, Output = any> = (input: Input) => Output | Promise<Output>
|
||||
|
||||
export type PickFrom<T, K extends Array<string>> = T extends Array<any>
|
||||
? T
|
||||
@ -61,13 +61,15 @@ export interface AsyncDataOptions<
|
||||
* A `null` or `undefined` return value will trigger a fetch.
|
||||
* Default is `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]` which only caches data when payloadExtraction is enabled.
|
||||
*/
|
||||
getCachedData?: (key: string) => DataT
|
||||
getCachedData?: (key: string, nuxtApp: NuxtApp) => DataT
|
||||
/**
|
||||
* A function that can be used to alter handler function result after resolving
|
||||
* A function that can be used to alter handler function result after resolving.
|
||||
* Do not use it along with the `pick` option.
|
||||
*/
|
||||
transform?: _Transform<ResT, DataT>
|
||||
/**
|
||||
* Only pick specified keys in this array from the handler function result
|
||||
* Only pick specified keys in this array from the handler function result.
|
||||
* Do not use it along with the `transform` option.
|
||||
*/
|
||||
pick?: PickKeys
|
||||
/**
|
||||
@ -109,6 +111,7 @@ export interface _AsyncData<DataT, ErrorT> {
|
||||
pending: Ref<boolean>
|
||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
clear: () => void
|
||||
error: Ref<ErrorT | null>
|
||||
status: Ref<AsyncDataRequestStatus>
|
||||
}
|
||||
@ -212,14 +215,17 @@ export function useAsyncData<
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
// When prerendering, share payload data automatically between requests
|
||||
const handler = import.meta.client || !import.meta.prerender || !nuxtApp.ssrContext?._sharedPrerenderCache ? _handler : async () => {
|
||||
const value = await nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
|
||||
if (value) { return value as ResT }
|
||||
const handler = import.meta.client || !import.meta.prerender || !nuxtApp.ssrContext?._sharedPrerenderCache
|
||||
? _handler
|
||||
: () => {
|
||||
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
|
||||
if (value) { return value as Promise<ResT> }
|
||||
|
||||
const promise = nuxtApp.runWithContext(_handler)
|
||||
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
const promise = nuxtApp.runWithContext(_handler)
|
||||
|
||||
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
// Used to get default values
|
||||
const getDefault = () => null
|
||||
@ -239,7 +245,7 @@ export function useAsyncData<
|
||||
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
||||
}
|
||||
|
||||
const hasCachedData = () => ![null, undefined].includes(options.getCachedData!(key) as any)
|
||||
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
||||
|
||||
// Create or use a shared asyncData entity
|
||||
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
||||
@ -248,7 +254,7 @@ export function useAsyncData<
|
||||
const _ref = options.deep ? ref : shallowRef
|
||||
|
||||
nuxtApp._asyncData[key] = {
|
||||
data: _ref(options.getCachedData!(key) ?? options.default!()),
|
||||
data: _ref(options.getCachedData!(key, nuxtApp) ?? options.default!()),
|
||||
pending: ref(!hasCachedData()),
|
||||
error: toRef(nuxtApp.payload._errors, key),
|
||||
status: ref('idle')
|
||||
@ -268,7 +274,7 @@ export function useAsyncData<
|
||||
}
|
||||
// Avoid fetching same key that is already fetched
|
||||
if ((opts._initial || (nuxtApp.isHydrating && opts._initial !== false)) && hasCachedData()) {
|
||||
return Promise.resolve(options.getCachedData!(key))
|
||||
return Promise.resolve(options.getCachedData!(key, nuxtApp))
|
||||
}
|
||||
asyncData.pending.value = true
|
||||
asyncData.status.value = 'pending'
|
||||
@ -281,13 +287,13 @@ export function useAsyncData<
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
.then((_result) => {
|
||||
.then(async (_result) => {
|
||||
// If this request is cancelled, resolve to the latest request.
|
||||
if ((promise as any).cancelled) { return nuxtApp._asyncDataPromises[key] }
|
||||
|
||||
let result = _result as unknown as DataT
|
||||
if (options.transform) {
|
||||
result = options.transform(_result)
|
||||
result = await options.transform(_result)
|
||||
}
|
||||
if (options.pick) {
|
||||
result = pick(result as any, options.pick) as DataT
|
||||
@ -318,6 +324,8 @@ export function useAsyncData<
|
||||
return nuxtApp._asyncDataPromises[key]!
|
||||
}
|
||||
|
||||
asyncData.clear = () => clearNuxtDataByKey(nuxtApp, key)
|
||||
|
||||
const initialFetch = () => asyncData.refresh({ _initial: true })
|
||||
|
||||
const fetchOnServer = options.server !== false && nuxtApp.payload.serverRendered
|
||||
@ -343,13 +351,11 @@ export function useAsyncData<
|
||||
if (instance && !instance._nuxtOnBeforeMountCbs) {
|
||||
instance._nuxtOnBeforeMountCbs = []
|
||||
const cbs = instance._nuxtOnBeforeMountCbs
|
||||
if (instance) {
|
||||
onBeforeMount(() => {
|
||||
cbs.forEach((cb) => { cb() })
|
||||
cbs.splice(0, cbs.length)
|
||||
})
|
||||
onUnmounted(() => cbs.splice(0, cbs.length))
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
cbs.forEach((cb) => { cb() })
|
||||
cbs.splice(0, cbs.length)
|
||||
})
|
||||
onUnmounted(() => cbs.splice(0, cbs.length))
|
||||
}
|
||||
|
||||
if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || hasCachedData())) {
|
||||
@ -364,16 +370,20 @@ export function useAsyncData<
|
||||
// 4. Navigation (lazy: false) - or plugin usage: await fetch
|
||||
initialFetch()
|
||||
}
|
||||
const hasScope = getCurrentScope()
|
||||
if (options.watch) {
|
||||
watch(options.watch, () => asyncData.refresh())
|
||||
const unsub = watch(options.watch, () => asyncData.refresh())
|
||||
if (hasScope) {
|
||||
onScopeDispose(unsub)
|
||||
}
|
||||
}
|
||||
const off = nuxtApp.hook('app:data:refresh', async (keys) => {
|
||||
if (!keys || keys.includes(key)) {
|
||||
await asyncData.refresh()
|
||||
}
|
||||
})
|
||||
if (instance) {
|
||||
onUnmounted(off)
|
||||
if (hasScope) {
|
||||
onScopeDispose(off)
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,7 +467,18 @@ export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | null
|
||||
}
|
||||
|
||||
return {
|
||||
data: toRef(nuxtApp.payload.data, key)
|
||||
data: computed({
|
||||
get () {
|
||||
return nuxtApp._asyncData[key]?.data.value ?? nuxtApp.payload.data[key]
|
||||
},
|
||||
set (value) {
|
||||
if (nuxtApp._asyncData[key]) {
|
||||
nuxtApp._asyncData[key]!.data.value = value
|
||||
} else {
|
||||
nuxtApp.payload.data[key] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,21 +505,29 @@ export function clearNuxtData (keys?: string | string[] | ((key: string) => bool
|
||||
: toArray(keys)
|
||||
|
||||
for (const key of _keys) {
|
||||
if (key in nuxtApp.payload.data) {
|
||||
nuxtApp.payload.data[key] = undefined
|
||||
}
|
||||
if (key in nuxtApp.payload._errors) {
|
||||
nuxtApp.payload._errors[key] = null
|
||||
}
|
||||
if (nuxtApp._asyncData[key]) {
|
||||
nuxtApp._asyncData[key]!.data.value = undefined
|
||||
nuxtApp._asyncData[key]!.error.value = null
|
||||
nuxtApp._asyncData[key]!.pending.value = false
|
||||
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||
}
|
||||
if (key in nuxtApp._asyncDataPromises) {
|
||||
nuxtApp._asyncDataPromises[key] = undefined
|
||||
}
|
||||
clearNuxtDataByKey(nuxtApp, key)
|
||||
}
|
||||
}
|
||||
|
||||
function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
|
||||
if (key in nuxtApp.payload.data) {
|
||||
nuxtApp.payload.data[key] = undefined
|
||||
}
|
||||
|
||||
if (key in nuxtApp.payload._errors) {
|
||||
nuxtApp.payload._errors[key] = null
|
||||
}
|
||||
|
||||
if (nuxtApp._asyncData[key]) {
|
||||
nuxtApp._asyncData[key]!.data.value = undefined
|
||||
nuxtApp._asyncData[key]!.error.value = null
|
||||
nuxtApp._asyncData[key]!.pending.value = false
|
||||
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||
}
|
||||
|
||||
if (key in nuxtApp._asyncDataPromises) {
|
||||
(nuxtApp._asyncDataPromises[key] as any).cancelled = true
|
||||
nuxtApp._asyncDataPromises[key] = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ async function runLegacyAsyncData (res: Record<string, any> | Promise<Record<str
|
||||
}
|
||||
|
||||
/** @since 3.0.0 */
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
export const defineNuxtComponent: typeof defineComponent =
|
||||
function defineNuxtComponent (...args: any[]): any {
|
||||
const [options, key] = args
|
||||
|
@ -55,7 +55,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
||||
|
||||
// use a custom ref to expire the cookie on client side otherwise use basic ref
|
||||
const cookie = import.meta.client && delay && !hasExpired
|
||||
? cookieRef<T | undefined>(cookieValue, delay)
|
||||
? cookieRef<T | undefined>(cookieValue, delay, opts.watch && opts.watch !== 'shallow')
|
||||
: ref<T | undefined>(cookieValue)
|
||||
|
||||
if (import.meta.dev && hasExpired) {
|
||||
@ -63,7 +63,15 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
||||
}
|
||||
|
||||
if (import.meta.client) {
|
||||
const channel = store || typeof BroadcastChannel === 'undefined' ? null : new BroadcastChannel(`nuxt:cookies:${name}`)
|
||||
let channel: null | BroadcastChannel = null
|
||||
try {
|
||||
if (!store && typeof BroadcastChannel !== 'undefined') {
|
||||
channel = new BroadcastChannel(`nuxt:cookies:${name}`)
|
||||
}
|
||||
} catch {
|
||||
// BroadcastChannel will fail in certain situations when cookies are disabled
|
||||
// or running in an iframe: see https://github.com/nuxt/nuxt/issues/26338
|
||||
}
|
||||
const callback = () => {
|
||||
if (opts.readonly || isEqual(cookie.value, cookies[name])) { return }
|
||||
writeClientCookie(name, cookie.value, opts as CookieSerializeOptions)
|
||||
@ -92,7 +100,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
||||
if (store) {
|
||||
store.onchange = (event) => {
|
||||
const cookie = event.changed.find((c: any) => c.name === name)
|
||||
if (cookie) handleChange({ value: cookie.value })
|
||||
if (cookie) { handleChange({ value: cookie.value }) }
|
||||
}
|
||||
} else if (channel) {
|
||||
channel.onmessage = ({ data }) => handleChange(data)
|
||||
@ -123,9 +131,9 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
||||
return cookie as CookieRef<T>
|
||||
}
|
||||
/** @since 3.10.0 */
|
||||
export function refreshCookie(name: string) {
|
||||
if (store || typeof BroadcastChannel === 'undefined') return
|
||||
|
||||
export function refreshCookie (name: string) {
|
||||
if (import.meta.server || store || typeof BroadcastChannel === 'undefined') { return }
|
||||
|
||||
new BroadcastChannel(`nuxt:cookies:${name}`)?.postMessage({ refresh: true })
|
||||
}
|
||||
|
||||
@ -174,14 +182,21 @@ function writeServerCookie (event: H3Event, name: string, value: any, opts: Cook
|
||||
const MAX_TIMEOUT_DELAY = 2_147_483_647
|
||||
|
||||
// custom ref that will update the value to undefined if the cookie expires
|
||||
function cookieRef<T> (value: T | undefined, delay: number) {
|
||||
function cookieRef<T> (value: T | undefined, delay: number, shouldWatch: boolean) {
|
||||
let timeout: NodeJS.Timeout
|
||||
let unsubscribe: (() => void) | undefined
|
||||
let elapsed = 0
|
||||
const internalRef = shouldWatch ? ref(value) : { value }
|
||||
if (getCurrentScope()) {
|
||||
onScopeDispose(() => { clearTimeout(timeout) })
|
||||
onScopeDispose(() => {
|
||||
unsubscribe?.()
|
||||
clearTimeout(timeout)
|
||||
})
|
||||
}
|
||||
|
||||
return customRef((track, trigger) => {
|
||||
if (shouldWatch) { unsubscribe = watch(internalRef, trigger) }
|
||||
|
||||
function createExpirationTimeout () {
|
||||
clearTimeout(timeout)
|
||||
const timeRemaining = delay - elapsed
|
||||
@ -190,7 +205,7 @@ function cookieRef<T> (value: T | undefined, delay: number) {
|
||||
elapsed += timeoutLength
|
||||
if (elapsed < delay) { return createExpirationTimeout() }
|
||||
|
||||
value = undefined
|
||||
internalRef.value = undefined
|
||||
trigger()
|
||||
}, timeoutLength)
|
||||
}
|
||||
@ -198,12 +213,12 @@ function cookieRef<T> (value: T | undefined, delay: number) {
|
||||
return {
|
||||
get () {
|
||||
track()
|
||||
return value
|
||||
return internalRef.value
|
||||
},
|
||||
set (newValue) {
|
||||
createExpirationTimeout()
|
||||
|
||||
value = newValue
|
||||
internalRef.value = newValue
|
||||
trigger()
|
||||
}
|
||||
}
|
||||
|
@ -52,10 +52,8 @@ export const clearError = async (options: { redirect?: string } = {}) => {
|
||||
|
||||
/** @since 3.0.0 */
|
||||
export const isNuxtError = <DataT = unknown>(
|
||||
error?: string | object
|
||||
): error is NuxtError<DataT> => (
|
||||
!!error && typeof error === 'object' && NUXT_ERROR_SIGNATURE in error
|
||||
)
|
||||
error?: string | object
|
||||
): error is NuxtError<DataT> => !!error && typeof error === 'object' && NUXT_ERROR_SIGNATURE in error
|
||||
|
||||
/** @since 3.0.0 */
|
||||
export const createError = <DataT = unknown>(
|
||||
|
@ -94,13 +94,7 @@ export function useFetch<
|
||||
) {
|
||||
const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
|
||||
|
||||
const _request = computed(() => {
|
||||
let r = request
|
||||
if (typeof r === 'function') {
|
||||
r = r()
|
||||
}
|
||||
return toValue(r)
|
||||
})
|
||||
const _request = computed(() => toValue(request))
|
||||
|
||||
const _key = opts.key || hash([autoKey, typeof _request.value === 'string' ? _request.value : '', ...generateOptionSegments(opts)])
|
||||
if (!_key || typeof _key !== 'string') {
|
||||
@ -243,10 +237,10 @@ export function useLazyFetch<
|
||||
autoKey)
|
||||
}
|
||||
|
||||
function generateOptionSegments <_ResT, DataT, DefaultT>(opts: UseFetchOptions<_ResT, DataT, any, DefaultT, any, any>) {
|
||||
function generateOptionSegments <_ResT, DataT, DefaultT> (opts: UseFetchOptions<_ResT, DataT, any, DefaultT, any, any>) {
|
||||
const segments: Array<string | undefined | Record<string, string>> = [
|
||||
toValue(opts.method as MaybeRef<string | undefined> | undefined)?.toUpperCase() || 'GET',
|
||||
toValue(opts.baseURL),
|
||||
toValue(opts.baseURL)
|
||||
]
|
||||
for (const _obj of [opts.params || opts.query]) {
|
||||
const obj = toValue(_obj)
|
||||
|
@ -3,6 +3,7 @@ import { useNuxtApp } from '../nuxt'
|
||||
import { clientOnlySymbol } from '#app/components/client-only'
|
||||
|
||||
const ATTR_KEY = 'data-n-ids'
|
||||
const SEPARATOR = '-'
|
||||
|
||||
/**
|
||||
* Generate an SSR-friendly unique identifier that can be passed to accessibility attributes.
|
||||
@ -13,7 +14,8 @@ export function useId (key?: string): string {
|
||||
throw new TypeError('[nuxt] [useId] key must be a string.')
|
||||
}
|
||||
// TODO: implement in composable-keys
|
||||
key = key.slice(1)
|
||||
// Make sure key starts with a letter to be a valid selector
|
||||
key = `n${key.slice(1)}`
|
||||
const nuxtApp = useNuxtApp()
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
@ -26,11 +28,11 @@ export function useId (key?: string): string {
|
||||
instance._nuxtIdIndex ||= {}
|
||||
instance._nuxtIdIndex[key] ||= 0
|
||||
|
||||
const instanceIndex = key + ':' + instance._nuxtIdIndex[key]++
|
||||
const instanceIndex = key + SEPARATOR + instance._nuxtIdIndex[key]++
|
||||
|
||||
if (import.meta.server) {
|
||||
const ids = JSON.parse(instance.attrs[ATTR_KEY] as string | undefined || '{}')
|
||||
ids[instanceIndex] = key + ':' + nuxtApp._id++
|
||||
ids[instanceIndex] = key + SEPARATOR + nuxtApp._id++
|
||||
instance.attrs[ATTR_KEY] = JSON.stringify(ids)
|
||||
return ids[instanceIndex]
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export {
|
||||
|
||||
export { defineNuxtComponent } from './component'
|
||||
export { useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData } from './asyncData'
|
||||
export type { AsyncDataOptions, AsyncData } from './asyncData'
|
||||
export type { AsyncDataOptions, AsyncData, AsyncDataRequestStatus } from './asyncData'
|
||||
export { useHydration } from './hydrate'
|
||||
export { callOnce } from './once'
|
||||
export { useState, clearNuxtState } from './state'
|
||||
@ -35,4 +35,5 @@ export type { NuxtAppManifest, NuxtAppManifestMeta } from './manifest'
|
||||
export type { ReloadNuxtAppOptions } from './chunk'
|
||||
export { reloadNuxtApp } from './chunk'
|
||||
export { useRequestURL } from './url'
|
||||
export { usePreviewMode } from './preview'
|
||||
export { useId } from './id'
|
||||
|
@ -7,6 +7,10 @@ export type LoadingIndicatorOpts = {
|
||||
duration: number
|
||||
/** @default 200 */
|
||||
throttle: number
|
||||
/** @default 500 */
|
||||
hideDelay: number
|
||||
/** @default 400 */
|
||||
resetDelay: number
|
||||
/**
|
||||
* You can provide a custom function to customize the progress estimation,
|
||||
* which is a function that receives the duration of the loading bar (above)
|
||||
@ -15,22 +19,13 @@ export type LoadingIndicatorOpts = {
|
||||
estimatedProgress?: (duration: number, elapsed: number) => number
|
||||
}
|
||||
|
||||
function _hide (isLoading: Ref<boolean>, progress: Ref<number>) {
|
||||
if (import.meta.client) {
|
||||
setTimeout(() => {
|
||||
isLoading.value = false
|
||||
setTimeout(() => { progress.value = 0 }, 400)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
export type LoadingIndicator = {
|
||||
_cleanup: () => void
|
||||
progress: Ref<number>
|
||||
isLoading: Ref<boolean>
|
||||
start: () => void
|
||||
set: (value: number) => void
|
||||
finish: () => void
|
||||
finish: (opts?: { force?: boolean }) => void
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
@ -40,7 +35,7 @@ function defaultEstimatedProgress (duration: number, elapsed: number): number {
|
||||
}
|
||||
|
||||
function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
||||
const { duration = 2000, throttle = 200 } = opts
|
||||
const { duration = 2000, throttle = 200, hideDelay = 500, resetDelay = 400 } = opts
|
||||
const getProgress = opts.estimatedProgress || defaultEstimatedProgress
|
||||
const nuxtApp = useNuxtApp()
|
||||
const progress = ref(0)
|
||||
@ -48,7 +43,9 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
||||
let done = false
|
||||
let rafId: number
|
||||
|
||||
let _throttle: any = null
|
||||
let throttleTimeout: number | NodeJS.Timeout
|
||||
let hideTimeout: number | NodeJS.Timeout
|
||||
let resetTimeout: number | NodeJS.Timeout
|
||||
|
||||
const start = () => set(0)
|
||||
|
||||
@ -60,7 +57,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
||||
clear()
|
||||
progress.value = at < 0 ? 0 : at
|
||||
if (throttle && import.meta.client) {
|
||||
_throttle = setTimeout(() => {
|
||||
throttleTimeout = setTimeout(() => {
|
||||
isLoading.value = true
|
||||
_startProgress()
|
||||
}, throttle)
|
||||
@ -70,19 +67,40 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function finish () {
|
||||
function _hide () {
|
||||
if (import.meta.client) {
|
||||
hideTimeout = setTimeout(() => {
|
||||
isLoading.value = false
|
||||
resetTimeout = setTimeout(() => { progress.value = 0 }, resetDelay)
|
||||
}, hideDelay)
|
||||
}
|
||||
}
|
||||
|
||||
function finish (opts: { force?: boolean } = {}) {
|
||||
progress.value = 100
|
||||
done = true
|
||||
clear()
|
||||
_hide(isLoading, progress)
|
||||
_clearTimeouts()
|
||||
if (opts.force) {
|
||||
progress.value = 0
|
||||
isLoading.value = false
|
||||
} else {
|
||||
_hide()
|
||||
}
|
||||
}
|
||||
|
||||
function _clearTimeouts () {
|
||||
if (import.meta.client) {
|
||||
clearTimeout(hideTimeout)
|
||||
clearTimeout(resetTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
function clear () {
|
||||
clearTimeout(_throttle)
|
||||
if (import.meta.client) {
|
||||
clearTimeout(throttleTimeout)
|
||||
cancelAnimationFrame(rafId)
|
||||
}
|
||||
_throttle = null
|
||||
}
|
||||
|
||||
function _startProgress () {
|
||||
@ -113,7 +131,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
||||
const unsubLoadingFinishHook = nuxtApp.hook('page:loading:end', () => {
|
||||
finish()
|
||||
})
|
||||
const unsubError = nuxtApp.hook('vue:error', finish)
|
||||
const unsubError = nuxtApp.hook('vue:error', () => finish())
|
||||
|
||||
_cleanup = () => {
|
||||
unsubError()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user