diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 68d3ffb9d6..3bbbe17f89 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts +FROM node:lts@sha256:fffa89e023a3351904c04284029105d9e2ac7020886d683775a298569591e5bb RUN apt-get update && \ apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \ diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..1ab482ad65 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,10 @@ +paths: + - 'packages/*/dist/**' + - 'packages/nuxt/bin/**' + - 'packages/schema/schema/**' +paths-ignore: + - 'test/**' + - '**/*.test.js' + - '**/*.test.ts' + - '**/*.test.tsx' + - '**/__tests__/**' diff --git a/.github/workflows/autofix-docs.yml b/.github/workflows/autofix-docs.yml index 177fcd67a5..c4350a2356 100644 --- a/.github/workflows/autofix-docs.yml +++ b/.github/workflows/autofix-docs.yml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index c354778edf..610d3ac48e 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 7a4afb38bb..e7e84f981a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -29,9 +29,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index b71b0e9baa..bda394c562 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -6,6 +6,8 @@ on: types: - closed +permissions: {} + jobs: cleanup: runs-on: ubuntu-latest @@ -20,14 +22,14 @@ jobs: gh extension install actions/gh-actions-cache echo "Fetching list of cache keys" - cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) + cacheKeysForPR=$(gh actions-cache list -R "$REPO" -B "$BRANCH" -L 100 | cut -f 1 ) ## Setting this to not fail the workflow while deleting cache keys. set +e echo "Deleting caches..." for cacheKey in $cacheKeysForPR do - gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + gh actions-cache delete "$cacheKey" -R "$REPO" -B "$BRANCH" --confirm done echo "Done" env: diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 78474f90e2..1b7e9b7ed9 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -6,9 +6,7 @@ on: - main - 3.x -permissions: - pull-requests: write - contents: write +permissions: {} concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.sha }} @@ -19,12 +17,16 @@ jobs: if: github.repository_owner == 'nuxt' && !contains(github.event.head_commit.message, 'v3.') && !contains(github.event.head_commit.message, 'v4.') runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22a43b06f6..0ba6897c9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,9 +37,9 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" @@ -57,7 +57,7 @@ jobs: run: pnpm build - name: Cache dist - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@84480863f228bb9747b473957fcc9e309aa96097 # v4.4.2 with: retention-days: 3 name: dist @@ -70,36 +70,30 @@ jobs: actions: read contents: read security-events: write - needs: - - build steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Initialize CodeQL - uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 with: - languages: javascript + config: | + paths: + - 'packages/*/src/**' + - 'packages/nuxt/bin/**' + - 'packages/schema/schema/**' + paths-ignore: + - 'test/**' + - '**/*.spec.ts' + - '**/*.test.ts' + - '**/__snapshots__/**' + languages: javascript-typescript queries: +security-and-quality - - name: Restore dist cache - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: dist - path: packages - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 with: - category: "/language:javascript" + category: "/language:javascript-typescript" typecheck: runs-on: ${{ matrix.os }} @@ -113,9 +107,9 @@ jobs: module: ["bundler", "node"] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" @@ -144,9 +138,9 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" @@ -168,9 +162,9 @@ jobs: needs: - build steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" @@ -197,7 +191,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] env: ["dev", "built"] - builder: ["vite", "webpack"] + builder: ["vite", "rspack", "webpack"] context: ["async", "default"] manifest: ["manifest-on", "manifest-off"] payload: ["json", "js"] @@ -205,12 +199,18 @@ jobs: exclude: - builder: "webpack" payload: "js" + - builder: "rspack" + payload: "js" - manifest: "manifest-off" payload: "js" - context: "default" payload: "js" - os: windows-latest payload: "js" + - env: "dev" + builder: "rspack" + - manifest: "manifest-off" + builder: "rspack" - env: "dev" builder: "webpack" - manifest: "manifest-off" @@ -219,9 +219,9 @@ jobs: timeout-minutes: 15 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: ${{ matrix.node }} cache: "pnpm" @@ -248,7 +248,7 @@ jobs: TEST_PAYLOAD: ${{ matrix.payload }} SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }} - - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.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 }} @@ -262,7 +262,6 @@ jobs: github.event_name == 'push' && github.repository == 'nuxt/nuxt' && !contains(github.event.head_commit.message, '[skip-release]') && - !startsWith(github.event.head_commit.message, 'chore') && !startsWith(github.event.head_commit.message, 'docs') needs: - lint @@ -272,11 +271,11 @@ jobs: timeout-minutes: 20 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" @@ -313,11 +312,11 @@ jobs: timeout-minutes: 20 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 2aae50705f..705f8d5c2d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: 'Dependency Review' uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 diff --git a/.github/workflows/docs-check-links.yml b/.github/workflows/docs-check-links.yml index e97699b3c4..81a8f72d81 100644 --- a/.github/workflows/docs-check-links.yml +++ b/.github/workflows/docs-check-links.yml @@ -19,17 +19,17 @@ jobs: steps: # Cache lychee results (e.g. to avoid hitting rate limits) - name: Restore lychee cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: .lycheecache key: cache-lychee-${{ github.sha }} restore-keys: cache-lychee- # check links with Lychee - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Lychee link checker - uses: lycheeverse/lychee-action@d4128702eae98bbc5ecf74df0165a8156c80920a # for v1.8.0 + uses: lycheeverse/lychee-action@731bf1a2affebd80fab6515ba61d2648a76929a4 # for v1.8.0 with: # arguments with file types to check args: >- diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bd12e4f3e7..1948fd8ab5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -21,9 +21,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/label-issue.yml b/.github/workflows/label-issue.yml new file mode 100644 index 0000000000..ebc2e3921b --- /dev/null +++ b/.github/workflows/label-issue.yml @@ -0,0 +1,28 @@ +name: chore + +on: + issues: + types: + - opened + +permissions: + issues: write + +jobs: + add-issue-labels: + name: Add labels + runs-on: ubuntu-latest + if: github.repository == 'nuxt/nuxt' + steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + // add 'pending triage' label if issue is created with no labels + if (context.payload.issue.labels.length === 0) { + github.rest.issues.addLabels({ + issue_number: context.payload.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['pending triage'] + }) + } diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index 82ec01db92..e0469e757f 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -8,6 +8,8 @@ on: - main - 3.x +permissions: {} + jobs: add-pr-labels: name: Add PR labels diff --git a/.github/workflows/lint-sherif.yml b/.github/workflows/lint-sherif.yml index 7774d89097..00d7bf5a68 100644 --- a/.github/workflows/lint-sherif.yml +++ b/.github/workflows/lint-sherif.yml @@ -23,9 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/lint-workflows.yml b/.github/workflows/lint-workflows.yml index 0820e5407f..a3d7781132 100644 --- a/.github/workflows/lint-workflows.yml +++ b/.github/workflows/lint-workflows.yml @@ -23,9 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions - name: Check workflow files - run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/590d3bd9dde0c91f7a66071d40eb84716526e5a6/scripts/download-actionlint.bash) 1.6.25 - ./actionlint -color -shellcheck="" + uses: docker://rhysd/actionlint:1.7.3@sha256:7617f05bd698cd2f1c3aedc05bc733ccec92cca0738f3e8722c32c5b42c70ae6 + with: + args: -color diff --git a/.github/workflows/notify-nuxt-bridge.yml b/.github/workflows/notify-nuxt-bridge.yml index fa97b12b95..b1f67c0509 100644 --- a/.github/workflows/notify-nuxt-bridge.yml +++ b/.github/workflows/notify-nuxt-bridge.yml @@ -4,6 +4,9 @@ on: types: [closed] paths: - "packages/nuxt/src/app/composables/**" + +permissions: {} + jobs: notify: if: github.event.pull_request.merged == true diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 7850e0f264..403ab99d59 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Ensure action is by maintainer - uses: octokit/request-action@872c5c97b3c85c23516a572f02b31401ef82415d # v2.3.1 + uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0 id: check_role with: route: GET /repos/nuxt/nuxt/collaborators/${{ github.event.comment.user.login }} @@ -39,7 +39,7 @@ jobs: GH_REPO: ${{ github.repository }} COMMENT_AT: ${{ github.event.comment.created_at }} run: | - pr="$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/${GH_REPO}/pulls/${PR_NUMBER})" + pr="$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/"${GH_REPO}"/pulls/"${PR_NUMBER}")" head_sha="$(echo "$pr" | jq -r .head.sha)" updated_at="$(echo "$pr" | jq -r .updated_at)" @@ -47,14 +47,14 @@ jobs: exit 1 fi - echo "head_sha=$head_sha" >> $GITHUB_OUTPUT - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ steps.pr.outputs.head_sha }} fetch-depth: 1 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 cache: "pnpm" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c597702bfd..1eda613a60 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 registry-url: "https://registry.npmjs.org/" diff --git a/.github/workflows/reproduire.yml b/.github/workflows/reproduire.yml index 4f02b2ce3a..d995f6d302 100644 --- a/.github/workflows/reproduire.yml +++ b/.github/workflows/reproduire.yml @@ -10,7 +10,7 @@ jobs: reproduire: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp with: label: needs reproduction diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 9a776f2e2d..0ad5cf877f 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: persist-credentials: false @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@84480863f228bb9747b473957fcc9e309aa96097 # v4.4.2 if: github.repository == 'nuxt/nuxt' && success() with: name: SARIF file @@ -68,7 +68,7 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 if: github.repository == 'nuxt/nuxt' && success() with: sarif_file: results.sarif diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index a3098e6f9e..d563f5045f 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -7,12 +7,12 @@ on: - edited - synchronize -permissions: - contents: read +permissions: {} jobs: semantic-pr: permissions: + contents: read pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR if: github.repository == 'nuxt/nuxt' && !startsWith(github.head_ref, 'v') @@ -26,6 +26,7 @@ jobs: kit nuxi nuxt + rspack schema test-utils ui-templates diff --git a/.github/workflows/stackblitz-link.yml b/.github/workflows/stackblitz-link.yml index be76b626c9..7da7c03a24 100644 --- a/.github/workflows/stackblitz-link.yml +++ b/.github/workflows/stackblitz-link.yml @@ -11,7 +11,7 @@ jobs: stackblitz: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: huang-julien/reproduire-sur-stackblitz@v1.0.1 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - uses: huang-julien/reproduire-sur-stackblitz@9ceccbfbb0f2f9a9a8db2d1f0dd909cf5cfe67aa # v1.0.2 with: reproduction-heading: '### Reproduction' diff --git a/docs/1.getting-started/1.introduction.md b/docs/1.getting-started/1.introduction.md index 2860e5bde3..90f7e84da0 100644 --- a/docs/1.getting-started/1.introduction.md +++ b/docs/1.getting-started/1.introduction.md @@ -2,7 +2,7 @@ title: Introduction description: Nuxt's goal is to make web development intuitive and performant with a great Developer Experience in mind. navigation: - icon: i-ph-info-duotone + icon: i-ph-info --- Nuxt is a free and [open-source framework](https://github.com/nuxt/nuxt) with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with [Vue.js](https://vuejs.org). diff --git a/docs/1.getting-started/10.deployment.md b/docs/1.getting-started/10.deployment.md index 9043f0035c..4b341e6a7d 100644 --- a/docs/1.getting-started/10.deployment.md +++ b/docs/1.getting-started/10.deployment.md @@ -1,7 +1,7 @@ --- title: 'Deployment' description: Learn how to deploy your Nuxt application to any hosting provider. -navigation.icon: i-ph-cloud-duotone +navigation.icon: i-ph-cloud --- A Nuxt application can be deployed on a Node.js server, pre-rendered for static hosting, or deployed to serverless or edge (CDN) environments. @@ -64,6 +64,10 @@ By default, the workload gets distributed to the workers with the round robin st :read-more{to="https://nitro.unjs.io/deploy/node" title="the Nitro documentation for node-server preset"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=0x1H6K5yOfs" target="\_blank"} +Watch Daniel Roe's short video on the topic. +:: + ## Static Hosting There are two ways to deploy a Nuxt application to any static hosting services: diff --git a/docs/1.getting-started/11.testing.md b/docs/1.getting-started/11.testing.md index aa5df5c718..dce433a768 100644 --- a/docs/1.getting-started/11.testing.md +++ b/docs/1.getting-started/11.testing.md @@ -1,7 +1,7 @@ --- title: Testing description: How to test your Nuxt application. -navigation.icon: i-ph-check-circle-duotone +navigation.icon: i-ph-check-circle --- ::tip @@ -10,7 +10,7 @@ If you are a module author, you can find more specific information in the [Modul Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via `@nuxt/test-utils`, a library of test utilities and configuration that currently powers the [tests we use on Nuxt itself](https://github.com/nuxt/nuxt/tree/main/test) and tests throughout the module ecosystem. -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=yGzwk9xi9gU" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=yGzwk9xi9gU" target="_blank"} Watch a video from Alexander Lichter about getting started with the `@nuxt/test-utils`. :: diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md index 096aa164a6..68cb635dc9 100644 --- a/docs/1.getting-started/12.upgrade.md +++ b/docs/1.getting-started/12.upgrade.md @@ -1,10 +1,9 @@ --- title: Upgrade Guide description: 'Learn how to upgrade to the latest Nuxt version.' -navigation.icon: i-ph-arrow-circle-up-duotone +navigation.icon: i-ph-arrow-circle-up --- - ## Upgrading Nuxt ### Latest release @@ -43,11 +42,11 @@ You can opt in to the 3.x branch nightly releases with `"nuxt": "npm:nuxt-nightl ## Testing Nuxt 4 -Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date). +The release date of Nuxt 4 is **to be announced**. It is dependent on having enough time after Nitro's major release to be properly tested in the community. You can follow progress towards Nitro's release in [this PR](https://github.com/unjs/nitro/pull/2521). -Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12+. +Until the release, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12+. -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"} Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already. :: @@ -73,6 +72,7 @@ export default defineNuxtConfig({ // resetAsyncDataToUndefined: true, // templateUtils: true, // relativeWatchPaths: true, + // normalizeComponentNames: false // defaults: { // useAsyncData: { // deep: true @@ -178,6 +178,7 @@ nuxt.config.ts 1. Create a new directory called `app/`. 1. Move your `assets/`, `components/`, `composables/`, `layouts/`, `middleware/`, `pages/`, `plugins/` and `utils/` folders under it, as well as `app.vue`, `error.vue`, `app.config.ts`. If you have an `app/router-options.ts` or `app/spa-loading-template.html`, these paths remain the same. 1. Make sure your `nuxt.config.ts`, `content/`, `layers/`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project. +1. Remember to update any third-party configuration files to work with the new directory structure, such as your `tailwindcss` or `eslint` configuration (if required - `@nuxtjs/tailwindcss` should automatically configure `tailwindcss` correctly). ::tip You can automate this migration by running `npx codemod@latest nuxt/4/file-structure` @@ -198,6 +199,43 @@ export default defineNuxtConfig({ }) ``` +#### Normalized Component Names + +๐Ÿšฆ **Impact Level**: Moderate + +Vue will now generate component names that match the Nuxt pattern for component naming. + +##### What Changed + +By default, if you haven't set it manually, Vue will assign a component name that matches +the filename of the component. + +```bash [Directory structure] +โ”œโ”€ components/ +โ”œโ”€โ”€โ”€ SomeFolder/ +โ”œโ”€โ”€โ”€โ”€โ”€ MyComponent.vue +``` + +In this case, the component name would be `MyComponent`, as far as Vue is concerned. If you wanted to use `` with it, or identify it in the Vue DevTools, you would need to use this name. + +But in order to auto-import it, you would need to use `SomeFolderMyComponent`. + +With this change, these two values will match, and Vue will generate a component name that matches the Nuxt pattern for component naming. + +##### Migration Steps + +Ensure that you use the updated name in any tests which use `findComponent` from `@vue/test-utils` and in any `` which depends on the name of your component. + +Alternatively, for now, you can disable this behaviour with: + +```ts twoslash [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + normalizeComponentNames: false + } +}) +``` + #### Shared Prerender Data ๐Ÿšฆ **Impact Level**: Medium @@ -407,7 +445,7 @@ We have already proactively migrated the public Nuxt modules which we are aware However, if you are a module author using the `builder:watch` hook and wishing to remain backwards/forwards compatible, you can use the following code to ensure that your code works the same in both Nuxt v3 and Nuxt v4: - ```diff +```diff + import { relative, resolve } from 'node:fs' // ... nuxt.hook('builder:watch', async (event, path) => { diff --git a/docs/1.getting-started/2.installation.md b/docs/1.getting-started/2.installation.md index 42bba1705b..128a135f57 100644 --- a/docs/1.getting-started/2.installation.md +++ b/docs/1.getting-started/2.installation.md @@ -1,7 +1,7 @@ --- title: 'Installation' description: 'Get started with Nuxt quickly with our online starters or start locally with your terminal.' -navigation.icon: i-ph-play-duotone +navigation.icon: i-ph-play --- ## Play Online @@ -94,7 +94,7 @@ bun run dev -o ``` :: -::tip{icon="i-ph-check-circle-duotone"} +::tip{icon="i-ph-check-circle"} Well done! A browser window should automatically open for . :: diff --git a/docs/1.getting-started/3.configuration.md b/docs/1.getting-started/3.configuration.md index 49a0822ecb..4cf9310b81 100644 --- a/docs/1.getting-started/3.configuration.md +++ b/docs/1.getting-started/3.configuration.md @@ -1,10 +1,9 @@ --- title: Configuration description: Nuxt is configured with sensible defaults to make you productive. -navigation.icon: i-ph-gear-duotone +navigation.icon: i-ph-gear --- - By default, Nuxt is configured to cover most use cases. The [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file can override or extend this default configuration. ## Nuxt Configuration @@ -29,7 +28,7 @@ Every option is described in the **Configuration Reference**. You don't have to use TypeScript to build an application with Nuxt. However, it is strongly recommended to use the `.ts` extension for the `nuxt.config` file. This way you can benefit from hints in your IDE to avoid typos and mistakes while editing your configuration. :: -### Environment overrides +### Environment Overrides You can configure fully typed, per-environment overrides in your nuxt.config @@ -46,7 +45,7 @@ export default defineNuxtConfig({ }) ``` -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=DFZI2iVCrNc" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=DFZI2iVCrNc" target="_blank"} Watch a video from Alexander Lichter about the env-aware `nuxt.config.ts`. :: diff --git a/docs/1.getting-started/3.views.md b/docs/1.getting-started/3.views.md index a3275b05c2..e169bdfa66 100644 --- a/docs/1.getting-started/3.views.md +++ b/docs/1.getting-started/3.views.md @@ -1,7 +1,7 @@ --- title: 'Views' description: 'Nuxt provides several component layers to implement the user interface of your application.' -navigation.icon: i-ph-layout-duotone +navigation.icon: i-ph-layout --- ## `app.vue` diff --git a/docs/1.getting-started/4.assets.md b/docs/1.getting-started/4.assets.md index ac0b964a2e..a46f973deb 100644 --- a/docs/1.getting-started/4.assets.md +++ b/docs/1.getting-started/4.assets.md @@ -1,7 +1,7 @@ --- title: 'Assets' description: 'Nuxt offers two options for your assets.' -navigation.icon: i-ph-image-duotone +navigation.icon: i-ph-image --- Nuxt uses two directories to handle assets like stylesheets, fonts or images. diff --git a/docs/1.getting-started/4.styling.md b/docs/1.getting-started/4.styling.md index 61ae9da785..d780ad3449 100644 --- a/docs/1.getting-started/4.styling.md +++ b/docs/1.getting-started/4.styling.md @@ -1,7 +1,7 @@ --- title: 'Styling' description: 'Learn how to style your Nuxt application.' -navigation.icon: i-ph-palette-duotone +navigation.icon: i-ph-palette --- Nuxt is highly flexible when it comes to styling. Write your own styles, or reference local and external stylesheets. @@ -530,7 +530,7 @@ export default defineNuxtConfig({ hooks: { 'build:manifest': (manifest) => { // find the app entry, css list - const css = manifest['node_modules/nuxt/dist/app/entry.js']?.css + const css = Object.values(manifest).find(options => options.isEntry)?.css if (css) { // start from the end of the array and go to the beginning for (let i = css.length - 1; i >= 0; i--) { diff --git a/docs/1.getting-started/5.routing.md b/docs/1.getting-started/5.routing.md index e39d5d446b..e88d9e8c24 100644 --- a/docs/1.getting-started/5.routing.md +++ b/docs/1.getting-started/5.routing.md @@ -1,7 +1,7 @@ --- title: 'Routing' description: Nuxt file-system routing creates a route for every file in the pages/ directory. -navigation.icon: i-ph-signpost-duotone +navigation.icon: i-ph-signpost --- One core feature of Nuxt is the file system router. Every Vue file inside the [`pages/`](/docs/guide/directory-structure/pages) directory creates a corresponding URL (or route) that displays the contents of the file. By using dynamic imports for each page, Nuxt leverages code-splitting to ship the minimum amount of JavaScript for the requested route. @@ -15,7 +15,7 @@ This file system routing uses naming conventions to create dynamic and nested ro ::code-group ```bash [Directory Structure] -| pages/ +-| pages/ ---| about.vue ---| index.vue ---| posts/ diff --git a/docs/1.getting-started/5.seo-meta.md b/docs/1.getting-started/5.seo-meta.md index 91b8fed31f..46a3f2298d 100644 --- a/docs/1.getting-started/5.seo-meta.md +++ b/docs/1.getting-started/5.seo-meta.md @@ -1,7 +1,7 @@ --- title: SEO and Meta description: Improve your Nuxt app's SEO with powerful head config, composables and components. -navigation.icon: i-ph-file-search-duotone +navigation.icon: i-ph-file-search --- ## Defaults diff --git a/docs/1.getting-started/5.transitions.md b/docs/1.getting-started/5.transitions.md index ccabe0ed6d..dcef4b284e 100644 --- a/docs/1.getting-started/5.transitions.md +++ b/docs/1.getting-started/5.transitions.md @@ -1,7 +1,7 @@ --- title: 'Transitions' description: Apply transitions between pages and layouts with Vue or native browser View Transitions. -navigation.icon: i-ph-exclude-square-duotone +navigation.icon: i-ph-exclude-square --- ::note diff --git a/docs/1.getting-started/6.data-fetching.md b/docs/1.getting-started/6.data-fetching.md index 39d82835c2..9e4e0a47ae 100644 --- a/docs/1.getting-started/6.data-fetching.md +++ b/docs/1.getting-started/6.data-fetching.md @@ -1,28 +1,24 @@ --- title: 'Data fetching' description: Nuxt provides composables to handle data fetching within your application. -navigation.icon: i-ph-plugs-connected-duotone +navigation.icon: i-ph-plugs-connected --- Nuxt comes with two composables and a built-in library to perform data-fetching in browser or server environments: `useFetch`, [`useAsyncData`](/docs/api/composables/use-async-data) and `$fetch`. In a nutshell: -- [`useFetch`](/docs/api/composables/use-fetch) is the most straightforward way to handle data fetching in a component setup function. -- [`$fetch`](/docs/api/utils/dollarfetch) is great to make network requests based on user interaction. -- [`useAsyncData`](/docs/api/composables/use-async-data), combined with `$fetch`, offers more fine-grained control. +- [`$fetch`](/docs/api/utils/dollarfetch) is the simplest way to make a network request. +- [`useFetch`](/docs/api/composables/use-fetch) is wrapper around `$fetch` that fetches data only once in [universal rendering](/docs/guide/concepts/rendering#universal-rendering). +- [`useAsyncData`](/docs/api/composables/use-async-data) is similar to `useFetch` but offers more fine-grained control. Both `useFetch` and `useAsyncData` share a common set of options and patterns that we will detail in the last sections. -Before that, it's imperative to know why these composables exist in the first place. +## The need for `useFetch` and `useAsyncData` -## Why use specific composables for data fetching? +Nuxt is a framework which can run isomorphic (or universal) code in both server and client environments. If the [`$fetch` function](/docs/api/utils/dollarfetch) is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated). This can cause hydration issues, increase the time to interactivity and cause unpredictable behavior. -Nuxt is a framework which can run isomorphic (or universal) code in both server and client environments. If the [`$fetch` function](/docs/api/utils/dollarfetch) is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated). This is why Nuxt offers specific data fetching composables so data is fetched only once. - -### Network calls duplication - -The [`useFetch`](/docs/api/composables/use-fetch) and [`useAsyncData`](/docs/api/composables/use-async-data) composables ensure that once an API call is made on the server, the data is properly forwarded to the client in the payload. +The [`useFetch`](/docs/api/composables/use-fetch) and [`useAsyncData`](/docs/api/composables/use-async-data) composables solve this problem by ensuring that if an API call is made on the server, the data is forwarded to the client in the payload. The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/docs/api/composables/use-nuxt-app#payload). It is used on the client to avoid refetching the same data when the code is executed in the browser [during hydration](/docs/guide/concepts/rendering#universal-rendering). @@ -30,41 +26,45 @@ The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/ Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the **Payload tab**. :: +```vue [app.vue] + + + +``` + +In the example above, `useFetch` would make sure that the request would occur in the server and is properly forwarded to the browser. `$fetch` has no such mechanism and is a better option to use when the request is solely made from the browser. + ### Suspense -Nuxt uses Vueโ€™s [``](https://vuejs.org/guide/built-ins/suspense) component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-calls basis. +Nuxt uses Vueโ€™s [``](https://vuejs.org/guide/built-ins/suspense) component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-call basis. ::note You can add the [``](/docs/api/components/nuxt-loading-indicator) to add a progress bar between page navigations. :: -## `useFetch` - -The [`useFetch`](/docs/api/composables/use-fetch) composable is the most straightforward way to perform data fetching. - -```vue twoslash [app.vue] - - - -``` - -This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility. - -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"} -Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way! -:: - -:read-more{to="/docs/api/composables/use-fetch"} - -:link-example{to="/docs/examples/features/data-fetching"} - ## `$fetch` -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. +Nuxt includes the [ofetch](https://github.com/unjs/ofetch) library, and is auto-imported as the `$fetch` alias globally across your application. ```vue twoslash [pages/todos.vue] + + +``` + +This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility. + +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"} +Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way! +:: + +:read-more{to="/docs/api/composables/use-fetch"} + +:link-example{to="/docs/examples/features/data-fetching"} + ## `useAsyncData` The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved. @@ -97,7 +121,7 @@ The `useAsyncData` composable is responsible for wrapping async logic and return It's developer experience sugar for the most common use case. :: -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=0X-aOpSGabA" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=0X-aOpSGabA" target="_blank"} Watch a video from Alexander Lichter to dig deeper into the difference between `useFetch` and `useAsyncData`. :: diff --git a/docs/1.getting-started/7.state-management.md b/docs/1.getting-started/7.state-management.md index 46e7b08ba2..94bf67f6b9 100644 --- a/docs/1.getting-started/7.state-management.md +++ b/docs/1.getting-started/7.state-management.md @@ -1,14 +1,14 @@ --- title: 'State Management' description: Nuxt provides powerful state management libraries and the useState composable to create a reactive and SSR-friendly shared state. -navigation.icon: i-ph-database-duotone +navigation.icon: i-ph-database --- Nuxt provides the [`useState`](/docs/api/composables/use-state) composable to create a reactive and SSR-friendly shared state across components. [`useState`](/docs/api/composables/use-state) is an SSR-friendly [`ref`](https://vuejs.org/api/reactivity-core.html#ref) replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key. -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"} Watch a video from Alexander Lichter about why and when to use `useState()`. :: @@ -27,7 +27,7 @@ Never define `const state = ref()` outside of ` ``` @@ -60,11 +60,15 @@ That means that (with very few exceptions) you cannot use them outside a Nuxt pl If you get an error message like `Nuxt instance is unavailable` then it probably means you are calling a Nuxt composable in the wrong place in the Vue or Nuxt lifecycle. -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=ofuKRZLtOdY" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=ofuKRZLtOdY" target="_blank"} Watch a video from Alexander Lichter about handling async code in composables and fixing `Nuxt instance is unavailable` in your app. :: -::read-more{to="/docs/guide/going-further/experimental-features#asynccontext" icon="i-ph-star-duotone"} +::tip +When using a composable that requires the Nuxt context inside a non-SFC component, you need to wrap your component with `defineNuxtComponent` instead of `defineComponent` +:: + +::read-more{to="/docs/guide/going-further/experimental-features#asynccontext" icon="i-ph-star"} Checkout the `asyncContext` experimental feature to use Nuxt composables in async functions. :: @@ -178,6 +182,6 @@ export default defineNuxtConfig({ }) ``` -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=FT2LQJ2NvVI" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=FT2LQJ2NvVI" target="_blank"} Watch a video from Alexander Lichter on how to easily set up custom auto imports. :: diff --git a/docs/2.guide/1.concepts/3.rendering.md b/docs/2.guide/1.concepts/3.rendering.md index ee0d2bd3b6..535b8bf57a 100644 --- a/docs/2.guide/1.concepts/3.rendering.md +++ b/docs/2.guide/1.concepts/3.rendering.md @@ -11,18 +11,41 @@ By default, Nuxt uses **universal rendering** to provide better user experience, ## Universal Rendering -When the browser requests a URL with universal (server-side + client-side) rendering enabled, the server returns a fully rendered HTML page to the browser. Whether the page has been generated in advance and cached or is rendered on the fly, at some point, Nuxt has run the JavaScript (Vue.js) code in a server environment, producing an HTML document. Users immediately get the content of our application, contrary to client-side rendering. This step is similar to traditional **server-side rendering** performed by PHP or Ruby applications. +This step is similar to traditional **server-side rendering** performed by PHP or Ruby applications. When the browser requests a URL with universal rendering enabled, Nuxt runs the JavaScript (Vue.js) code in a server environment and returns a fully rendered HTML page to the browser. Nuxt may also return a fully rendered HTML page from a cache if the page was generated in advance. Users immediately get the entirety of the initial content of the application, contrary to client-side rendering. -To not lose the benefits of the client-side rendering method, such as dynamic interfaces and pages transitions, the Client (browser) loads the JavaScript code that runs on the Server in the background once the HTML document has been downloaded. The browser interprets it again (hence **Universal rendering**) and Vue.js takes control of the document and enables interactivity. - -Making a static page interactive in the browser is called "Hydration". +Once the HTML document has been downloaded, the browser interprets this and Vue.js takes control of the document. The same JavaScript code that once ran on the server runs on the client (browser) **again** in the background now enabling interactivity (hence **Universal rendering**) by binding its listeners to the HTML. This is called **Hydration**. When hydration is complete, the page can enjoy benefits such as dynamic interfaces and page transitions. Universal rendering allows a Nuxt application to provide quick page load times while preserving the benefits of client-side rendering. Furthermore, as the content is already present in the HTML document, crawlers can index it without overhead. ![Users can access the static content when the HTML document is loaded. Hydration then allows page's interactivity](/assets/docs/concepts/rendering/ssr.svg) +**What's server-rendered and what's client-rendered?** + +It is normal to ask which parts of a Vue file runs on the server and/or the client in universal rendering mode. + +```vue [app.vue] + + + +``` + +On the initial request, the `counter` ref is initialized in the server since it is rendered inside the `

` tag. The contents of `handleClick` is never executed here. During hydration in the browser, the `counter` ref is re-initialized. The `handleClick` finally binds itself to the button; Therefore it is reasonable to deduce that the body of `handleClick` will always run in a browser environment. + +[Middlewares](/docs/guide/directory-structure/middleware) and [pages](/docs/guide/directory-structure/pages) run in the server and on the client during hydration. [Plugins](/docs/guide/directory-structure/plugins) can be rendered on the server or client or both. [Components](/docs/guide/directory-structure/components) can be forced to run on the client only as well. [Composables](/docs/guide/directory-structure/composables) and [utilities](/docs/guide/directory-structure/utils) are rendered based on the context of their usage. + **Benefits of server-side rendering:** -- **Performance**: Users can get immediate access to the page's content because browsers can display static content much faster than JavaScript-generated content. At the same time, Nuxt preserves the interactivity of a web application when the hydration process happens. +- **Performance**: Users can get immediate access to the page's content because browsers can display static content much faster than JavaScript-generated content. At the same time, Nuxt preserves the interactivity of a web application during the hydration process. - **Search Engine Optimization**: Universal rendering delivers the entire HTML content of the page to the browser as a classic server application. Web crawlers can directly index the page's content, which makes Universal rendering a great choice for any content that you want to index quickly. **Downsides of server-side rendering:** @@ -116,7 +139,7 @@ export default defineNuxtConfig({ '/': { prerender: true }, // Products page generated on demand, revalidates in background, cached until API response changes '/products': { swr: true }, - // Product page generated on demand, revalidates in background, cached for 1 hour (3600 seconds) + // Product pages generated on demand, revalidates in background, cached for 1 hour (3600 seconds) '/products/**': { swr: 3600 }, // Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds) '/blog': { isr: 3600 }, @@ -136,7 +159,7 @@ export default defineNuxtConfig({ The different properties you can use are the following: - `redirect: string`{lang=ts} - Define server-side redirects. -- `ssr: boolean`{lang=ts} - Disables server-side rendering for sections of your app and make them SPA-only with `ssr: false` +- `ssr: boolean`{lang=ts} - Disables server-side rendering of the HTML for sections of your app and make them render only in the browser 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. diff --git a/docs/2.guide/1.concepts/4.server-engine.md b/docs/2.guide/1.concepts/4.server-engine.md index f721299477..b668b85c10 100644 --- a/docs/2.guide/1.concepts/4.server-engine.md +++ b/docs/2.guide/1.concepts/4.server-engine.md @@ -51,7 +51,7 @@ You can access these types when using [`$fetch()`](/docs/api/utils/dollarfetch) Nitro produces a standalone server dist that is independent of `node_modules`. -The server in Nuxt 2 is not standalone and requires part of Nuxt core to be involved by running `nuxt start` (with the [`nuxt-start`](https://www.npmjs.com/package/nuxt-start) or [`nuxt`](https://www.npmjs.com/package/nuxt) distributions) or custom programmatic usage, which is fragile and prone to breakage and not suitable for serverless and service-worker environments. +The server in Nuxt 2 is not standalone and requires part of Nuxt core to be involved by running `nuxt start` (with the [`nuxt-start`](https://www.npmjs.com/package/nuxt-start) or [`nuxt`](https://www.npmjs.com/package/nuxt) distributions) or custom programmatic usage, which is fragile and prone to breakage and not suitable for serverless and service worker environments. Nuxt generates this dist when running `nuxt build` into a [`.output`](/docs/guide/directory-structure/output) directory. diff --git a/docs/2.guide/1.concepts/8.typescript.md b/docs/2.guide/1.concepts/8.typescript.md index 31d247b7e8..a06015dc98 100644 --- a/docs/2.guide/1.concepts/8.typescript.md +++ b/docs/2.guide/1.concepts/8.typescript.md @@ -61,7 +61,7 @@ This file contains the recommended basic TypeScript configuration for your proje [Read more about how to extend this configuration](/docs/guide/directory-structure/tsconfig). -::tip{icon="i-ph-video-duotone" to="https://youtu.be/umLI7SlPygY" target="_blank"} +::tip{icon="i-ph-video" to="https://youtu.be/umLI7SlPygY" target="_blank"} Watch a video from Daniel Roe explaining built-in Nuxt aliases. :: diff --git a/docs/2.guide/1.concepts/9.code-style.md b/docs/2.guide/1.concepts/9.code-style.md index 2a6a32afb3..edbfa3c490 100644 --- a/docs/2.guide/1.concepts/9.code-style.md +++ b/docs/2.guide/1.concepts/9.code-style.md @@ -7,7 +7,7 @@ description: "Nuxt supports ESLint out of the box" The recommended approach for Nuxt is to enable ESLint support using the [`@nuxt/eslint`](https://eslint.nuxt.com/packages/module) module, that will setup project-aware ESLint configuration for you. -:::callout{icon="i-ph-lightbulb-duotone"} +:::callout{icon="i-ph-lightbulb"} The module is designed for the [new ESLint flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) with is the [default format since ESLint v9](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/). If you are using the legacy `.eslintrc` config, you will need to [configure manually with `@nuxt/eslint-config`](https://eslint.nuxt.com/packages/config#legacy-config-format). We highly recommend you to migrate over the flat config to be future-proof. diff --git a/docs/2.guide/1.concepts/_dir.yml b/docs/2.guide/1.concepts/_dir.yml index 5ba97e4962..83d82a0dee 100644 --- a/docs/2.guide/1.concepts/_dir.yml +++ b/docs/2.guide/1.concepts/_dir.yml @@ -1,3 +1,3 @@ title: Key Concepts titleTemplate: '%s ยท Nuxt Concepts' -icon: i-ph-medal-duotone +icon: i-ph-medal diff --git a/docs/2.guide/2.directory-structure/0.nuxt.md b/docs/2.guide/2.directory-structure/0.nuxt.md index c60627ca9b..603b9108ee 100644 --- a/docs/2.guide/2.directory-structure/0.nuxt.md +++ b/docs/2.guide/2.directory-structure/0.nuxt.md @@ -2,7 +2,7 @@ title: ".nuxt" description: "Nuxt uses the .nuxt/ directory in development to generate your Vue application." head.title: ".nuxt/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- ::important diff --git a/docs/2.guide/2.directory-structure/0.output.md b/docs/2.guide/2.directory-structure/0.output.md index 2db69102fe..dc0f868d65 100644 --- a/docs/2.guide/2.directory-structure/0.output.md +++ b/docs/2.guide/2.directory-structure/0.output.md @@ -2,7 +2,7 @@ title: ".output" description: "Nuxt creates the .output/ directory when building your application for production." head.title: ".output/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- ::important diff --git a/docs/2.guide/2.directory-structure/1.assets.md b/docs/2.guide/2.directory-structure/1.assets.md index b274baef9e..edf9c52bd6 100644 --- a/docs/2.guide/2.directory-structure/1.assets.md +++ b/docs/2.guide/2.directory-structure/1.assets.md @@ -2,7 +2,7 @@ title: "assets" description: "The assets/ directory is used to add all the website's assets that the build tool will process." head.title: "assets/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- The directory usually contains the following types of files: diff --git a/docs/2.guide/2.directory-structure/1.components.md b/docs/2.guide/2.directory-structure/1.components.md index 04dad5527b..0656b96e75 100644 --- a/docs/2.guide/2.directory-structure/1.components.md +++ b/docs/2.guide/2.directory-structure/1.components.md @@ -2,15 +2,15 @@ title: "components" head.title: "components/" description: "The components/ directory is where you put all your Vue components." -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- Nuxt automatically imports any components in this directory (along with components that are registered by any modules you may be using). ```bash [Directory Structure] -| components/ ---| AppHeader.vue ---| AppFooter.vue +-| components/ +---| AppHeader.vue +---| AppFooter.vue ``` ```html [app.vue] @@ -28,10 +28,10 @@ Nuxt automatically imports any components in this directory (along with componen If you have a component in nested directories such as: ```bash [Directory Structure] -| components/ ---| base/ -----| foo/ -------| Button.vue +-| components/ +---| base/ +-----| foo/ +-------| Button.vue ``` ... then the component's name will be based on its own path directory and filename, with duplicate segments being removed. Therefore, the component's name will be: @@ -82,6 +82,10 @@ const MyButton = resolveComponent('MyButton') If you are using `resolveComponent` to handle dynamic components, make sure not to insert anything but the name of the component, which must be a string and not a variable. :: +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=4kq8E5IUM2U" target="\_blank"} +Watch Daniel Roe's short video about `resolveComponent`. +:: + Alternatively, though not recommended, you can register all your components globally, which will create async chunks for all your components and make them available throughout your application. ```diff @@ -166,6 +170,10 @@ export default defineNuxtConfig({ }) ``` +::note +Any nested directories need to be added first as they are scanned in order. +:: + ## npm Packages If you want to auto-import components from an npm package, you can use [`addComponent`](/docs/api/kit/components#addcomponent) in a [local module](/docs/guide/directory-structure/modules) to register them. @@ -198,10 +206,6 @@ export default defineNuxtModule({ :: -::note -Any nested directories need to be added first as they are scanned in order. -:: - ## Component Extensions By default, any file with an extension specified in the [extensions key of `nuxt.config.ts`](/docs/api/nuxt-config#extensions) is treated as a component. @@ -254,11 +258,11 @@ Server components allow server-rendering individual components within your clien Server components can either be used on their own or paired with a [client component](#paired-with-a-client-component). -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=u1yyXe86xJM" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=u1yyXe86xJM" target="_blank"} Watch Learn Vue video about Nuxt Server Components. :: -::tip{icon="i-ph-article-duotone" to="https://roe.dev/blog/nuxt-server-components" target="_blank"} +::tip{icon="i-ph-article" to="https://roe.dev/blog/nuxt-server-components" target="_blank"} Read Daniel Roe's guide to Nuxt Server Components. :: @@ -281,8 +285,8 @@ export default defineNuxtConfig({ Now you can register server-only components with the `.server` suffix and use them anywhere in your application automatically. ```bash [Directory Structure] -| components/ ---| HighlightedMarkdown.server.vue +-| components/ +---| HighlightedMarkdown.server.vue ``` ```vue [pages/example.vue] @@ -355,9 +359,9 @@ Slots can be interactive and are wrapped within a `

` with `display: content In this case, the `.server` + `.client` components are two 'halves' of a component and can be used in advanced use cases for separate implementations of a component on server and client side. ```bash [Directory Structure] -| components/ ---| Comments.client.vue ---| Comments.server.vue +-| components/ +---| Comments.client.vue +---| Comments.server.vue ``` ```vue [pages/example.vue] @@ -385,15 +389,15 @@ You can use the `components:dirs` hook to extend the directory list without requ Imagine a directory structure like this: ```bash [Directory Structure] -| node_modules/ +-| node_modules/ ---| awesome-ui/ -------| components/ ----------| Alert.vue ----------| Button.vue -------| nuxt.js -| pages/ +-----| components/ +-------| Alert.vue +-------| Button.vue +-----| nuxt.js +-| pages/ ---| index.vue -| nuxt.config.js +-| nuxt.config.js ``` Then in `awesome-ui/nuxt.js` you can use the `components:dirs` hook: diff --git a/docs/2.guide/2.directory-structure/1.composables.md b/docs/2.guide/2.directory-structure/1.composables.md index 5adae8bd29..ed96746656 100644 --- a/docs/2.guide/2.directory-structure/1.composables.md +++ b/docs/2.guide/2.directory-structure/1.composables.md @@ -2,7 +2,7 @@ title: 'composables' head.title: 'composables/' description: Use the composables/ directory to auto-import your Vue composables into your application. -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- ## Usage @@ -85,11 +85,11 @@ export const useHello = () => { Nuxt only scans files at the top level of the [`composables/` directory](/docs/guide/directory-structure/composables), e.g.: ```bash [Directory Structure] -| composables/ +-| composables/ ---| index.ts // scanned ---| useFoo.ts // scanned ------| nested/ --------| utils.ts // not scanned +---| nested/ +-----| utils.ts // not scanned ``` Only `composables/index.ts` and `composables/useFoo.ts` would be searched for imports. diff --git a/docs/2.guide/2.directory-structure/1.content.md b/docs/2.guide/2.directory-structure/1.content.md index 361a5971c8..5800a362a0 100644 --- a/docs/2.guide/2.directory-structure/1.content.md +++ b/docs/2.guide/2.directory-structure/1.content.md @@ -2,7 +2,7 @@ title: 'content' head.title: 'content/' description: Use the content/ directory to create a file-based CMS for your application. -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- [Nuxt Content](https://content.nuxt.com) reads the [`content/` directory](/docs/guide/directory-structure/content) in your project and parses `.md`, `.yml`, `.csv` and `.json` files to create a file-based CMS for your application. diff --git a/docs/2.guide/2.directory-structure/1.layouts.md b/docs/2.guide/2.directory-structure/1.layouts.md index ee95033f8e..4aa9fc98f8 100644 --- a/docs/2.guide/2.directory-structure/1.layouts.md +++ b/docs/2.guide/2.directory-structure/1.layouts.md @@ -2,10 +2,10 @@ title: "layouts" head.title: "layouts/" description: "Nuxt provides a layouts framework to extract common UI patterns into reusable layouts." -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- -::tip{icon="i-ph-rocket-launch-duotone" color="gray" } +::tip{icon="i-ph-rocket-launch" color="gray" } For best performance, components placed in this directory will be automatically loaded via asynchronous import when used. :: diff --git a/docs/2.guide/2.directory-structure/1.middleware.md b/docs/2.guide/2.directory-structure/1.middleware.md index 64e66ecf6d..d17c6ceb7c 100644 --- a/docs/2.guide/2.directory-structure/1.middleware.md +++ b/docs/2.guide/2.directory-structure/1.middleware.md @@ -2,7 +2,7 @@ title: "middleware" description: "Nuxt provides middleware to run code before navigating to a particular route." head.title: "middleware/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- Nuxt provides a customizable **route middleware** framework you can use throughout your application, ideal for extracting code that you want to run before navigating to a particular route. @@ -72,11 +72,11 @@ Middleware runs in the following order: For example, assuming you have the following middleware and component: -```text [middleware/ directory] -middleware/ ---| analytics.global.ts ---| setup.global.ts ---| auth.ts +```bash [middleware/ directory] +-| middleware/ +---| analytics.global.ts +---| setup.global.ts +---| auth.ts ``` ```vue twoslash [pages/profile.vue] @@ -105,11 +105,11 @@ By default, global middleware is executed alphabetically based on the filename. However, there may be times you want to define a specific order. For example, in the last scenario, `setup.global.ts` may need to run before `analytics.global.ts`. In that case, we recommend prefixing global middleware with 'alphabetical' numbering. -```text [Directory structure] -middleware/ ---| 01.setup.global.ts ---| 02.analytics.global.ts ---| auth.ts +```bash [Directory structure] +-| middleware/ +---| 01.setup.global.ts +---| 02.analytics.global.ts +---| auth.ts ``` ::note diff --git a/docs/2.guide/2.directory-structure/1.modules.md b/docs/2.guide/2.directory-structure/1.modules.md index f544e815bc..0321c56f69 100644 --- a/docs/2.guide/2.directory-structure/1.modules.md +++ b/docs/2.guide/2.directory-structure/1.modules.md @@ -2,7 +2,7 @@ title: 'modules' head.title: 'modules/' description: Use the modules/ directory to automatically register local modules within your application. -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- It is a good place to place any local modules you develop while building your application. @@ -61,6 +61,6 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/creating-your-first-module-from-scratch?friend=nuxt" target="_blank"} Watch Vue School video about Nuxt private modules. :: diff --git a/docs/2.guide/2.directory-structure/1.node_modules.md b/docs/2.guide/2.directory-structure/1.node_modules.md index 13c7780e6a..afdb2d753a 100644 --- a/docs/2.guide/2.directory-structure/1.node_modules.md +++ b/docs/2.guide/2.directory-structure/1.node_modules.md @@ -2,7 +2,7 @@ title: "node_modules" description: "The package manager stores the dependencies of your project in the node_modules/ directory." head.title: "node_modules/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- The package manager ([`npm`](https://docs.npmjs.com/cli/commands/npm) or [`yarn`](https://yarnpkg.com) or [`pnpm`](https://pnpm.io/cli/install) or [`bun`](https://bun.sh/package-manager)) creates this directory to store the dependencies of your project. diff --git a/docs/2.guide/2.directory-structure/1.pages.md b/docs/2.guide/2.directory-structure/1.pages.md index 1367f6f871..0efb13daac 100644 --- a/docs/2.guide/2.directory-structure/1.pages.md +++ b/docs/2.guide/2.directory-structure/1.pages.md @@ -2,7 +2,7 @@ title: "pages" description: "Nuxt provides file-based routing to create routes within your web application." head.title: "pages/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- ::note @@ -159,7 +159,7 @@ Example: ```bash [Directory Structure] -| pages/ ---| parent/ -------| child.vue +-----| child.vue ---| parent.vue ``` @@ -408,7 +408,7 @@ However, you can use [Nuxt Layers](/docs/getting-started/layers) to create group ```bash [Directory Structure] -| some-app/ ---| nuxt.config.ts ----| pages +---| pages/ -----| app-page.vue -| nuxt.config.ts ``` diff --git a/docs/2.guide/2.directory-structure/1.plugins.md b/docs/2.guide/2.directory-structure/1.plugins.md index d76acdade7..572675300c 100644 --- a/docs/2.guide/2.directory-structure/1.plugins.md +++ b/docs/2.guide/2.directory-structure/1.plugins.md @@ -2,7 +2,7 @@ title: "plugins" description: "Nuxt has a plugins system to use Vue plugins and more at the creation of your Vue application." head.title: "plugins/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- Nuxt automatically reads the files in the `plugins/` directory and loads them at the creation of the Vue application. @@ -76,7 +76,7 @@ export default defineNuxtPlugin({ }) ``` -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=2aXZyXB1QGQ" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=2aXZyXB1QGQ" target="_blank"} Watch a video from Alexander Lichter about the Object Syntax for Nuxt plugins. :: @@ -108,7 +108,7 @@ In case you're new to 'alphabetical' numbering, remember that filenames are sort ### Parallel Plugins -By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait the end of the plugin's execution before loading the next plugin. +By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait until the end of the plugin's execution before loading the next plugin. ```ts twoslash [plugins/my-plugin.ts] export default defineNuxtPlugin({ diff --git a/docs/2.guide/2.directory-structure/1.public.md b/docs/2.guide/2.directory-structure/1.public.md index da5daa87bf..894654a962 100644 --- a/docs/2.guide/2.directory-structure/1.public.md +++ b/docs/2.guide/2.directory-structure/1.public.md @@ -2,7 +2,7 @@ title: "public" description: "The public/ directory is used to serve your website's static assets." head.title: "public/" -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- Files contained within the `public/` directory are served at the root and are not modified by the build process. This is suitable for files that have to keep their names (e.g. `robots.txt`) _or_ likely won't change (e.g. `favicon.ico`). diff --git a/docs/2.guide/2.directory-structure/1.server.md b/docs/2.guide/2.directory-structure/1.server.md index 37e9cfe1f6..cbf68709d9 100644 --- a/docs/2.guide/2.directory-structure/1.server.md +++ b/docs/2.guide/2.directory-structure/1.server.md @@ -2,7 +2,7 @@ title: server head.title: 'server/' description: The server/ directory is used to register API and server handlers to your application. -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- Nuxt automatically scans files inside these directories to register API and server handlers with Hot Module Replacement (HMR) support. @@ -347,6 +347,22 @@ export default defineEventHandler((event) => { }) ``` +### Forwarding Context & Headers + +By default, neither the headers from the incoming request nor the request context are forwarded when +making fetch requests in server routes. You can use `event.$fetch` to forward the request context and headers when making fetch requests in server routes. + +```ts [server/api/forward.ts] +export default defineEventHandler((event) => { + return event.$fetch('/api/forwarded') +}) +``` + +::note +Headers that are **not meant to be forwarded** will **not be included** in the request. These headers include, for example: +`transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `expect`, `host`, `accept` +:: + ## Advanced Usage ### Nitro Config diff --git a/docs/2.guide/2.directory-structure/1.utils.md b/docs/2.guide/2.directory-structure/1.utils.md index 74d847d675..f7148c93fb 100644 --- a/docs/2.guide/2.directory-structure/1.utils.md +++ b/docs/2.guide/2.directory-structure/1.utils.md @@ -2,7 +2,7 @@ title: 'utils' head.title: 'utils/' description: Use the utils/ directory to auto-import your utility functions throughout your application. -navigation.icon: i-ph-folder-duotone +navigation.icon: i-ph-folder --- The main purpose of the [`utils/` directory](/docs/guide/directory-structure/utils) is to allow a semantic distinction between your Vue composables and other auto-imported utility functions. diff --git a/docs/2.guide/2.directory-structure/2.env.md b/docs/2.guide/2.directory-structure/2.env.md index b0d4a82e6e..422dde9f90 100644 --- a/docs/2.guide/2.directory-structure/2.env.md +++ b/docs/2.guide/2.directory-structure/2.env.md @@ -2,7 +2,7 @@ title: ".env" description: "A .env file specifies your build/dev-time environment variables." head.title: ".env" -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- ::important diff --git a/docs/2.guide/2.directory-structure/2.gitignore.md b/docs/2.guide/2.directory-structure/2.gitignore.md index a4d69321ee..9247e32dbd 100644 --- a/docs/2.guide/2.directory-structure/2.gitignore.md +++ b/docs/2.guide/2.directory-structure/2.gitignore.md @@ -2,7 +2,7 @@ title: ".gitignore" description: "A .gitignore file specifies intentionally untracked files that git should ignore." head.title: ".gitignore" -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- A `.gitignore` file specifies intentionally untracked files that git should ignore. diff --git a/docs/2.guide/2.directory-structure/2.nuxtignore.md b/docs/2.guide/2.directory-structure/2.nuxtignore.md index 6c34c0be42..93a1139b14 100644 --- a/docs/2.guide/2.directory-structure/2.nuxtignore.md +++ b/docs/2.guide/2.directory-structure/2.nuxtignore.md @@ -2,7 +2,7 @@ title: .nuxtignore head.title: '.nuxtignore' description: The .nuxtignore file lets Nuxt ignore files in your projectโ€™s root directory during the build phase. -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- The `.nuxtignore` file tells Nuxt to ignore files in your projectโ€™s root directory ([`rootDir`](/docs/api/nuxt-config#rootdir)) during the build phase. diff --git a/docs/2.guide/2.directory-structure/3.app-config.md b/docs/2.guide/2.directory-structure/3.app-config.md index a027077a32..656d5c16a1 100644 --- a/docs/2.guide/2.directory-structure/3.app-config.md +++ b/docs/2.guide/2.directory-structure/3.app-config.md @@ -2,7 +2,7 @@ title: app.config.ts head.title: 'app.config.ts' description: Expose reactive configuration within your application with the App Config file. -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- Nuxt provides an `app.config` config file to expose reactive configuration within your application with the ability to update it at runtime within lifecycle or using a nuxt plugin and editing it with HMR (hot-module-replacement). @@ -121,3 +121,37 @@ export default defineAppConfig({ ``` :: + +## Known Limitations + +As of Nuxt v3.3, the `app.config.ts` file is shared with Nitro, which results in the following limitations: + +1. You cannot import Vue components directly in `app.config.ts`. +2. Some auto-imports are not available in the Nitro context. + +These limitations occur because Nitro processes the app config without full Vue component support. + +While it's possible to use Vite plugins in the Nitro config as a workaround, this approach is not recommended: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + nitro: { + vite: { + plugins: [vue()] + } + } +}) +``` + +::warning +Using this workaround may lead to unexpected behavior and bugs. The Vue plugin is one of many that are not available in the Nitro context. +:: + +Related issues: +- [Issue #19858](https://github.com/nuxt/nuxt/issues/19858) +- [Issue #19854](https://github.com/nuxt/nuxt/issues/19854) + +::info +Nitro v3 will resolve these limitations by removing support for the app config. +You can track the progress in [this pull request](https://github.com/unjs/nitro/pull/2521). +:: diff --git a/docs/2.guide/2.directory-structure/3.app.md b/docs/2.guide/2.directory-structure/3.app.md index 2716e38581..8158891ada 100644 --- a/docs/2.guide/2.directory-structure/3.app.md +++ b/docs/2.guide/2.directory-structure/3.app.md @@ -2,7 +2,7 @@ title: "app.vue" description: "The app.vue file is the main component of your Nuxt application." head.title: "app.vue" -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- ## Minimal Usage diff --git a/docs/2.guide/2.directory-structure/3.error.md b/docs/2.guide/2.directory-structure/3.error.md index ffdd6eac0e..13dd45bf7a 100644 --- a/docs/2.guide/2.directory-structure/3.error.md +++ b/docs/2.guide/2.directory-structure/3.error.md @@ -2,7 +2,7 @@ title: "error.vue" description: "The error.vue file is the error page in your Nuxt application." head.title: "error.vue" -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- During the lifespan of your application, some errors may appear unexpectedly at runtime. In such case, we can use the `error.vue` file to override the default error files and display the error nicely. diff --git a/docs/2.guide/2.directory-structure/3.nuxt-config.md b/docs/2.guide/2.directory-structure/3.nuxt-config.md index 1174095d2b..997f8999bc 100644 --- a/docs/2.guide/2.directory-structure/3.nuxt-config.md +++ b/docs/2.guide/2.directory-structure/3.nuxt-config.md @@ -2,7 +2,7 @@ title: "nuxt.config.ts" description: "Nuxt can be easily configured with a single nuxt.config file." head.title: "nuxt.config.ts" -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- The `nuxt.config` file extension can either be `.js`, `.ts` or `.mjs`. diff --git a/docs/2.guide/2.directory-structure/3.package.md b/docs/2.guide/2.directory-structure/3.package.md index ad5757fbf0..c2e6e56607 100644 --- a/docs/2.guide/2.directory-structure/3.package.md +++ b/docs/2.guide/2.directory-structure/3.package.md @@ -2,7 +2,7 @@ title: package.json head.title: package.json description: The package.json file contains all the dependencies and scripts for your application. -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- The minimal `package.json` of your Nuxt application should looks like: diff --git a/docs/2.guide/2.directory-structure/3.tsconfig.md b/docs/2.guide/2.directory-structure/3.tsconfig.md index eca36286d1..5ed9df4449 100644 --- a/docs/2.guide/2.directory-structure/3.tsconfig.md +++ b/docs/2.guide/2.directory-structure/3.tsconfig.md @@ -2,7 +2,7 @@ title: "tsconfig.json" description: "Nuxt generates a .nuxt/tsconfig.json file with sensible defaults and your aliases." head.title: "tsconfig.json" -navigation.icon: i-ph-file-duotone +navigation.icon: i-ph-file --- Nuxt [automatically generates](/docs/guide/concepts/typescript) a `.nuxt/tsconfig.json` file with the resolved aliases you are using in your Nuxt project, as well as with other sensible defaults. diff --git a/docs/2.guide/2.directory-structure/_dir.yml b/docs/2.guide/2.directory-structure/_dir.yml index 4d663658ad..4f0a802ac3 100644 --- a/docs/2.guide/2.directory-structure/_dir.yml +++ b/docs/2.guide/2.directory-structure/_dir.yml @@ -1,3 +1,3 @@ title: Directory Structure titleTemplate: '%s ยท Nuxt Directory Structure' -icon: i-ph-folders-duotone +icon: i-ph-folders diff --git a/docs/2.guide/3.going-further/1.experimental-features.md b/docs/2.guide/3.going-further/1.experimental-features.md index 136cadf805..31bed1a8ae 100644 --- a/docs/2.guide/3.going-further/1.experimental-features.md +++ b/docs/2.guide/3.going-further/1.experimental-features.md @@ -104,11 +104,11 @@ export default defineNuxtConfig({ Matching route rules will be created, based on the page's `path`. -::read-more{to="/docs/api/utils/define-route-rules" icon="i-ph-function-duotone"} +::read-more{to="/docs/api/utils/define-route-rules" icon="i-ph-function"} Read more in `defineRouteRules` utility. :: -:read-more{to="/docs/guide/concepts/rendering#hybrid-rendering" icon="i-ph-medal-duotone"} +:read-more{to="/docs/guide/concepts/rendering#hybrid-rendering" icon="i-ph-medal"} ## renderJsonPayloads @@ -254,7 +254,7 @@ Out of the box, this will enable typed usage of [`navigateTo`](/docs/api/utils/n You can even get typed params within a page by using `const route = useRoute('route-name')`. -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=SXk-L19gTZk" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=SXk-L19gTZk" target="_blank"} Watch a video from Daniel Roe explaining type-safe routing in Nuxt. :: @@ -292,7 +292,7 @@ export default defineNuxtConfig({ }) ``` -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=1jUupYHVvrU" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=1jUupYHVvrU" target="_blank"} Watch a video from Alexander Lichter about the experimental `sharedPrerenderData` setting. :: @@ -390,3 +390,31 @@ In addition, any changes to files within `srcDir` will trigger a rebuild of the ::note A maximum of 10 cache tarballs are kept. :: + +## normalizeComponentNames + +Ensure that auto-generated Vue component names match the full component name +you would use to auto-import the component. + +```ts twoslash [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + normalizeComponentNames: true + } +}) +``` + +By default, if you haven't set it manually, Vue will assign a component name that matches +the filename of the component. + +```bash [Directory structure] +โ”œโ”€ components/ +โ”œโ”€โ”€โ”€ SomeFolder/ +โ”œโ”€โ”€โ”€โ”€โ”€ MyComponent.vue +``` + +In this case, the component name would be `MyComponent`, as far as Vue is concerned. If you wanted to use `` with it, or identify it in the Vue DevTools, you would need to use this component. + +But in order to auto-import it, you would need to use `SomeFolderMyComponent`. + +By setting `experimental.normalizeComponentNames`, these two values match, and Vue will generate a component name that matches the Nuxt pattern for component naming. diff --git a/docs/2.guide/3.going-further/1.features.md b/docs/2.guide/3.going-further/1.features.md index 02056f1e12..247df516e2 100644 --- a/docs/2.guide/3.going-further/1.features.md +++ b/docs/2.guide/3.going-further/1.features.md @@ -61,9 +61,12 @@ export default defineNuxtConfig({ app: 'app' }, experimental: { + sharedPrerenderData: false, compileTemplate: true, + resetAsyncDataToUndefined: true, templateUtils: true, relativeWatchPaths: true, + normalizeComponentNames: false defaults: { useAsyncData: { deep: true diff --git a/docs/2.guide/3.going-further/10.runtime-config.md b/docs/2.guide/3.going-further/10.runtime-config.md index d43b794bf1..69f9fb9876 100644 --- a/docs/2.guide/3.going-further/10.runtime-config.md +++ b/docs/2.guide/3.going-further/10.runtime-config.md @@ -61,7 +61,7 @@ Setting the default of `runtimeConfig` values to *differently named environment It is advised to use environment variables that match the structure of your `runtimeConfig` object. :: -::tip{icon="i-ph-video-duotone" to="https://youtu.be/_FYV5WfiWvs" target="_blank"} +::tip{icon="i-ph-video" to="https://youtu.be/_FYV5WfiWvs" target="_blank"} Watch a video from Alexander Lichter showcasing the top mistake developers make using runtimeConfig. :: diff --git a/docs/2.guide/3.going-further/3.modules.md b/docs/2.guide/3.going-further/3.modules.md index eb418d0f72..b12d886821 100644 --- a/docs/2.guide/3.going-further/3.modules.md +++ b/docs/2.guide/3.going-further/3.modules.md @@ -45,7 +45,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/navigating-the-official-starter-template?friend=nuxt" target="_blank"} Watch Vue School video about Nuxt module starter template. :: @@ -274,7 +274,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/extending-and-altering-nuxt-configuration-and-options?friend=nuxt" target="_blank"} Watch Vue School video about altering Nuxt configuration. :: @@ -311,7 +311,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/passing-and-exposing-module-options?friend=nuxt" target="_blank"} Watch Vue School video about passing and exposing Nuxt module options. :: @@ -538,7 +538,7 @@ export default defineNuxtModule({ :read-more{to="/docs/api/advanced/hooks"} -::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/nuxt-lifecycle-hooks?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/nuxt-lifecycle-hooks?friend=nuxt" target="_blank"} Watch Vue School video about using Nuxt lifecycle hooks in modules. :: @@ -764,7 +764,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/exploring-nuxt-modules-ecosystem-and-module-types?friend=nuxt" target="_blank"} Watch Vue School video about Nuxt module types. :: diff --git a/docs/2.guide/3.going-further/_dir.yml b/docs/2.guide/3.going-further/_dir.yml index 20cbae3d01..80b2c5b728 100644 --- a/docs/2.guide/3.going-further/_dir.yml +++ b/docs/2.guide/3.going-further/_dir.yml @@ -1,3 +1,3 @@ title: Going Further titleTemplate: '%s ยท Nuxt Advanced' -icon: i-ph-star-duotone +icon: i-ph-star diff --git a/docs/2.guide/4.recipes/1.custom-routing.md b/docs/2.guide/4.recipes/1.custom-routing.md index 7b1b24e321..f9b191768a 100644 --- a/docs/2.guide/4.recipes/1.custom-routing.md +++ b/docs/2.guide/4.recipes/1.custom-routing.md @@ -22,7 +22,7 @@ export default { { name: 'home', path: '/', - component: () => import('~/pages/home.vue').then(r => r.default || r) + component: () => import('~/pages/home.vue') } ], } satisfies RouterConfig diff --git a/docs/2.guide/4.recipes/3.custom-usefetch.md b/docs/2.guide/4.recipes/3.custom-usefetch.md index e8f25f6a2b..a0ac6d7e29 100644 --- a/docs/2.guide/4.recipes/3.custom-usefetch.md +++ b/docs/2.guide/4.recipes/3.custom-usefetch.md @@ -31,14 +31,8 @@ export default defineNuxtPlugin((nuxtApp) => { baseURL: 'https://api.nuxt.com', onRequest({ request, options, error }) { if (session.value?.token) { - const headers = options.headers ||= {} - if (Array.isArray(headers)) { - headers.push(['Authorization', `Bearer ${session.value?.token}`]) - } else if (headers instanceof Headers) { - headers.set('Authorization', `Bearer ${session.value?.token}`) - } else { - headers.Authorization = `Bearer ${session.value?.token}` - } + // note that this relies on ofetch >= 1.4.0 - you may need to refresh your lockfile + options.headers.set('Authorization', `Bearer ${session.value?.token}`) } }, async onResponseError({ response }) { @@ -96,6 +90,28 @@ const { data: modules } = await useAPI('/modules') ``` +If you want to customize the type of any error returned, you can also do so: + +```ts +import type { FetchError } from 'ofetch' +import type { UseFetchOptions } from 'nuxt/app' + +interface CustomError { + message: string + statusCode: number +} + +export function useAPI( + url: string | (() => string), + options?: UseFetchOptions, +) { + return useFetch>(url, { + ...options, + $fetch: useNuxtApp().$api + }) +} +``` + ::note This example demonstrates how to use a custom `useFetch`, but the same structure is identical for a custom `useAsyncData`. :: diff --git a/docs/2.guide/4.recipes/_dir.yml b/docs/2.guide/4.recipes/_dir.yml index b63c755e5f..5030f4b88d 100644 --- a/docs/2.guide/4.recipes/_dir.yml +++ b/docs/2.guide/4.recipes/_dir.yml @@ -1,3 +1,3 @@ title: Recipes titleTemplate: '%s ยท Recipes' -icon: i-ph-cooking-pot-duotone +icon: i-ph-cooking-pot diff --git a/docs/2.guide/_dir.yml b/docs/2.guide/_dir.yml index 39506eabf0..9fb4817fc8 100644 --- a/docs/2.guide/_dir.yml +++ b/docs/2.guide/_dir.yml @@ -1,2 +1,2 @@ title: 'Guide' -icon: i-ph-book-open-duotone +icon: i-ph-book-open diff --git a/docs/3.api/1.components/_dir.yml b/docs/3.api/1.components/_dir.yml index d78fe4060a..33401303cf 100644 --- a/docs/3.api/1.components/_dir.yml +++ b/docs/3.api/1.components/_dir.yml @@ -1,3 +1,3 @@ title: 'Components' titleTemplate: '%s ยท Nuxt Components' -icon: i-ph-cube-duotone +icon: i-ph-cube diff --git a/docs/3.api/2.composables/_dir.yml b/docs/3.api/2.composables/_dir.yml index 35d41bbd10..e33d9ed036 100644 --- a/docs/3.api/2.composables/_dir.yml +++ b/docs/3.api/2.composables/_dir.yml @@ -1,3 +1,3 @@ title: 'Composables' titleTemplate: '%s ยท Nuxt Composables' -icon: i-ph-arrows-left-right-duotone +icon: i-ph-arrows-left-right diff --git a/docs/3.api/2.composables/use-async-data.md b/docs/3.api/2.composables/use-async-data.md index a29f09d36c..7619dd1eb7 100644 --- a/docs/3.api/2.composables/use-async-data.md +++ b/docs/3.api/2.composables/use-async-data.md @@ -114,7 +114,7 @@ function useAsyncData( key: string, handler: (nuxtApp?: NuxtApp) => Promise, options?: AsyncDataOptions -): Promise +): Promise> type AsyncDataOptions = { server?: boolean diff --git a/docs/3.api/2.composables/use-fetch.md b/docs/3.api/2.composables/use-fetch.md index 4ca72835f1..56effdc62c 100644 --- a/docs/3.api/2.composables/use-fetch.md +++ b/docs/3.api/2.composables/use-fetch.md @@ -50,8 +50,8 @@ You can also use [interceptors](https://github.com/unjs/ofetch#%EF%B8%8F-interce const { data, status, error, refresh, clear } = await useFetch('/api/auth/login', { onRequest({ request, options }) { // Set the request headers - options.headers = options.headers || {} - options.headers.authorization = '...' + // note that this relies on ofetch >= 1.4.0 - you may need to refresh your lockfile + options.headers.set('Authorization', '...') }, onRequestError({ request, options, error }) { // Handle the request errors @@ -70,7 +70,11 @@ const { data, status, error, refresh, clear } = await useFetch('/api/auth/login' `useFetch` is a reserved function name transformed by the compiler, so you should not name your own function `useFetch`. :: -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"} +::warning +If you encounter the `data` variable destructured from a `useFetch` returns a string and not a JSON parsed object then make sure your component doesn't include an import statement like `import { useFetch } from '@vueuse/core`. +:: + +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"} Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way! :: @@ -143,7 +147,7 @@ If you have not fetched data on the server (for example, with `server: false`), ```ts [Signature] function useFetch( - url: string | Request | Ref | () => string | Request, + url: string | Request | Ref | (() => string) | Request, options?: UseFetchOptions ): Promise> diff --git a/docs/3.api/2.composables/use-nuxt-app.md b/docs/3.api/2.composables/use-nuxt-app.md index 5ab9289638..6b915edf00 100644 --- a/docs/3.api/2.composables/use-nuxt-app.md +++ b/docs/3.api/2.composables/use-nuxt-app.md @@ -138,7 +138,7 @@ Nuxt exposes the following properties through `ssrContext`: 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. - ::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=8w6ffRBs8a4" target="_blank"} + ::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=8w6ffRBs8a4" target="_blank"} Watch a video from Alexander Lichter about serializing payloads, especially with regards to classes. :: diff --git a/docs/3.api/2.composables/use-request-fetch.md b/docs/3.api/2.composables/use-request-fetch.md new file mode 100644 index 0000000000..dac922e393 --- /dev/null +++ b/docs/3.api/2.composables/use-request-fetch.md @@ -0,0 +1,52 @@ +--- +title: 'useRequestFetch' +description: 'Forward the request context and headers for server-side fetch requests with the useRequestFetch composable.' +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/ssr.ts + size: xs +--- + +You can use `useRequestFetch` to forward the request context and headers when making server-side fetch requests. + +When making a client-side fetch request, the browser automatically sends the necessary headers. +However, when making a request during server-side rendering, because the request is made on the server, we need to forward the headers manually. + +::note +Headers that are **not meant to be forwarded** will **not be included** in the request. These headers include, for example: +`transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `expect`, `host`, `accept` +:: + +::tip +The [`useFetch`](/docs/api/composables/use-fetch) composable uses `useRequestFetch` under the hood to automatically forward the request context and headers. +:: + +::code-group + +```vue [pages/index.vue] + +``` + +```ts [server/api/cookies.ts] +export default defineEventHandler((event) => { + const cookies = parseCookies(event) + + return { cookies } +}) +``` + +:: + +::tip +In the browser during client-side navigation, `useRequestFetch` will behave just like regular [`$fetch`](/docs/api/utils/dollarfetch). +:: diff --git a/docs/3.api/2.composables/use-response-header.md b/docs/3.api/2.composables/use-response-header.md new file mode 100644 index 0000000000..d78fd89a4a --- /dev/null +++ b/docs/3.api/2.composables/use-response-header.md @@ -0,0 +1,48 @@ +--- +title: "useResponseHeader" +description: "Use useResponseHeader to set a server response header." +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/ssr.ts + size: xs +--- + +::important +This composable is available in Nuxt v3.14+. +:: + +You can use the built-in [`useResponseHeader`](/docs/api/composables/use-response-header) composable to set any server response header within your pages, components, and plugins. + +```ts +// Set the a custom response header +const header = useResponseHeader('X-My-Header'); +header.value = 'my-value'; +``` + +## Example + +We can use `useResponseHeader` to easily set a response header on a per-page basis. + +```vue [pages/test.vue] + + + +``` + +We can use `useResponseHeader` for example in Nuxt [middleware](/docs/guide/directory-structure/middleware) to set a response header for all pages. + +```ts [middleware/my-header-middleware.ts] +export default defineNuxtRouteMiddleware((to, from) => { + const header = useResponseHeader('X-My-Always-Header'); + header.value = `I'm Always here!`; +}); + +``` diff --git a/docs/3.api/2.composables/use-state.md b/docs/3.api/2.composables/use-state.md index 513bd407c6..c8194dc9ed 100644 --- a/docs/3.api/2.composables/use-state.md +++ b/docs/3.api/2.composables/use-state.md @@ -25,7 +25,7 @@ Because the data inside `useState` will be serialized to JSON, it is important t `useState` is a reserved function name transformed by the compiler, so you should not name your own function `useState`. :: -::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"} +::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"} Watch a video from Alexander Lichter about why and when to use `useState()`. :: diff --git a/docs/3.api/3.utils/$fetch.md b/docs/3.api/3.utils/$fetch.md index ff0ed5cf05..ab8a947aad 100644 --- a/docs/3.api/3.utils/$fetch.md +++ b/docs/3.api/3.utils/$fetch.md @@ -10,11 +10,11 @@ links: Nuxt uses [ofetch](https://github.com/unjs/ofetch) to expose globally the `$fetch` helper for making HTTP requests within your Vue app or API routes. -::tip{icon="i-ph-rocket-launch-duotone" color="gray"} +::tip{icon="i-ph-rocket-launch" color="gray"} During server-side rendering, calling `$fetch` to fetch your internal [API routes](/docs/guide/directory-structure/server) will directly call the relevant function (emulating the request), **saving an additional API call**. :: -::note{color="blue" icon="i-ph-info-duotone"} +::note{color="blue" icon="i-ph-info"} Using `$fetch` in components without wrapping it with [`useAsyncData`](/docs/api/composables/use-async-data) causes fetching the data twice: initially on the server, then again on the client-side during hydration, because `$fetch` does not transfer state from the server to the client. Thus, the fetch will be executed on both sides because the client has to get the data again. :: diff --git a/docs/3.api/3.utils/_dir.yml b/docs/3.api/3.utils/_dir.yml index 50d20caf25..c3ef54ea5c 100644 --- a/docs/3.api/3.utils/_dir.yml +++ b/docs/3.api/3.utils/_dir.yml @@ -1,3 +1,3 @@ title: 'Utils' titleTemplate: '%s ยท Nuxt Utils' -navigation.icon: i-ph-function-duotone +navigation.icon: i-ph-function diff --git a/docs/3.api/3.utils/define-route-rules.md b/docs/3.api/3.utils/define-route-rules.md index 309a64fd21..50557cea88 100644 --- a/docs/3.api/3.utils/define-route-rules.md +++ b/docs/3.api/3.utils/define-route-rules.md @@ -8,7 +8,7 @@ links: size: xs --- -::read-more{to="/docs/guide/going-further/experimental-features#inlinerouterules" icon="i-ph-star-duotone"} +::read-more{to="/docs/guide/going-further/experimental-features#inlinerouterules" icon="i-ph-star"} This feature is experimental and in order to use it you must enable the `experimental.inlineRouteRules` option in your `nuxt.config`. :: @@ -47,6 +47,6 @@ When running [`nuxt build`](/docs/api/commands/build), the home page will be pre For more control, such as if you are using a custom `path` or `alias` set in the page's [`definePageMeta`](/docs/api/utils/define-page-meta), you should set `routeRules` directly within your `nuxt.config`. -::read-more{to="/docs/guide/concepts/rendering#hybrid-rendering" icon="i-ph-medal-duotone"} +::read-more{to="/docs/guide/concepts/rendering#hybrid-rendering" icon="i-ph-medal"} Read more about the `routeRules`. :: diff --git a/docs/3.api/3.utils/preload-route-components.md b/docs/3.api/3.utils/preload-route-components.md index a0bf7c1932..d888bb75d4 100644 --- a/docs/3.api/3.utils/preload-route-components.md +++ b/docs/3.api/3.utils/preload-route-components.md @@ -10,7 +10,7 @@ links: Preloading routes loads the components of a given route that the user might navigate to in future. This ensures that the components are available earlier and less likely to block the navigation, improving performance. -::tip{icon="i-ph-rocket-launch-duotone" color="gray"} +::tip{icon="i-ph-rocket-launch" color="gray"} Nuxt already automatically preloads the necessary routes if you're using the `NuxtLink` component. :: diff --git a/docs/3.api/3.utils/reload-nuxt-app.md b/docs/3.api/3.utils/reload-nuxt-app.md index 3b25a95905..0244c78b9c 100644 --- a/docs/3.api/3.utils/reload-nuxt-app.md +++ b/docs/3.api/3.utils/reload-nuxt-app.md @@ -14,7 +14,7 @@ links: By default, it will also save the current `state` of your app (that is, any state you could access with `useState`). -::read-more{to="/docs/guide/going-further/experimental-features#restorestate" icon="i-ph-star-duotone"} +::read-more{to="/docs/guide/going-further/experimental-features#restorestate" icon="i-ph-star"} You can enable experimental restoration of this state by enabling the `experimental.restoreState` option in your `nuxt.config` file. :: diff --git a/docs/3.api/4.commands/_dir.yml b/docs/3.api/4.commands/_dir.yml index b1123168e0..00af2f6eb1 100644 --- a/docs/3.api/4.commands/_dir.yml +++ b/docs/3.api/4.commands/_dir.yml @@ -1,3 +1,3 @@ title: 'Commands' -icon: i-ph-terminal-window-duotone +icon: i-ph-terminal-window titleTemplate: '%s ยท Nuxt Commands' diff --git a/docs/3.api/5.kit/12.resolving.md b/docs/3.api/5.kit/12.resolving.md index eac13cab4f..8218ecfb0d 100644 --- a/docs/3.api/5.kit/12.resolving.md +++ b/docs/3.api/5.kit/12.resolving.md @@ -211,7 +211,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/resolving-paths-and-injecting-assets-to-the-app?friend=nuxt" target="_blank"} Watch Vue School video about createResolver. :: diff --git a/docs/3.api/5.kit/4.autoimports.md b/docs/3.api/5.kit/4.autoimports.md index 6a0b0a08a5..4aa9aac211 100644 --- a/docs/3.api/5.kit/4.autoimports.md +++ b/docs/3.api/5.kit/4.autoimports.md @@ -18,7 +18,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/expanding-nuxt-s-auto-imports?friend=nuxt" target="_blank"} Watch Vue School video about Auto-imports Nuxt Kit utilities. :: diff --git a/docs/3.api/5.kit/5.components.md b/docs/3.api/5.kit/5.components.md index b112c84962..3d41667d31 100644 --- a/docs/3.api/5.kit/5.components.md +++ b/docs/3.api/5.kit/5.components.md @@ -10,7 +10,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/injecting-components-and-component-directories?friend=nuxt" target="_blank"} Watch Vue School video about injecting components. :: diff --git a/docs/3.api/5.kit/7.pages.md b/docs/3.api/5.kit/7.pages.md index 7e7dd6e8d3..8c1d91a685 100644 --- a/docs/3.api/5.kit/7.pages.md +++ b/docs/3.api/5.kit/7.pages.md @@ -12,7 +12,7 @@ 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 offers the `extendPages` feature, which allows you to extend and alter the pages configuration. -::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/extend-and-alter-nuxt-pages?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/extend-and-alter-nuxt-pages?friend=nuxt" target="_blank"} Watch Vue School video about extendPages. :: @@ -71,7 +71,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares?friend=nuxt" target="_blank"} Watch Vue School video about adding route rules and route middelwares. :: @@ -192,7 +192,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares?friend=nuxt" target="_blank"} Watch Vue School video about adding route rules and route middelwares. :: diff --git a/docs/3.api/5.kit/9.plugins.md b/docs/3.api/5.kit/9.plugins.md index 4ee2eda5af..e2f09cfc76 100644 --- a/docs/3.api/5.kit/9.plugins.md +++ b/docs/3.api/5.kit/9.plugins.md @@ -14,7 +14,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/injecting-plugins?friend=nuxt" target="_blank"} Watch Vue School video about addPlugin. :: @@ -114,7 +114,7 @@ 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?friend=nuxt" target="_blank"} +::tip{icon="i-ph-video" to="https://vueschool.io/lessons/injecting-plugin-templates?friend=nuxt" target="_blank"} Watch Vue School video about addPluginTemplate. :: diff --git a/docs/3.api/5.kit/_dir.yml b/docs/3.api/5.kit/_dir.yml index dda66db56e..86a5d387a4 100644 --- a/docs/3.api/5.kit/_dir.yml +++ b/docs/3.api/5.kit/_dir.yml @@ -1,3 +1,3 @@ title: Nuxt Kit -navigation.icon: i-ph-toolbox-duotone +navigation.icon: i-ph-toolbox titleTemplate: '%s ยท Nuxt Kit' diff --git a/docs/3.api/6.advanced/_dir.yml b/docs/3.api/6.advanced/_dir.yml index b8a90804b7..e0a580e33c 100644 --- a/docs/3.api/6.advanced/_dir.yml +++ b/docs/3.api/6.advanced/_dir.yml @@ -1 +1 @@ -icon: i-ph-brain-duotone +icon: i-ph-brain diff --git a/docs/3.api/6.nuxt-config.md b/docs/3.api/6.nuxt-config.md index 7f915757f6..cad9250920 100644 --- a/docs/3.api/6.nuxt-config.md +++ b/docs/3.api/6.nuxt-config.md @@ -2,7 +2,7 @@ title: Nuxt Configuration titleTemplate: '%s' description: Discover all the options you can use in your nuxt.config.ts file. -navigation.icon: i-ph-gear-duotone +navigation.icon: i-ph-gear --- ::note{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/tree/main/packages/schema/src/config" target="_blank"} diff --git a/docs/3.api/index.md b/docs/3.api/index.md index f0b12b7425..7e4970a2ae 100644 --- a/docs/3.api/index.md +++ b/docs/3.api/index.md @@ -7,25 +7,25 @@ surround: false --- ::card-group - ::card{icon="i-ph-cube-duotone" title="Components" to="/docs/api/components/client-only"} + ::card{icon="i-ph-cube" title="Components" to="/docs/api/components/client-only"} Explore Nuxt built-in components for pages, layouts, head, and more. :: - ::card{icon="i-ph-arrows-left-right-duotone" title="Composables" to="/docs/api/composables/use-app-config"} + ::card{icon="i-ph-arrows-left-right" title="Composables" to="/docs/api/composables/use-app-config"} Discover Nuxt composable functions for data-fetching, head management and more. :: - ::card{icon="i-ph-function-duotone" title="Utils" to="/docs/api/utils/dollarfetch"} + ::card{icon="i-ph-function" title="Utils" to="/docs/api/utils/dollarfetch"} Learn about Nuxt utility functions for navigation, error handling and more. :: - ::card{icon="i-ph-terminal-window-duotone" title="Commands" to="/docs/api/commands/add"} + ::card{icon="i-ph-terminal-window" title="Commands" to="/docs/api/commands/add"} List of Nuxt CLI commands to init, analyze, build, and preview your application. :: - ::card{icon="i-ph-toolbox-duotone" title="Nuxt Kit" to="/docs/api/kit/modules"} + ::card{icon="i-ph-toolbox" title="Nuxt Kit" to="/docs/api/kit/modules"} Understand Nuxt Kit utilities to create modules and control Nuxt. :: - ::card{icon="i-ph-brain-duotone" title="Advanced" to="/docs/api/advanced/hooks"} + ::card{icon="i-ph-brain" title="Advanced" to="/docs/api/advanced/hooks"} Go deep in Nuxt internals with Nuxt lifecycle hooks. :: - ::card{icon="i-ph-gear-duotone" title="Nuxt Configuration" to="/docs/api/nuxt-config"} + ::card{icon="i-ph-gear" title="Nuxt Configuration" to="/docs/api/nuxt-config"} Explore all Nuxt configuration options to customize your application. :: :: diff --git a/docs/5.community/2.getting-help.md b/docs/5.community/2.getting-help.md index 4100c849d6..ded7e1292a 100644 --- a/docs/5.community/2.getting-help.md +++ b/docs/5.community/2.getting-help.md @@ -2,7 +2,7 @@ title: Getting Help description: We're a friendly community of developers and we'd love to help. navigation: - icon: i-ph-lifebuoy-duotone + icon: i-ph-lifebuoy --- At some point, you may find that there's an issue you need some help with. diff --git a/docs/5.community/3.reporting-bugs.md b/docs/5.community/3.reporting-bugs.md index c82a6d8ba6..30b60f3386 100644 --- a/docs/5.community/3.reporting-bugs.md +++ b/docs/5.community/3.reporting-bugs.md @@ -1,7 +1,7 @@ --- title: 'Reporting Bugs' description: 'One of the most valuable roles in open source is taking the time to report bugs helpfully.' -navigation.icon: i-ph-bug-duotone +navigation.icon: i-ph-bug --- Try as we might, we will never completely eliminate bugs. diff --git a/docs/5.community/4.contribution.md b/docs/5.community/4.contribution.md index 3702d845a8..d8e58c24dc 100644 --- a/docs/5.community/4.contribution.md +++ b/docs/5.community/4.contribution.md @@ -1,7 +1,7 @@ --- title: 'Contribution' description: 'Nuxt is a community project - and so we love contributions of all kinds! โค๏ธ' -navigation.icon: i-ph-git-pull-request-duotone +navigation.icon: i-ph-git-pull-request --- There is a range of different ways you might be able to contribute to the Nuxt ecosystem. @@ -184,21 +184,21 @@ Here are some tips that may help improve your documentation: Keep in mind your readers can have different backgrounds and experiences. Therefore, these words don't convey meaning and can be harmful. - ::caution{ icon="i-ph-x-circle-duotone"} + ::caution{ icon="i-ph-x-circle"} Simply make sure the function returns a promise. :: - ::tip{icon="i-ph-check-circle-duotone"} + ::tip{icon="i-ph-check-circle"} Make sure the function returns a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). :: * Prefer [active voice](https://developers.google.com/tech-writing/one/active-voice). - ::caution{icon="i-ph-x-circle-duotone"} + ::caution{icon="i-ph-x-circle"} An error will be thrown by Nuxt. :: - ::tip{icon="i-ph-check-circle-duotone"} + ::tip{icon="i-ph-check-circle"} Nuxt will throw an error. :: diff --git a/docs/5.community/5.framework-contribution.md b/docs/5.community/5.framework-contribution.md index a96de2e749..7c9315fa3a 100644 --- a/docs/5.community/5.framework-contribution.md +++ b/docs/5.community/5.framework-contribution.md @@ -1,6 +1,6 @@ --- title: 'Framework' -navigation.icon: i-ph-github-logo-duotone +navigation.icon: i-ph-github-logo description: Some specific points about contributions to the framework repository. --- diff --git a/docs/5.community/6.roadmap.md b/docs/5.community/6.roadmap.md index 4230570244..df987f8bfb 100644 --- a/docs/5.community/6.roadmap.md +++ b/docs/5.community/6.roadmap.md @@ -1,7 +1,7 @@ --- title: 'Roadmap' description: 'Nuxt is constantly evolving, with new features and modules being added all the time.' -navigation.icon: i-ph-map-trifold-duotone +navigation.icon: i-ph-map-trifold --- ::read-more{to="/blog"} diff --git a/docs/5.community/7.changelog.md b/docs/5.community/7.changelog.md index bc1d34cb72..942ec3e32b 100644 --- a/docs/5.community/7.changelog.md +++ b/docs/5.community/7.changelog.md @@ -1,7 +1,7 @@ --- title: 'Releases' description: Discover the latest releases of Nuxt & Nuxt official modules. -navigation.icon: i-ph-notification-duotone +navigation.icon: i-ph-notification --- ::card-group diff --git a/docs/5.community/_dir.yml b/docs/5.community/_dir.yml index 1330352c11..de92f13d6f 100644 --- a/docs/5.community/_dir.yml +++ b/docs/5.community/_dir.yml @@ -1,3 +1,3 @@ title: 'Community' titleTemplate: '%s ยท Nuxt Community' -icon: i-ph-chats-teardrop-duotone +icon: i-ph-chats-teardrop diff --git a/docs/6.bridge/_dir.yml b/docs/6.bridge/_dir.yml index f2a37c2daa..f7db65f48d 100644 --- a/docs/6.bridge/_dir.yml +++ b/docs/6.bridge/_dir.yml @@ -1,3 +1,3 @@ titleTemplate: 'Migrate to Nuxt Bridge: %s' title: 'Migrate to Nuxt Bridge' -icon: i-ph-bridge-duotone +icon: i-ph-bridge diff --git a/docs/7.migration/20.module-authors.md b/docs/7.migration/20.module-authors.md index ff42347c12..abe8fc67c1 100644 --- a/docs/7.migration/20.module-authors.md +++ b/docs/7.migration/20.module-authors.md @@ -9,7 +9,7 @@ Nuxt 3 has a basic backward compatibility layer for Nuxt 2 modules using `@nuxt/ We have prepared a [Dedicated Guide](/docs/guide/going-further/modules) for authoring Nuxt 3 ready modules using `@nuxt/kit`. Currently best migration path is to follow it and rewrite your modules. Rest of this guide includes preparation steps if you prefer to avoid a full rewrite yet making modules compatible with Nuxt 3. -::tip{icon="i-ph-puzzle-piece-duotone" to="/modules"} +::tip{icon="i-ph-puzzle-piece" to="/modules"} Explore Nuxt 3 compatible modules. :: diff --git a/docs/7.migration/_dir.yml b/docs/7.migration/_dir.yml index 54585df393..a880111684 100644 --- a/docs/7.migration/_dir.yml +++ b/docs/7.migration/_dir.yml @@ -1,3 +1,3 @@ titleTemplate: 'Migrate to Nuxt 3: %s' title: 'Migrate to Nuxt 3' -icon: i-ph-arrow-circle-up-duotone +icon: i-ph-arrow-circle-up diff --git a/docs/_dir.yml b/docs/_dir.yml index 6639eb3a35..18fdf7dc26 100644 --- a/docs/_dir.yml +++ b/docs/_dir.yml @@ -1,2 +1,2 @@ title: Docs -icon: i-ph-book-bookmark-duotone +icon: i-ph-book-bookmark diff --git a/lychee.toml b/lychee.toml index b7cdde5945..b00e5bee37 100644 --- a/lychee.toml +++ b/lychee.toml @@ -14,5 +14,7 @@ exclude = [ # TODO: remove when their SSL certificate is valid again "https://www.conventionalcommits.org", # single-quotes are required for regexp - '(https?:\/\/github\.com\/)(.*\/)(generate)', + '(https?:\/\/github\.com\/)(.*\/)(generate)', + "https://localhost:3000", + "https://github.com/nuxt-contrib/vue3-ssr-starter/generate", ] diff --git a/package.json b/package.json index 47e64b43a9..9a0bc90c73 100644 --- a/package.json +++ b/package.json @@ -35,84 +35,86 @@ }, "resolutions": { "@nuxt/kit": "workspace:*", + "@nuxt/rspack-builder": "workspace:*", "@nuxt/schema": "workspace:*", "@nuxt/ui-templates": "workspace:*", "@nuxt/vite-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*", - "@types/node": "20.16.5", - "c12": "2.0.0-beta.2", + "@types/node": "20.16.11", + "@vue/compiler-core": "3.5.11", + "@vue/compiler-dom": "3.5.11", + "@vue/shared": "3.5.11", + "c12": "2.0.1", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", - "jiti": "2.0.0-beta.3", + "jiti": "2.3.3", "magic-string": "^0.30.11", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", "nuxt": "workspace:*", - "postcss": "8.4.45", - "rollup": "4.21.2", - "typescript": "5.5.4", + "ohash": "1.1.4", + "postcss": "8.4.47", + "rollup": "4.24.0", + "send": ">=0.19.0", + "typescript": "5.6.3", "ufo": "1.5.4", - "unbuild": "3.0.0-rc.7", - "vite": "5.4.3", - "vue": "3.5.1" + "unbuild": "3.0.0-rc.11", + "vite": "5.4.8", + "vue": "3.5.11" }, "devDependencies": { - "@eslint/js": "9.9.1", - "@nuxt/eslint-config": "0.5.5", + "@eslint/js": "9.12.0", + "@nuxt/eslint-config": "0.5.7", "@nuxt/kit": "workspace:*", - "@nuxt/test-utils": "3.14.1", + "@nuxt/rspack-builder": "workspace:*", + "@nuxt/test-utils": "3.14.3", "@nuxt/webpack-builder": "workspace:*", "@testing-library/vue": "8.1.0", "@types/eslint__js": "8.42.3", - "@types/node": "20.16.5", + "@types/node": "20.16.11", "@types/semver": "7.5.8", - "@unhead/schema": "1.10.4", - "@unhead/vue": "1.10.4", - "@vitejs/plugin-vue": "5.1.3", - "@vitest/coverage-v8": "2.0.5", + "@unhead/schema": "1.11.7", + "@unhead/vue": "1.11.7", + "@vitejs/plugin-vue": "5.1.4", + "@vitest/coverage-v8": "2.1.2", "@vue/test-utils": "2.4.6", "autoprefixer": "10.4.20", "case-police": "0.7.0", - "changelogen": "0.5.5", + "changelogen": "0.5.7", "consola": "3.2.3", "cssnano": "7.0.6", "destr": "2.0.3", - "devalue": "5.0.0", - "eslint": "9.9.1", + "devalue": "5.1.1", + "eslint": "9.12.0", "eslint-plugin-no-only-tests": "3.3.0", - "eslint-plugin-perfectionist": "3.4.0", - "eslint-typegen": "0.3.1", + "eslint-plugin-perfectionist": "3.8.0", + "eslint-typegen": "0.3.2", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", - "happy-dom": "15.7.3", - "jiti": "2.0.0-beta.3", - "markdownlint-cli": "0.41.0", + "happy-dom": "15.7.4", + "jiti": "2.3.3", + "markdownlint-cli": "0.42.0", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", - "nuxi": "3.13.1", + "nuxi": "3.14.0", "nuxt": "workspace:*", "nuxt-content-twoslash": "0.1.1", - "ofetch": "1.3.4", + "ofetch": "1.4.1", "pathe": "1.1.2", - "playwright-core": "1.46.1", + "playwright-core": "1.48.0", "rimraf": "6.0.1", "semver": "7.6.3", "sherif": "1.0.0", "std-env": "3.7.0", "tinyexec": "0.3.0", - "tinyglobby": "0.2.5", - "typescript": "5.5.4", + "tinyglobby": "0.2.9", + "typescript": "5.6.3", "ufo": "1.5.4", - "vitest": "2.0.5", + "vitest": "2.1.2", "vitest-environment-nuxt": "1.0.1", - "vue": "3.5.1", - "vue-router": "4.4.3", + "vue": "3.5.11", + "vue-router": "4.4.5", "vue-tsc": "2.1.6" }, - "packageManager": "pnpm@9.9.0", + "packageManager": "pnpm@9.12.1", "engines": { "node": "^16.10.0 || >=18.0.0" }, - "version": "", - "pnpm": { - "patchedDependencies": { - "ofetch@1.3.4": "patches/ofetch@1.3.4.patch" - } - } + "version": "" } diff --git a/packages/kit/build.config.ts b/packages/kit/build.config.ts index 87a8ccb072..d2a1f7fa7d 100644 --- a/packages/kit/build.config.ts +++ b/packages/kit/build.config.ts @@ -6,6 +6,7 @@ export default defineBuildConfig({ 'src/index', ], externals: [ + '@rspack/core', '@nuxt/schema', 'nitropack', 'nitro', diff --git a/packages/kit/package.json b/packages/kit/package.json index 138f42a389..651056b852 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -27,34 +27,35 @@ }, "dependencies": { "@nuxt/schema": "workspace:*", - "c12": "^2.0.0-beta.2", + "c12": "^2.0.1", "consola": "^3.2.3", "defu": "^6.1.4", "destr": "^2.0.3", "errx": "^0.1.0", "globby": "^14.0.2", "hash-sum": "^2.0.0", - "ignore": "^5.3.2", - "jiti": "^2.0.0-beta.3", + "ignore": "^6.0.2", + "jiti": "^2.3.3", "klona": "^2.0.6", - "mlly": "^1.7.1", + "mlly": "^1.7.2", "pathe": "^1.1.2", - "pkg-types": "^1.2.0", + "pkg-types": "^1.2.1", "scule": "^1.3.0", "semver": "^7.6.3", "ufo": "^1.5.4", "unctx": "^2.3.1", - "unimport": "^3.11.1", - "untyped": "^1.4.2" + "unimport": "^3.13.1", + "untyped": "^1.5.1" }, "devDependencies": { + "@rspack/core": "1.0.8", "@types/hash-sum": "1.0.2", "@types/semver": "7.5.8", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", - "unbuild": "3.0.0-rc.7", - "vite": "5.4.3", - "vitest": "2.0.5", - "webpack": "5.94.0" + "unbuild": "3.0.0-rc.11", + "vite": "5.4.8", + "vitest": "2.1.2", + "webpack": "5.95.0" }, "engines": { "node": "^14.18.0 || >=16.10.0" diff --git a/packages/kit/src/build.ts b/packages/kit/src/build.ts index fab8c45228..ba4a1d17ba 100644 --- a/packages/kit/src/build.ts +++ b/packages/kit/src/build.ts @@ -1,4 +1,5 @@ import type { Configuration as WebpackConfig, WebpackPluginInstance } from 'webpack' +import type { RspackPluginInstance } from '@rspack/core' import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite' import { useNuxt } from './context' import { toArray } from './utils' @@ -36,16 +37,7 @@ export interface ExtendWebpackConfigOptions extends ExtendConfigOptions {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface ExtendViteConfigOptions extends ExtendConfigOptions {} -/** - * Extend webpack config - * - * The fallback function might be called multiple times - * when applying to both client and server builds. - */ -export function extendWebpackConfig ( - fn: ((config: WebpackConfig) => void), - options: ExtendWebpackConfigOptions = {}, -) { +const extendWebpackCompatibleConfig = (builder: 'rspack' | 'webpack') => (fn: ((config: WebpackConfig) => void), options: ExtendWebpackConfigOptions = {}) => { const nuxt = useNuxt() if (options.dev === false && nuxt.options.dev) { @@ -55,7 +47,7 @@ export function extendWebpackConfig ( return } - nuxt.hook('webpack:config', (configs: WebpackConfig[]) => { + nuxt.hook(`${builder}:config`, (configs) => { if (options.server !== false) { const config = configs.find(i => i.name === 'server') if (config) { @@ -71,13 +63,25 @@ export function extendWebpackConfig ( }) } +/** + * Extend webpack config + * + * The fallback function might be called multiple times + * when applying to both client and server builds. + */ +export const extendWebpackConfig = extendWebpackCompatibleConfig('webpack') +/** + * Extend rspack config + * + * The fallback function might be called multiple times + * when applying to both client and server builds. + */ +export const extendRspackConfig = extendWebpackCompatibleConfig('rspack') + /** * Extend Vite config */ -export function extendViteConfig ( - fn: ((config: ViteConfig) => void), - options: ExtendViteConfigOptions = {}, -) { +export function extendViteConfig (fn: ((config: ViteConfig) => void), options: ExtendViteConfigOptions = {}) { const nuxt = useNuxt() if (options.dev === false && nuxt.options.dev) { @@ -114,6 +118,18 @@ export function addWebpackPlugin (pluginOrGetter: WebpackPluginInstance | Webpac config.plugins[method](...toArray(plugin)) }, options) } +/** + * Append rspack plugin to the config. + */ +export function addRspackPlugin (pluginOrGetter: RspackPluginInstance | RspackPluginInstance[] | (() => RspackPluginInstance | RspackPluginInstance[]), options?: ExtendWebpackConfigOptions) { + extendRspackConfig((config) => { + const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push' + const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter + + config.plugins = config.plugins || [] + config.plugins[method](...toArray(plugin)) + }, options) +} /** * Append Vite plugin to the config. @@ -131,6 +147,7 @@ export function addVitePlugin (pluginOrGetter: VitePlugin | VitePlugin[] | (() = interface AddBuildPluginFactory { vite?: () => VitePlugin | VitePlugin[] webpack?: () => WebpackPluginInstance | WebpackPluginInstance[] + rspack?: () => RspackPluginInstance | RspackPluginInstance[] } export function addBuildPlugin (pluginFactory: AddBuildPluginFactory, options?: ExtendConfigOptions) { @@ -141,4 +158,8 @@ export function addBuildPlugin (pluginFactory: AddBuildPluginFactory, options?: if (pluginFactory.webpack) { addWebpackPlugin(pluginFactory.webpack, options) } + + if (pluginFactory.rspack) { + addRspackPlugin(pluginFactory.rspack, options) + } } diff --git a/packages/kit/src/compatibility.ts b/packages/kit/src/compatibility.ts index 00ad74b67c..e2859e1812 100644 --- a/packages/kit/src/compatibility.ts +++ b/packages/kit/src/compatibility.ts @@ -8,6 +8,7 @@ export function normalizeSemanticVersion (version: string) { } const builderMap = { + '@nuxt/rspack-builder': 'rspack', '@nuxt/vite-builder': 'vite', '@nuxt/webpack-builder': 'webpack', } diff --git a/packages/kit/src/components.ts b/packages/kit/src/components.ts index 1257a6a68d..669d6ce410 100644 --- a/packages/kit/src/components.ts +++ b/packages/kit/src/components.ts @@ -6,8 +6,6 @@ import { logger } from './logger' /** * Register a directory to be scanned for components and imported only when used. - * - * Requires Nuxt 2.13+ */ export async function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) { const nuxt = useNuxt() @@ -23,8 +21,6 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial { _imports.push(...toArray(imports)) }) } export function addImportsDir (dirs: string | string[], opts: { prepend?: boolean } = {}) { - assertNuxtCompatibility({ bridge: true }) - useNuxt().hook('imports:dirs', (_dirs: string[]) => { for (const dir of toArray(dirs)) { _dirs[opts.prepend ? 'unshift' : 'push'](dir) @@ -22,8 +17,6 @@ export function addImportsDir (dirs: string | string[], opts: { prepend?: boolea }) } export function addImportsSources (presets: ImportPresetWithDeprecation | ImportPresetWithDeprecation[]) { - assertNuxtCompatibility({ bridge: true }) - useNuxt().hook('imports:sources', (_presets: ImportPresetWithDeprecation[]) => { for (const preset of toArray(presets)) { _presets.push(preset) diff --git a/packages/kit/src/index.ts b/packages/kit/src/index.ts index bde038e6fb..c9b94204b0 100644 --- a/packages/kit/src/index.ts +++ b/packages/kit/src/index.ts @@ -13,7 +13,7 @@ export type { LoadNuxtOptions } from './loader/nuxt' // Utils export { addImports, addImportsDir, addImportsSources } from './imports' export { updateRuntimeConfig, useRuntimeConfig } from './runtime-config' -export { addBuildPlugin, addVitePlugin, addWebpackPlugin, extendViteConfig, extendWebpackConfig } from './build' +export { addBuildPlugin, addVitePlugin, addRspackPlugin, addWebpackPlugin, extendViteConfig, extendRspackConfig, extendWebpackConfig } from './build' export type { ExtendConfigOptions, ExtendViteConfigOptions, ExtendWebpackConfigOptions } from './build' export { assertNuxtCompatibility, checkNuxtCompatibility, getNuxtVersion, hasNuxtCompatibility, isNuxtMajorVersion, normalizeSemanticVersion, isNuxt2, isNuxt3 } from './compatibility' export { addComponent, addComponentsDir } from './components' diff --git a/packages/kit/src/layout.ts b/packages/kit/src/layout.ts index 3116d421dd..65fd183163 100644 --- a/packages/kit/src/layout.ts +++ b/packages/kit/src/layout.ts @@ -5,7 +5,7 @@ import { useNuxt } from './context' import { logger } from './logger' import { addTemplate } from './template' -export function addLayout (this: any, template: NuxtTemplate | string, name?: string) { +export function addLayout (template: NuxtTemplate | string, name?: string) { const nuxt = useNuxt() const { filename, src } = addTemplate(template) const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '') diff --git a/packages/kit/src/module/define.ts b/packages/kit/src/module/define.ts index 06435f6923..33ac6d015a 100644 --- a/packages/kit/src/module/define.ts +++ b/packages/kit/src/module/define.ts @@ -79,9 +79,9 @@ function _defineNuxtModule< } // Module format is always a simple function - async function normalizedModule (this: any, inlineOptions: Partial, nuxt: Nuxt): Promise { + async function normalizedModule (inlineOptions: Partial, nuxt = tryUseNuxt()!): Promise { if (!nuxt) { - nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */ + throw new TypeError('Cannot use module outside of Nuxt context') } // Avoid duplicate installs diff --git a/packages/kit/src/module/install.ts b/packages/kit/src/module/install.ts index 07a5cda317..39a460cbed 100644 --- a/packages/kit/src/module/install.ts +++ b/packages/kit/src/module/install.ts @@ -72,34 +72,34 @@ export const normalizeModuleTranspilePath = (p: string) => { export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) { let buildTimeModuleMeta: ModuleMeta = {} - const jiti = createJiti(nuxt.options.rootDir, { - interopDefault: true, - alias: nuxt.options.alias, - }) + const jiti = createJiti(nuxt.options.rootDir, { alias: nuxt.options.alias }) // Import if input is string if (typeof nuxtModule === 'string') { - const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule] - let error: unknown - for (const path of paths) { - try { - const src = jiti.esmResolve(path) - nuxtModule = await jiti.import(src) as NuxtModule + const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule, join(nuxt.options.rootDir, nuxtModule)] - // nuxt-module-builder generates a module.json with metadata including the version - const moduleMetadataPath = join(dirname(src), 'module.json') - if (existsSync(moduleMetadataPath)) { - buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8')) + for (const parentURL of nuxt.options.modulesDir) { + for (const path of paths) { + try { + const src = jiti.esmResolve(path, { parentURL: parentURL.replace(/\/node_modules\/?$/, '') }) + nuxtModule = await jiti.import(src, { default: true }) as NuxtModule + + // nuxt-module-builder generates a module.json with metadata including the version + const moduleMetadataPath = join(dirname(src), 'module.json') + if (existsSync(moduleMetadataPath)) { + buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8')) + } + break + } catch (error: unknown) { + const code = (error as Error & { code?: string }).code + if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT') { + continue + } + logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`) + throw error } - break - } catch (_err: unknown) { - error = _err - continue } - } - if (typeof nuxtModule !== 'function' && error) { - logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`) - throw error + if (typeof nuxtModule !== 'string') { break } } } diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts index 86da4d1a49..8c40713e76 100644 --- a/packages/kit/src/template.ts +++ b/packages/kit/src/template.ts @@ -1,7 +1,7 @@ import { existsSync, promises as fsp } from 'node:fs' import { basename, isAbsolute, join, parse, relative, resolve } from 'pathe' import hash from 'hash-sum' -import type { Nuxt, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema' +import type { Nuxt, NuxtServerTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema' import { withTrailingSlash } from 'ufo' import { defu } from 'defu' import type { TSConfig } from 'pkg-types' @@ -32,6 +32,18 @@ export function addTemplate (_template: NuxtTemplate | string) { return template } +/** + * Adds a virtual file that can be used within the Nuxt Nitro server build. + */ +export function addServerTemplate (template: NuxtServerTemplate) { + const nuxt = useNuxt() + + nuxt.options.nitro.virtual ||= {} + nuxt.options.nitro.virtual[template.filename] = template.getContents + + return template +} + /** * Renders given types using lodash template during build into the project buildDir * and register them as types. @@ -211,13 +223,10 @@ export async function _generateTypes (nuxt: Nuxt) { exclude: [...exclude], } satisfies TSConfig) - const aliases: Record = { - ...nuxt.options.alias, - '#build': nuxt.options.buildDir, - } + const aliases: Record = nuxt.options.alias // Exclude bridge alias types to support Volar - const excludedAlias = [/^@vue\/.*$/] + const excludedAlias = [/^@vue\/.*$/, /^#internal\/nuxt/] const basePath = tsConfig.compilerOptions!.baseUrl ? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl) @@ -320,11 +329,6 @@ export async function writeTypes (nuxt: Nuxt) { await fsp.writeFile(declarationPath, GeneratedBy + '\n' + declaration) } - // This is needed for Nuxt 2 which clears the build directory again before building - // https://github.com/nuxt/nuxt/blob/2.x/packages/builder/src/builder.js#L144 - // @ts-expect-error TODO: Nuxt 2 hook - nuxt.hook('builder:prepared', writeFile) - await writeFile() } diff --git a/packages/kit/test/generate-types.spec.ts b/packages/kit/test/generate-types.spec.ts index b5bcc9a6bb..d65d6809bd 100644 --- a/packages/kit/test/generate-types.spec.ts +++ b/packages/kit/test/generate-types.spec.ts @@ -34,9 +34,6 @@ describe('tsConfig generation', () => { const { tsConfig } = await _generateTypes(mockNuxt) expect(tsConfig.compilerOptions?.paths).toMatchInlineSnapshot(` { - "#build": [ - ".", - ], "some-custom-alias": [ "../some-alias", ], diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 39e8942467..206cba390a 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -60,78 +60,80 @@ }, "dependencies": { "@nuxt/devalue": "^2.0.2", - "@nuxt/devtools": "^1.4.1", + "@nuxt/devtools": "^1.5.2", "@nuxt/kit": "workspace:*", "@nuxt/schema": "workspace:*", - "@nuxt/telemetry": "^2.5.4", + "@nuxt/telemetry": "^2.6.0", "@nuxt/vite-builder": "workspace:*", - "@unhead/dom": "^1.10.4", - "@unhead/ssr": "^1.10.4", - "@unhead/vue": "^1.10.4", - "@vue/shared": "^3.5.1", + "@unhead/dom": "^1.11.7", + "@unhead/shared": "^1.11.7", + "@unhead/ssr": "^1.11.7", + "@unhead/vue": "^1.11.7", + "@vue/shared": "^3.5.11", "acorn": "8.12.1", - "c12": "^2.0.0-beta.2", - "chokidar": "^3.6.0", + "c12": "^2.0.1", + "chokidar": "^4.0.1", "compatx": "^0.1.8", "consola": "^3.2.3", "cookie-es": "^1.2.2", "defu": "^6.1.4", "destr": "^2.0.3", - "devalue": "^5.0.0", + "devalue": "^5.1.1", "errx": "^0.1.0", - "esbuild": "^0.23.1", + "esbuild": "^0.24.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "globby": "^14.0.2", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "hookable": "^5.5.3", - "ignore": "^5.3.2", + "ignore": "^6.0.2", "impound": "^0.1.0", - "jiti": "^2.0.0-beta.3", + "jiti": "^2.3.3", "klona": "^2.0.6", "knitwork": "^1.1.0", "magic-string": "^0.30.11", - "mlly": "^1.7.1", + "mlly": "^1.7.2", "nanotar": "^0.1.1", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", - "nuxi": "^3.13.1", - "nypm": "^0.3.11", - "ofetch": "^1.3.4", - "ohash": "^1.1.3", + "nuxi": "^3.14.0", + "nypm": "^0.3.12", + "ofetch": "^1.4.1", + "ohash": "^1.1.4", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", - "pkg-types": "^1.2.0", + "pkg-types": "^1.2.1", "radix3": "^1.1.2", "scule": "^1.3.0", "semver": "^7.6.3", "std-env": "^3.7.0", "strip-literal": "^2.1.0", - "tinyglobby": "0.2.5", + "tinyglobby": "0.2.9", "ufo": "^1.5.4", "ultrahtml": "^1.5.3", "uncrypto": "^0.1.3", "unctx": "^2.3.1", "unenv": "^1.10.0", - "unimport": "^3.11.1", - "unplugin": "^1.13.1", - "unplugin-vue-router": "^0.10.7", - "unstorage": "^1.11.1", - "untyped": "^1.4.2", - "vue": "^3.5.1", - "vue-bundle-renderer": "^2.1.0", + "unhead": "^1.11.7", + "unimport": "^3.13.1", + "unplugin": "^1.14.1", + "unplugin-vue-router": "^0.10.8", + "unstorage": "^1.12.0", + "untyped": "^1.5.1", + "vue": "^3.5.11", + "vue-bundle-renderer": "^2.1.1", "vue-devtools-stub": "^0.1.0", - "vue-router": "^4.4.3" + "vue-router": "^4.4.5" }, "devDependencies": { - "@nuxt/scripts": "0.8.3", + "@nuxt/scripts": "0.9.4", "@nuxt/ui-templates": "1.3.4", "@parcel/watcher": "2.4.1", - "@types/estree": "1.0.5", - "@vitejs/plugin-vue": "5.1.3", - "@vue/compiler-sfc": "3.5.1", - "unbuild": "3.0.0-rc.7", - "vite": "5.4.3", - "vitest": "2.0.5" + "@types/estree": "1.0.6", + "@vitejs/plugin-vue": "5.1.4", + "@vue/compiler-sfc": "3.5.11", + "unbuild": "3.0.0-rc.11", + "vite": "5.4.8", + "vitest": "2.1.2" }, "peerDependencies": { "@parcel/watcher": "^2.1.0", diff --git a/packages/nuxt/src/app/components/client-fallback.server.ts b/packages/nuxt/src/app/components/client-fallback.server.ts index c1bc3c3e02..dd4e0cdb28 100644 --- a/packages/nuxt/src/app/components/client-fallback.server.ts +++ b/packages/nuxt/src/app/components/client-fallback.server.ts @@ -54,8 +54,10 @@ const NuxtClientFallbackServer = defineComponent({ const defaultSlot = ctx.slots.default?.() const ssrVNodes = createBuffer() - for (let i = 0; i < (defaultSlot?.length || 0); i++) { - ssrRenderVNode(ssrVNodes.push, defaultSlot![i], vm!) + if (defaultSlot) { + for (let i = 0; i < defaultSlot.length; i++) { + ssrRenderVNode(ssrVNodes.push, defaultSlot[i]!, vm!) + } } const buffer = ssrVNodes.getBuffer() diff --git a/packages/nuxt/src/app/components/dev-only.ts b/packages/nuxt/src/app/components/dev-only.ts index a1446dd3dd..94b5ae84e3 100644 --- a/packages/nuxt/src/app/components/dev-only.ts +++ b/packages/nuxt/src/app/components/dev-only.ts @@ -2,6 +2,7 @@ import { defineComponent } from 'vue' export default defineComponent({ name: 'DevOnly', + inheritAttrs: false, setup (_, props) { if (import.meta.dev) { return () => props.slots.default?.() diff --git a/packages/nuxt/src/app/components/island-renderer.ts b/packages/nuxt/src/app/components/island-renderer.ts index ebbca6fd43..189c980e50 100644 --- a/packages/nuxt/src/app/components/island-renderer.ts +++ b/packages/nuxt/src/app/components/island-renderer.ts @@ -8,6 +8,7 @@ import { createError } from '../composables/error' import { islandComponents } from '#build/components.islands.mjs' export default defineComponent({ + name: 'IslandRenderer', props: { context: { type: Object as () => { name: string, props?: Record }, diff --git a/packages/nuxt/src/app/components/nuxt-error-boundary.ts b/packages/nuxt/src/app/components/nuxt-error-boundary.ts index ea54ff8393..8fb88d830a 100644 --- a/packages/nuxt/src/app/components/nuxt-error-boundary.ts +++ b/packages/nuxt/src/app/components/nuxt-error-boundary.ts @@ -2,6 +2,8 @@ import { defineComponent, onErrorCaptured, ref } from 'vue' import { useNuxtApp } from '../nuxt' export default defineComponent({ + name: 'NuxtErrorBoundary', + inheritAttrs: false, emits: { error (_error: unknown) { return true @@ -11,14 +13,16 @@ export default defineComponent({ const error = ref(null) const nuxtApp = useNuxtApp() - onErrorCaptured((err, target, info) => { - if (import.meta.client && (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered)) { - emit('error', err) - nuxtApp.hooks.callHook('vue:error', err, target, info) - error.value = err - return false - } - }) + if (import.meta.client) { + onErrorCaptured((err, target, info) => { + if (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered) { + emit('error', err) + nuxtApp.hooks.callHook('vue:error', err, target, info) + error.value = err + return false + } + }) + } function clearError () { error.value = null diff --git a/packages/nuxt/src/app/components/nuxt-error-page.vue b/packages/nuxt/src/app/components/nuxt-error-page.vue index 14feb22ae0..c41743972b 100644 --- a/packages/nuxt/src/app/components/nuxt-error-page.vue +++ b/packages/nuxt/src/app/components/nuxt-error-page.vue @@ -40,10 +40,10 @@ const description = _error.message || _error.toString() const stack = import.meta.dev && !is404 ? _error.description || `
${stacktrace}
` : undefined // TODO: Investigate side-effect issue with imports -const _Error404 = defineAsyncComponent(() => import('./error-404.vue').then(r => r.default || r)) +const _Error404 = defineAsyncComponent(() => import('./error-404.vue')) const _Error = import.meta.dev - ? defineAsyncComponent(() => import('./error-dev.vue').then(r => r.default || r)) - : defineAsyncComponent(() => import('./error-500.vue').then(r => r.default || r)) + ? defineAsyncComponent(() => import('./error-dev.vue')) + : defineAsyncComponent(() => import('./error-500.vue')) const ErrorTemplate = is404 ? _Error404 : _Error diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index c9a4619c54..c6bb6d8657 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -29,22 +29,26 @@ const getId = import.meta.client ? () => (id++).toString() : randomUUID const components = import.meta.client ? new Map() : undefined async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) { + if (!paths) { return } + const promises: Array> = [] - for (const component in paths) { + for (const [component, item] of Object.entries(paths)) { if (!(components!.has(component))) { promises.push((async () => { - const chunkSource = join(source, paths[component].chunk) + const chunkSource = join(source, item.chunk) const c = await import(/* @vite-ignore */ chunkSource).then(m => m.default || m) components!.set(component, c) })()) } } + await Promise.all(promises) } export default defineComponent({ name: 'NuxtIsland', + inheritAttrs: false, props: { name: { type: String, @@ -275,7 +279,7 @@ export default defineComponent({ 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)) }), ) } } diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index f38ddbd777..77006518a2 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -328,8 +328,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) { const path = typeof to.value === 'string' ? to.value : isExternal.value ? resolveRouteObject(to.value) : router.resolve(to.value).fullPath + const normalizedPath = isExternal.value ? new URL(path, window.location.href).href : path await Promise.all([ - nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}), + nuxtApp.hooks.callHook('link:prefetch', normalizedPath).catch(() => {}), !isExternal.value && !hasTarget.value && preloadRouteComponents(to.value as string, router).catch(() => {}), ]) } diff --git a/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts b/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts index e459781dd2..b8ef06f9c4 100644 --- a/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts +++ b/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts @@ -18,6 +18,7 @@ export const NuxtTeleportIslandSymbol = Symbol('NuxtTeleportIslandComponent') as /* @__PURE__ */ export default defineComponent({ name: 'NuxtTeleportIslandComponent', + inheritAttrs: false, props: { to: { type: String, @@ -38,7 +39,7 @@ export default defineComponent({ const islandContext = nuxtApp.ssrContext!.islandContext! return () => { - const slot = slots.default!()[0] + const slot = slots.default!()[0]! const slotType = slot.type as ExtendedComponent const name = (slotType.__name || slotType.name) as string diff --git a/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts b/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts index 9f9fefc8b1..7db8042735 100644 --- a/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts +++ b/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts @@ -9,6 +9,7 @@ import { NuxtTeleportIslandSymbol } from './nuxt-teleport-island-component' /* @__PURE__ */ export default defineComponent({ name: 'NuxtTeleportIslandSlot', + inheritAttrs: false, props: { name: { type: String, diff --git a/packages/nuxt/src/app/components/test-component-wrapper.ts b/packages/nuxt/src/app/components/test-component-wrapper.ts index 8b0da24381..b2de69d8f5 100644 --- a/packages/nuxt/src/app/components/test-component-wrapper.ts +++ b/packages/nuxt/src/app/components/test-component-wrapper.ts @@ -7,7 +7,7 @@ import { devRootDir } from '#build/nuxt.config.mjs' export default (url: string) => defineComponent({ name: 'NuxtTestComponentWrapper', - + inheritAttrs: false, async setup (props, { attrs }) { const query = parseQuery(new URL(url, 'http://localhost').search) const urlProps = query.props ? destr>(query.props as string) : {} diff --git a/packages/nuxt/src/app/components/utils.ts b/packages/nuxt/src/app/components/utils.ts index bb16708588..0bde127ec5 100644 --- a/packages/nuxt/src/app/components/utils.ts +++ b/packages/nuxt/src/app/components/utils.ts @@ -100,7 +100,7 @@ export function vforToArray (source: any): any[] { const keys = Object.keys(source) const array = new Array(keys.length) for (let i = 0, l = keys.length; i < l; i++) { - const key = keys[i] + const key = keys[i]! array[i] = source[key] } return array diff --git a/packages/nuxt/src/app/composables/error.ts b/packages/nuxt/src/app/composables/error.ts index 8c5885e52d..6bde98fbdc 100644 --- a/packages/nuxt/src/app/composables/error.ts +++ b/packages/nuxt/src/app/composables/error.ts @@ -1,13 +1,15 @@ import type { H3Error } from 'h3' import { createError as createH3Error } from 'h3' import { toRef } from 'vue' +import type { Ref } from 'vue' import { useNuxtApp } from '../nuxt' +import type { NuxtPayload } from '../nuxt' import { useRouter } from './router' export const NUXT_ERROR_SIGNATURE = '__nuxt_error' /** @since 3.0.0 */ -export const useError = () => toRef(useNuxtApp().payload, 'error') +export const useError = (): Ref => toRef(useNuxtApp().payload, 'error') // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface NuxtError extends H3Error {} diff --git a/packages/nuxt/src/app/composables/fetch.ts b/packages/nuxt/src/app/composables/fetch.ts index 093554f82b..5ce5a87d1f 100644 --- a/packages/nuxt/src/app/composables/fetch.ts +++ b/packages/nuxt/src/app/composables/fetch.ts @@ -152,7 +152,7 @@ export function useFetch< let controller: AbortController const asyncData = useAsyncData<_ResT, ErrorT, DataT, PickKeys, DefaultT>(key, () => { - controller?.abort?.('Request aborted as another request to the same endpoint was initiated.') + controller?.abort?.(new DOMException('Request aborted as another request to the same endpoint was initiated.', 'AbortError')) controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController /** @@ -164,7 +164,7 @@ export function useFetch< const timeoutLength = toValue(opts.timeout) let timeoutId: NodeJS.Timeout if (timeoutLength) { - timeoutId = setTimeout(() => controller.abort('Request aborted due to timeout.'), timeoutLength) + timeoutId = setTimeout(() => controller.abort(new DOMException('Request aborted due to timeout.', 'AbortError')), timeoutLength) controller.signal.onabort = () => clearTimeout(timeoutId) } diff --git a/packages/nuxt/src/app/composables/index.ts b/packages/nuxt/src/app/composables/index.ts index c0abdec2e4..05ba560d09 100644 --- a/packages/nuxt/src/app/composables/index.ts +++ b/packages/nuxt/src/app/composables/index.ts @@ -24,7 +24,7 @@ export { useFetch, useLazyFetch } from './fetch' export type { FetchResult, UseFetchOptions } from './fetch' export { useCookie, refreshCookie } from './cookie' export type { CookieOptions, CookieRef } from './cookie' -export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus } from './ssr' +export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader } from './ssr' export { onNuxtReady } from './ready' export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' diff --git a/packages/nuxt/src/app/composables/payload.ts b/packages/nuxt/src/app/composables/payload.ts index 1708bbad99..e24d34feab 100644 --- a/packages/nuxt/src/app/composables/payload.ts +++ b/packages/nuxt/src/app/composables/payload.ts @@ -23,7 +23,7 @@ export async function loadPayload (url: string, opts: LoadPayloadOptions = {}): const nuxtApp = useNuxtApp() const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {} if (payloadURL in cache) { - return cache[payloadURL] + return cache[payloadURL] || null } cache[payloadURL] = isPrerendered(url).then((prerendered) => { if (!prerendered) { diff --git a/packages/nuxt/src/app/composables/preload.ts b/packages/nuxt/src/app/composables/preload.ts index 1358273b8a..cac6f5c85a 100644 --- a/packages/nuxt/src/app/composables/preload.ts +++ b/packages/nuxt/src/app/composables/preload.ts @@ -14,7 +14,12 @@ export const preloadComponents = async (components: string | string[]) => { const nuxtApp = useNuxtApp() components = toArray(components) - await Promise.all(components.map(name => _loadAsyncComponent(nuxtApp.vueApp._context.components[name]))) + await Promise.all(components.map((name) => { + const component = nuxtApp.vueApp._context.components[name] + if (component) { + return _loadAsyncComponent(component) + } + })) } /** diff --git a/packages/nuxt/src/app/composables/ssr.ts b/packages/nuxt/src/app/composables/ssr.ts index 56f3383109..59db9bd327 100644 --- a/packages/nuxt/src/app/composables/ssr.ts +++ b/packages/nuxt/src/app/composables/ssr.ts @@ -1,6 +1,6 @@ import type { H3Event } from 'h3' -import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders } from 'h3' -import { getCurrentInstance } from 'vue' +import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders, getResponseHeader, removeResponseHeader, setResponseHeader } from 'h3' +import { computed, getCurrentInstance, ref } from 'vue' import { useServerHead } from '@unhead/vue' import type { NuxtApp } from '../nuxt' @@ -61,6 +61,34 @@ export function setResponseStatus (arg1: H3Event | number | undefined, arg2?: nu } } +/** @since 3.14.0 */ +export function useResponseHeader (header: string) { + if (import.meta.client) { + if (import.meta.dev) { + return computed({ + get: () => undefined, + set: () => console.warn('[nuxt] Setting response headers is not supported in the browser.'), + }) + } + return ref() + } + + const event = useRequestEvent()! + + return computed({ + get () { + return getResponseHeader(event, header) + }, + set (newValue) { + if (!newValue) { + return removeResponseHeader(event, header) + } + + return setResponseHeader(event, header, newValue) + }, + }) +} + /** @since 3.8.0 */ export function prerenderRoutes (path: string | string[]) { if (!import.meta.server || !import.meta.prerender) { return } diff --git a/packages/nuxt/src/app/entry.ts b/packages/nuxt/src/app/entry.ts index e60a48bd8f..dac40f4b97 100644 --- a/packages/nuxt/src/app/entry.ts +++ b/packages/nuxt/src/app/entry.ts @@ -2,6 +2,7 @@ import { createApp, createSSRApp, nextTick } from 'vue' import type { App } from 'vue' // This file must be imported first as we set globalThis.$fetch via this import +// @ts-expect-error virtual file import '#build/fetch.mjs' import { applyPlugins, createNuxtApp } from './nuxt' @@ -9,6 +10,7 @@ import type { CreateOptions } from './nuxt' import { createError } from './composables/error' +// @ts-expect-error virtual file import '#build/css' // @ts-expect-error virtual file import plugins from '#build/plugins' @@ -65,6 +67,10 @@ if (import.meta.client) { } vueApp.config.errorHandler = handleVueError + // If the errorHandler is not overridden by the user, we unset it after the app is hydrated + nuxt.hook('app:suspense:resolve', () => { + if (vueApp.config.errorHandler === handleVueError) { vueApp.config.errorHandler = undefined } + }) try { await applyPlugins(nuxt, plugins) @@ -82,9 +88,6 @@ if (import.meta.client) { handleVueError(err) } - // If the errorHandler is not overridden by the user, we unset it - if (vueApp.config.errorHandler === handleVueError) { vueApp.config.errorHandler = undefined } - return vueApp } diff --git a/packages/nuxt/src/app/index.ts b/packages/nuxt/src/app/index.ts index 363c72d1bf..43fcc404e1 100644 --- a/packages/nuxt/src/app/index.ts +++ b/packages/nuxt/src/app/index.ts @@ -1,7 +1,7 @@ export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt' export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt' -export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index' +export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index' export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index' export { defineNuxtLink } from './components/index' diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index 3b48c880bd..a903987a01 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -18,10 +18,9 @@ import type { AsyncDataRequestStatus } from '../app/composables/asyncData' import type { NuxtAppManifestMeta } from '../app/composables/manifest' import type { LoadingIndicator } from '../app/composables/loading-indicator' import type { RouteAnnouncer } from '../app/composables/route-announcer' -import type { ViewTransition } from './plugins/view-transitions.client' // @ts-expect-error virtual file -import { appId, multiApp } from '#build/nuxt.config.mjs' +import { appId, chunkErrorEvent, multiApp } from '#build/nuxt.config.mjs' import type { NuxtAppLiterals } from '#app' @@ -276,7 +275,7 @@ export function createNuxtApp (options: CreateOptions) { static: { data: {}, }, - runWithContext (fn: any) { + runWithContext (fn: () => T) { if (nuxtApp._scope.active && !getCurrentScope()) { return nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn)) } @@ -371,12 +370,16 @@ export function createNuxtApp (options: CreateOptions) { defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp) defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp) - // Listen to chunk load errors if (import.meta.client) { - window.addEventListener('nuxt.preloadError', (event) => { - nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload }) - }) - + // Listen to chunk load errors + if (chunkErrorEvent) { + window.addEventListener(chunkErrorEvent, (event) => { + nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload }) + if (nuxtApp.isHydrating || event.payload.message.includes('Unable to preload CSS')) { + event.preventDefault() + } + }) + } window.useNuxtApp = window.useNuxtApp || useNuxtApp // Log errors captured when running plugins, in the `app:created` and `app:beforeMount` hooks diff --git a/packages/nuxt/src/app/plugins/chunk-reload.client.ts b/packages/nuxt/src/app/plugins/chunk-reload.client.ts index e8eb5e2e48..06cd15841c 100644 --- a/packages/nuxt/src/app/plugins/chunk-reload.client.ts +++ b/packages/nuxt/src/app/plugins/chunk-reload.client.ts @@ -10,7 +10,7 @@ export default defineNuxtPlugin({ const router = useRouter() const config = useRuntimeConfig() - const chunkErrors = new Set() + const chunkErrors = new Set() router.beforeEach(() => { chunkErrors.clear() }) nuxtApp.hook('app:chunkError', ({ error }) => { chunkErrors.add(error) }) diff --git a/packages/nuxt/src/app/plugins/dev-server-logs.ts b/packages/nuxt/src/app/plugins/dev-server-logs.ts index 398ab47cdd..468ba80d3c 100644 --- a/packages/nuxt/src/app/plugins/dev-server-logs.ts +++ b/packages/nuxt/src/app/plugins/dev-server-logs.ts @@ -40,7 +40,7 @@ export default defineNuxtPlugin(async (nuxtApp) => { } if (typeof window !== 'undefined') { - const nuxtLogsElement = document.querySelector(`[data-nuxt-logs="${nuxtApp._name}"]`) + const nuxtLogsElement = document.querySelector(`[data-nuxt-logs="${nuxtApp._id}"]`) const content = nuxtLogsElement?.textContent const logs = content ? parse(content, { ...devRevivers, ...nuxtApp._payloadRevivers }) as LogObject[] : [] await nuxtApp.hooks.callHook('dev:ssr-logs', logs) diff --git a/packages/nuxt/src/app/plugins/revive-payload.client.ts b/packages/nuxt/src/app/plugins/revive-payload.client.ts index 3ab2f211df..2d2c2e8bbd 100644 --- a/packages/nuxt/src/app/plugins/revive-payload.client.ts +++ b/packages/nuxt/src/app/plugins/revive-payload.client.ts @@ -7,18 +7,18 @@ import { defineNuxtPlugin, useNuxtApp } from '../nuxt' // @ts-expect-error Virtual file. import { componentIslands } from '#build/nuxt.config.mjs' -const revivers: Record any> = { - NuxtError: data => createError(data), - EmptyShallowRef: data => shallowRef(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data)), - EmptyRef: data => ref(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data)), - ShallowRef: data => shallowRef(data), - ShallowReactive: data => shallowReactive(data), - Ref: data => ref(data), - Reactive: data => reactive(data), -} +const revivers: [string, (data: any) => any][] = [ + ['NuxtError', data => createError(data)], + ['EmptyShallowRef', data => shallowRef(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data))], + ['EmptyRef', data => ref(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data))], + ['ShallowRef', data => shallowRef(data)], + ['ShallowReactive', data => shallowReactive(data)], + ['Ref', data => ref(data)], + ['Reactive', data => reactive(data)], +] if (componentIslands) { - revivers.Island = ({ key, params, result }: any) => { + revivers.push(['Island', ({ key, params, result }: any) => { const nuxtApp = useNuxtApp() if (!nuxtApp.isHydrating) { nuxtApp.payload.data[key] = nuxtApp.payload.data[key] || $fetch(`/__nuxt_island/${key}.json`, { @@ -33,15 +33,15 @@ if (componentIslands) { html: '', ...result, } - } + }]) } export default defineNuxtPlugin({ name: 'nuxt:revive-payload:client', order: -30, async setup (nuxtApp) { - for (const reviver in revivers) { - definePayloadReviver(reviver, revivers[reviver as keyof typeof revivers]) + for (const [reviver, fn] of revivers) { + definePayloadReviver(reviver, fn) } Object.assign(nuxtApp.payload, await nuxtApp.runWithContext(getNuxtClientPayload)) delete window.__NUXT__ diff --git a/packages/nuxt/src/app/plugins/view-transitions.client.ts b/packages/nuxt/src/app/plugins/view-transitions.client.ts index e23dc02a04..c24fb0e35e 100644 --- a/packages/nuxt/src/app/plugins/view-transitions.client.ts +++ b/packages/nuxt/src/app/plugins/view-transitions.client.ts @@ -56,15 +56,3 @@ export default defineNuxtPlugin((nuxtApp) => { finishTransition = undefined }) }) - -export interface ViewTransition { - ready: Promise - finished: Promise - updateCallbackDone: Promise -} - -declare global { - interface Document { - startViewTransition?: (callback: () => Promise | void) => ViewTransition - } -} diff --git a/packages/nuxt/src/components/module.ts b/packages/nuxt/src/components/module.ts index c42ab16aae..8f1589bb08 100644 --- a/packages/nuxt/src/components/module.ts +++ b/packages/nuxt/src/components/module.ts @@ -1,16 +1,18 @@ -import fs, { statSync } from 'node:fs' -import { join, normalize, relative, resolve } from 'pathe' -import { addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, logger, resolveAlias, updateTemplates } from '@nuxt/kit' +import { existsSync, statSync, writeFileSync } from 'node:fs' +import { isAbsolute, join, normalize, relative, resolve } from 'pathe' +import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit' import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema' import { distDir } from '../dirs' -import { clientFallbackAutoIdPlugin } from './client-fallback-auto-id' import { componentNamesTemplate, componentsIslandsTemplate, componentsMetadataTemplate, componentsPluginTemplate, componentsTypeTemplate } from './templates' import { scanComponents } from './scan' -import { loaderPlugin } from './loader' -import { TreeShakeTemplatePlugin } from './tree-shake' -import { componentsChunkPlugin, islandsTransform } from './islandsTransform' -import { createTransformPlugin } from './transform' + +import { ClientFallbackAutoIdPlugin } from './plugins/client-fallback-auto-id' +import { LoaderPlugin } from './plugins/loader' +import { ComponentsChunkPlugin, IslandsTransformPlugin } from './plugins/islands-transform' +import { TransformPlugin } from './plugins/transform' +import { TreeShakeTemplatePlugin } from './plugins/tree-shake' +import { ComponentNamePlugin } from './plugins/component-names' const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string' const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch { return false } } @@ -30,7 +32,7 @@ export default defineNuxtModule({ defaults: { dirs: [], }, - setup (componentOptions, nuxt) { + async setup (componentOptions, nuxt) { let componentDirs: ComponentsDir[] = [] const context = { components: [] as Component[], @@ -42,6 +44,11 @@ export default defineNuxtModule({ : context.components } + if (nuxt.options.experimental.normalizeComponentNames) { + addBuildPlugin(ComponentNamePlugin({ sourcemap: !!nuxt.options.sourcemap.client, getComponents }), { server: false }) + addBuildPlugin(ComponentNamePlugin({ sourcemap: !!nuxt.options.sourcemap.server, getComponents }), { client: false }) + } + const normalizeDirs = (dir: any, cwd: string, options?: { priority?: number }): ComponentsDir[] => { if (Array.isArray(dir)) { return dir.map(dir => normalizeDirs(dir, cwd, options)).flat().sort(compareDirByPathLength) @@ -127,23 +134,18 @@ export default defineNuxtModule({ addTemplate(componentsMetadataTemplate) } - const unpluginServer = createTransformPlugin(nuxt, getComponents, 'server') - const unpluginClient = createTransformPlugin(nuxt, getComponents, 'client') - - addVitePlugin(() => unpluginServer.vite(), { server: true, client: false }) - addVitePlugin(() => unpluginClient.vite(), { server: false, client: true }) - - addWebpackPlugin(() => unpluginServer.webpack(), { server: true, client: false }) - addWebpackPlugin(() => unpluginClient.webpack(), { server: false, client: true }) + const serverComponentRuntime = await findPath(join(distDir, 'components/runtime/server-component')) ?? join(distDir, 'components/runtime/server-component') + addBuildPlugin(TransformPlugin(nuxt, { getComponents, serverComponentRuntime, mode: 'server' }), { server: true, client: false }) + addBuildPlugin(TransformPlugin(nuxt, { getComponents, serverComponentRuntime, mode: 'client' }), { server: false, client: true }) // Do not prefetch global components chunks nuxt.hook('build:manifest', (manifest) => { const sourceFiles = getComponents().filter(c => c.global).map(c => relative(nuxt.options.srcDir, c.filePath)) - for (const key in manifest) { - if (manifest[key].isEntry) { - manifest[key].dynamicImports = - manifest[key].dynamicImports?.filter(i => !sourceFiles.includes(i)) + for (const chunk of Object.values(manifest)) { + if (chunk.isEntry) { + chunk.dynamicImports = + chunk.dynamicImports?.filter(i => !sourceFiles.includes(i)) } } }) @@ -161,7 +163,7 @@ export default defineNuxtModule({ } }) - const serverPlaceholderPath = resolve(distDir, 'app/components/server-placeholder') + const serverPlaceholderPath = await findPath(join(distDir, 'app/components/server-placeholder')) ?? join(distDir, 'app/components/server-placeholder') // Scan components and add to plugin nuxt.hook('app:templates', async (app) => { @@ -169,6 +171,10 @@ export default defineNuxtModule({ await nuxt.callHook('components:extend', newComponents) // add server placeholder for .client components server side. issue: #7085 for (const component of newComponents) { + if (!(component as any /* untyped internal property */)._scanned && !(component.filePath in nuxt.vfs) && isAbsolute(component.filePath) && !existsSync(component.filePath)) { + // attempt to resolve component path + component.filePath = await resolvePath(component.filePath, { fallbackToOriginal: true }) + } if (component.mode === 'client' && !newComponents.some(c => c.pascalName === component.pascalName && c.mode === 'server')) { newComponents.push({ ...component, @@ -208,109 +214,78 @@ export default defineNuxtModule({ } }) - nuxt.hook('vite:extendConfig', (config, { isClient, isServer }) => { - const mode = isClient ? 'client' : 'server' + addBuildPlugin(TreeShakeTemplatePlugin({ sourcemap: !!nuxt.options.sourcemap.server, getComponents }), { client: false }) - config.plugins = config.plugins || [] - if (isServer) { - config.plugins.push(TreeShakeTemplatePlugin.vite({ - sourcemap: !!nuxt.options.sourcemap[mode], - getComponents, - })) - } - if (nuxt.options.experimental.clientFallback) { - config.plugins.push(clientFallbackAutoIdPlugin.vite({ - sourcemap: !!nuxt.options.sourcemap[mode], - rootDir: nuxt.options.rootDir, - })) - } - config.plugins.push(loaderPlugin.vite({ - sourcemap: !!nuxt.options.sourcemap[mode], - getComponents, - mode, - transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined, - experimentalComponentIslands: !!nuxt.options.experimental.componentIslands, - })) + if (nuxt.options.experimental.clientFallback) { + addBuildPlugin(ClientFallbackAutoIdPlugin({ sourcemap: !!nuxt.options.sourcemap.client, rootDir: nuxt.options.rootDir }), { server: false }) + addBuildPlugin(ClientFallbackAutoIdPlugin({ sourcemap: !!nuxt.options.sourcemap.server, rootDir: nuxt.options.rootDir }), { client: false }) + } - if (nuxt.options.experimental.componentIslands) { - const selectiveClient = typeof nuxt.options.experimental.componentIslands === 'object' && nuxt.options.experimental.componentIslands.selectiveClient + const sharedLoaderOptions = { + getComponents, + serverComponentRuntime, + transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined, + experimentalComponentIslands: !!nuxt.options.experimental.componentIslands, + } + + addBuildPlugin(LoaderPlugin({ ...sharedLoaderOptions, sourcemap: !!nuxt.options.sourcemap.client, mode: 'client' }), { server: false }) + addBuildPlugin(LoaderPlugin({ ...sharedLoaderOptions, sourcemap: !!nuxt.options.sourcemap.server, mode: 'server' }), { client: false }) + + if (nuxt.options.experimental.componentIslands) { + const selectiveClient = typeof nuxt.options.experimental.componentIslands === 'object' && nuxt.options.experimental.componentIslands.selectiveClient + + addVitePlugin({ + name: 'nuxt-server-component-hmr', + handleHotUpdate (ctx) { + const components = getComponents() + const filePath = normalize(ctx.file) + const comp = components.find(c => c.filePath === filePath) + if (comp?.mode === 'server') { + ctx.server.ws.send({ + event: `nuxt-server-component:${comp.pascalName}`, + type: 'custom', + }) + } + }, + }, { server: false }) + + addBuildPlugin(IslandsTransformPlugin({ getComponents, selectiveClient }), { client: false }) + + // TODO: refactor this + nuxt.hook('vite:extendConfig', (config, { isClient }) => { + config.plugins = config.plugins || [] if (isClient && selectiveClient) { - fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') + writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') if (!nuxt.options.dev) { - config.plugins.push(componentsChunkPlugin.vite({ + config.plugins.push(ComponentsChunkPlugin.vite({ getComponents, buildDir: nuxt.options.buildDir, })) } else { - fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), `export const paths = ${JSON.stringify( + writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), `export const paths = ${JSON.stringify( getComponents().filter(c => c.mode === 'client' || c.mode === 'all').reduce((acc, c) => { if (c.filePath.endsWith('.vue') || c.filePath.endsWith('.js') || c.filePath.endsWith('.ts')) { return Object.assign(acc, { [c.pascalName]: `/@fs/${c.filePath}` }) } - const filePath = fs.existsSync(`${c.filePath}.vue`) ? `${c.filePath}.vue` : fs.existsSync(`${c.filePath}.js`) ? `${c.filePath}.js` : `${c.filePath}.ts` + const filePath = existsSync(`${c.filePath}.vue`) ? `${c.filePath}.vue` : existsSync(`${c.filePath}.js`) ? `${c.filePath}.js` : `${c.filePath}.ts` return Object.assign(acc, { [c.pascalName]: `/@fs/${filePath}` }) }, {} as Record), )}`) } } + }) - if (isServer) { - config.plugins.push(islandsTransform.vite({ - getComponents, - selectiveClient, - })) - } - } - if (!isServer && nuxt.options.experimental.componentIslands) { - config.plugins.push({ - name: 'nuxt-server-component-hmr', - handleHotUpdate (ctx) { - const components = getComponents() - const filePath = normalize(ctx.file) - const comp = components.find(c => c.filePath === filePath) - if (comp?.mode === 'server') { - ctx.server.ws.send({ - event: `nuxt-server-component:${comp.pascalName}`, - type: 'custom', - }) + for (const key of ['rspack:config', 'webpack:config'] as const) { + nuxt.hook(key, (configs) => { + configs.forEach((config) => { + const mode = config.name === 'client' ? 'client' : 'server' + config.plugins = config.plugins || [] + + if (mode !== 'server') { + writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') } - }, + }) }) } - }) - nuxt.hook('webpack:config', (configs) => { - configs.forEach((config) => { - const mode = config.name === 'client' ? 'client' : 'server' - config.plugins = config.plugins || [] - if (mode === 'server') { - config.plugins.push(TreeShakeTemplatePlugin.webpack({ - sourcemap: !!nuxt.options.sourcemap[mode], - getComponents, - })) - } - if (nuxt.options.experimental.clientFallback) { - config.plugins.push(clientFallbackAutoIdPlugin.webpack({ - sourcemap: !!nuxt.options.sourcemap[mode], - rootDir: nuxt.options.rootDir, - })) - } - config.plugins.push(loaderPlugin.webpack({ - sourcemap: !!nuxt.options.sourcemap[mode], - getComponents, - mode, - transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined, - experimentalComponentIslands: !!nuxt.options.experimental.componentIslands, - })) - - if (nuxt.options.experimental.componentIslands) { - if (mode === 'server') { - config.plugins.push(islandsTransform.webpack({ - getComponents, - })) - } else { - fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') - } - } - }) - }) + } }, }) diff --git a/packages/nuxt/src/components/client-fallback-auto-id.ts b/packages/nuxt/src/components/plugins/client-fallback-auto-id.ts similarity index 92% rename from packages/nuxt/src/components/client-fallback-auto-id.ts rename to packages/nuxt/src/components/plugins/client-fallback-auto-id.ts index 7aa42f1d0a..9f3e1b119c 100644 --- a/packages/nuxt/src/components/client-fallback-auto-id.ts +++ b/packages/nuxt/src/components/plugins/client-fallback-auto-id.ts @@ -3,7 +3,7 @@ import type { ComponentsOptions } from '@nuxt/schema' import MagicString from 'magic-string' import { isAbsolute, relative } from 'pathe' import { hash } from 'ohash' -import { isVue } from '../core/utils' +import { isVue } from '../../core/utils' interface LoaderOptions { sourcemap?: boolean @@ -12,7 +12,7 @@ interface LoaderOptions { } const CLIENT_FALLBACK_RE = /<(?:NuxtClientFallback|nuxt-client-fallback)(?: [^>]*)?>/ const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g -export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions) => { +export const ClientFallbackAutoIdPlugin = (options: LoaderOptions) => createUnplugin(() => { const exclude = options.transform?.exclude || [] const include = options.transform?.include || [] diff --git a/packages/nuxt/src/components/plugins/component-names.ts b/packages/nuxt/src/components/plugins/component-names.ts new file mode 100644 index 0000000000..4a24ebe96b --- /dev/null +++ b/packages/nuxt/src/components/plugins/component-names.ts @@ -0,0 +1,46 @@ +import { createUnplugin } from 'unplugin' +import MagicString from 'magic-string' +import type { Component } from 'nuxt/schema' +import { isVue } from '../../core/utils' + +interface NameDevPluginOptions { + sourcemap: boolean + getComponents: () => Component[] +} +/** + * Set the default name of components to their PascalCase name + */ +export const ComponentNamePlugin = (options: NameDevPluginOptions) => createUnplugin(() => { + return { + name: 'nuxt:component-name-plugin', + enforce: 'post', + transformInclude (id) { + return isVue(id) || !!id.match(/\.[tj]sx$/) + }, + transform (code, id) { + const filename = id.match(/([^/\\]+)\.\w+$/)?.[1] + if (!filename) { + return + } + + const component = options.getComponents().find(c => c.filePath === id) + + if (!component) { + return + } + + const NAME_RE = new RegExp(`__name:\\s*['"]${filename}['"]`) + const s = new MagicString(code) + s.replace(NAME_RE, `__name: ${JSON.stringify(component.pascalName)}`) + + if (s.hasChanged()) { + return { + code: s.toString(), + map: options.sourcemap + ? s.generateMap({ hires: true }) + : undefined, + } + } + }, + } +}) diff --git a/packages/nuxt/src/components/islandsTransform.ts b/packages/nuxt/src/components/plugins/islands-transform.ts similarity index 63% rename from packages/nuxt/src/components/islandsTransform.ts rename to packages/nuxt/src/components/plugins/islands-transform.ts index e10f1cd957..70ad9fb895 100644 --- a/packages/nuxt/src/components/islandsTransform.ts +++ b/packages/nuxt/src/components/plugins/islands-transform.ts @@ -9,7 +9,7 @@ import { ELEMENT_NODE, parse, walk } from 'ultrahtml' import { hash } from 'ohash' import { resolvePath } from '@nuxt/kit' import defu from 'defu' -import { isVue } from '../core/utils' +import { isVue } from '../../core/utils' interface ServerOnlyComponentTransformPluginOptions { getComponents: () => Component[] @@ -24,7 +24,7 @@ interface ComponentChunkOptions { buildDir: string } -const SCRIPT_RE = /]*>/g +const SCRIPT_RE = /]*>/gi const HAS_SLOT_OR_CLIENT_RE = /]*>|nuxt-client/ const TEMPLATE_RE = /