diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8a3d0b82acd1..3c387296d5e4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts@sha256:0e910f435308c36ea60b4cfd7b80208044d77a074d16b768a81901ce938a62dc +FROM node:lts@sha256:99981c3d1aac0d98cd9f03f74b92dddf30f30ffb0b34e6df8bd96283f62f12c6 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/workflows/autofix-docs.yml b/.github/workflows/autofix-docs.yml index b4ed01816e8b..167999e92dc2 100644 --- a/.github/workflows/autofix-docs.yml +++ b/.github/workflows/autofix-docs.yml @@ -1,4 +1,4 @@ -name: autofix.ci # needed to securely identify the workflow +name: autofix.ci # needed to securely identify the workflow on: pull_request: @@ -18,10 +18,10 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -33,4 +33,4 @@ jobs: - name: Lint (docs) run: pnpm lint:docs:fix - - uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c + - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 3c914e7dbd25..68a21e9a323f 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -1,4 +1,4 @@ -name: autofix.ci # needed to securely identify the workflow +name: autofix.ci # needed to securely identify the workflow on: pull_request: @@ -14,17 +14,17 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies run: pnpm install - name: Check engine ranges, peer dependency ranges and installed versions - run: pnpm installed-check --fix -d + run: pnpm installed-check --no-include-workspace-root --ignore-dev --workspace-ignore='test/**,playground' --fix - name: Build (stub) run: pnpm dev:prepare @@ -55,4 +55,4 @@ jobs: - name: Lint (code) run: pnpm lint:fix - - uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c + - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index ccbf0feaeead..000000000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: benchmark - -on: - workflow_dispatch: - # pull_request: - # paths-ignore: - # - "docs/**" - # - "*.md" - # branches: - # - main - # - "!v[0-9]*" - -# https://github.com/vitejs/vite/blob/main/.github/workflows/ci.yml -env: - # 7 GiB by default on GitHub, setting to 6 GiB - # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources - NODE_OPTIONS: --max-old-space-size=6144 - -# Remove default permissions of GITHUB_TOKEN for security -# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs -permissions: {} - -concurrency: - group: ${{ github.workflow }}-${{ github.event.number || github.sha }} - cancel-in-progress: ${{ github.event_name != 'push' }} - -jobs: - run: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install - - - name: Build (stub) - run: pnpm dev:prepare - - - name: Build - run: pnpm build - - - name: Run benchmarks - uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0 - with: - run: pnpm vitest bench - token: ${{ secrets.CODSPEED_TOKEN }} diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index e66a5f6fe612..52be56d3e71b 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -25,10 +25,10 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29bda499bc9e..31da488984bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,10 +38,10 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -56,11 +56,8 @@ jobs: - name: Build run: pnpm build - - name: Check types - run: pnpm test:attw - - name: Cache dist - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: retention-days: 3 name: dist @@ -81,7 +78,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: config: | paths: @@ -98,7 +95,7 @@ jobs: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: category: "/language:${{ matrix.language }}" @@ -115,10 +112,10 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -146,10 +143,10 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -161,6 +158,9 @@ jobs: - name: Lint run: pnpm lint + - name: Check built types + run: pnpm test:attw + test-unit: # autofix workflow will be triggered instead for PRs if: github.event_name == 'push' @@ -170,10 +170,10 @@ jobs: - build steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -188,6 +188,59 @@ jobs: - name: Test (runtime unit) run: pnpm test:runtime + test-size: + runs-on: ubuntu-latest + needs: + - build + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 + with: + node-version: lts/* + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Restore dist cache + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: dist + path: packages + + - name: Check bundle size + run: pnpm vitest run bundle + + test-benchmark: + runs-on: ubuntu-latest + needs: + - build + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 + with: + node-version: lts/* + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Restore dist cache + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: dist + path: packages + + - name: Run benchmarks + uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0 + with: + run: pnpm vitest bench + token: ${{ secrets.CODSPEED_TOKEN }} + test-fixtures: runs-on: ${{ matrix.os }} needs: @@ -227,8 +280,8 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: node-version: ${{ matrix.node }} cache: "pnpm" @@ -255,25 +308,23 @@ jobs: TEST_PAYLOAD: ${{ matrix.payload }} SKIP_BUNDLE_SIZE: true - - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 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 }} - build-release: + release-nightly: concurrency: group: release permissions: id-token: write if: | github.event_name == 'push' && - github.repository == 'nuxt/nuxt' && + github.repository_owner == 'nuxt' && !contains(github.event.head_commit.message, '[skip-release]') && !startsWith(github.event.head_commit.message, 'docs') needs: - - lint - build - - test-fixtures runs-on: ubuntu-latest timeout-minutes: 20 @@ -281,10 +332,10 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -299,22 +350,13 @@ jobs: - name: Release Edge run: ./scripts/release-edge.sh ${{ github.ref == 'refs/heads/main' && 'latest' || '3x' }} env: - NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} NPM_CONFIG_PROVENANCE: true release-pr: - concurrency: - group: release - permissions: - id-token: write - pull-requests: write - if: | - github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.name, '🧷 edge release') + if: github.repository_owner == 'nuxt' && github.event_name != 'push' needs: - - lint - build - - test-fixtures runs-on: ubuntu-latest timeout-minutes: 20 @@ -322,10 +364,10 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -337,8 +379,4 @@ jobs: name: dist path: packages - - name: Release Edge - run: ./scripts/release-edge.sh pr-${{ github.event.issue.number }} - env: - NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} - NPM_CONFIG_PROVENANCE: true + - run: pnpm pkg-pr-new publish --compact './packages/kit' './packages/nuxt' './packages/rspack' './packages/schema' './packages/vite' './packages/webpack' diff --git a/.github/workflows/docs-check-links.yml b/.github/workflows/docs-check-links.yml index 5f7b8d97a109..357476f057a7 100644 --- a/.github/workflows/docs-check-links.yml +++ b/.github/workflows/docs-check-links.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Lychee link checker - uses: lycheeverse/lychee-action@f796c8b7d468feb9b8c0a46da3fac0af6874d374 # for v1.8.0 + uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 # 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 4353c01c5950..c002c027aab1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,10 +22,10 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies diff --git a/.github/workflows/lint-monorepo.yml b/.github/workflows/lint-monorepo.yml index 7b225b16e0ef..45a8eadac801 100644 --- a/.github/workflows/lint-monorepo.yml +++ b/.github/workflows/lint-monorepo.yml @@ -1,4 +1,4 @@ -name: CI +name: ci on: push: @@ -26,10 +26,10 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* cache: "pnpm" - name: Install dependencies @@ -39,4 +39,4 @@ jobs: run: pnpm sherif -r multiple-dependency-versions - name: Check engine ranges, peer dependency ranges and installed versions - run: pnpm installed-check -d + run: pnpm installed-check --no-include-workspace-root --ignore-dev --workspace-ignore='test/**,playground' diff --git a/.github/workflows/lint-workflows.yml b/.github/workflows/lint-workflows.yml index 1c9a88c27d05..6d2424c52db5 100644 --- a/.github/workflows/lint-workflows.yml +++ b/.github/workflows/lint-workflows.yml @@ -26,6 +26,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions - name: Check workflow files - uses: docker://rhysd/actionlint:1.7.5@sha256:d1b3d067b912ea4cbf697750cee80f59114414365b916167b4c7b4cf9504ca9a + uses: docker://rhysd/actionlint:1.7.7@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9 with: args: -color diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0b74d25c2b0..b8f242785b44 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,10 +22,10 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - run: corepack enable - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: - node-version: 20 + node-version: lts/* registry-url: "https://registry.npmjs.org/" cache: "pnpm" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index dcd76a0c88e3..6b5a992f8cc8 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (опциональный). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 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@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 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 d563f5045fab..41e53837bc97 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -17,7 +17,7 @@ jobs: statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR if: github.repository == 'nuxt/nuxt' && !startsWith(github.head_ref, 'v') runs-on: ubuntu-latest - name: Semantic pull request + name: semantic-pr steps: - name: Validate PR title uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 23dccb624d1a..8620cec097c0 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'workflow_dispatch' || github.repository == 'nuxt/nuxt' steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: days-before-stale: -1 # Issues and PR will never be flagged stale automatically. stale-issue-label: 'needs reproduction' # Label that flags an issue as stale. diff --git a/debug/build-config.ts b/debug/build-config.ts new file mode 100644 index 000000000000..254d0d215e08 --- /dev/null +++ b/debug/build-config.ts @@ -0,0 +1,23 @@ +import { fileURLToPath } from 'node:url' +import process from 'node:process' + +import type { InputPluginOption } from 'rollup' +import type { BuildOptions } from 'unbuild' + +import { AnnotateFunctionTimingsPlugin } from './plugins/timings-unbuild' + +export const stubOptions = { + jiti: { + transformOptions: { + babel: { + plugins: (process.env.TIMINGS_DEBUG ? [fileURLToPath(new URL('./plugins/timings-babel.mjs', import.meta.url))] : []) as any, + }, + }, + }, +} satisfies BuildOptions['stubOptions'] + +export function addRollupTimingsPlugin (options: { plugins: InputPluginOption[] }) { + if (process.env.TIMINGS_DEBUG) { + options.plugins.push(AnnotateFunctionTimingsPlugin()) + } +} diff --git a/debug/plugins/timings-babel.mjs b/debug/plugins/timings-babel.mjs new file mode 100644 index 000000000000..80bf51458999 --- /dev/null +++ b/debug/plugins/timings-babel.mjs @@ -0,0 +1,167 @@ +// @ts-check + +import { fileURLToPath } from 'node:url' + +import { declare } from '@babel/helper-plugin-utils' +import { types as t } from '@babel/core' + +const metricsPath = fileURLToPath(new URL('../../debug-timings.json', import.meta.url)) + +// inlined from https://github.com/danielroe/errx +function captureStackTrace () { + const IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[a-z]:[/\\]/i + const LINE_RE = /^\s+at (?:(?[^)]+) \()?(?[^)]+)\)?$/u + const SOURCE_RE = /^(?.+):(?\d+):(?\d+)$/u + + if (!Error.captureStackTrace) { + return [] + } + // eslint-disable-next-line unicorn/error-message + const stack = new Error() + Error.captureStackTrace(stack) + const trace = [] + for (const line of stack.stack?.split('\n') || []) { + const parsed = LINE_RE.exec(line)?.groups + if (!parsed) { + continue + } + if (!parsed.source) { + continue + } + const parsedSource = SOURCE_RE.exec(parsed.source)?.groups + if (parsedSource) { + Object.assign(parsed, parsedSource) + } + if (IS_ABSOLUTE_RE.test(parsed.source)) { + parsed.source = `file://${parsed.source}` + } + if (parsed.source === import.meta.url) { + continue + } + for (const key of ['line', 'column']) { + if (parsed[key]) { + // @ts-expect-error + parsed[key] = Number(parsed[key]) + } + } + trace.push(parsed) + } + return trace +} + +export const leading = ` +import { writeFileSync as ____writeFileSync } from 'node:fs' +const ___captureStackTrace = ${captureStackTrace.toString()}; +globalThis.___calls ||= {}; +globalThis.___timings ||= {}; +globalThis.___callers ||= {};` + +function onExit () { + if (globalThis.___logged) { return } + globalThis.___logged = true + + // eslint-disable-next-line no-undef + ____writeFileSync(metricsPath, JSON.stringify(Object.fromEntries(Object.entries(globalThis.___timings).map(([name, time]) => [ + name, + { + time: Number(Number(time).toFixed(2)), + calls: globalThis.___calls[name], + callers: globalThis.___callers[name] ? Object.fromEntries(Object.entries(globalThis.___callers[name]).map(([name, count]) => [name.trim(), count]).sort((a, b) => typeof b[0] === 'string' && typeof a[0] === 'string' ? a[0].localeCompare(b[0]) : 0)) : undefined, + }, + ]).sort((a, b) => typeof b[0] === 'string' && typeof a[0] === 'string' ? a[0].localeCompare(b[0]) : 0)), null, 2)) + + // worst by total time + const timings = Object.entries(globalThis.___timings) + + const topFunctionsTotalTime = timings + .sort((a, b) => b[1] - a[1]) + .slice(0, 20) + .map(([name, time]) => ({ + name, + time: Number(Number(time).toFixed(2)), + calls: globalThis.___calls[name], + callers: globalThis.___callers[name] && Object.entries(globalThis.___callers[name]).map(([name, count]) => `${name.trim()} (${count})`).join(', '), + })) + + // eslint-disable-next-line no-console + console.log('Top 20 functions by total time:') + // eslint-disable-next-line no-console + console.table(topFunctionsTotalTime) + + // worst by average time (excluding single calls) + const topFunctionsAverageTime = timings + .filter(([name]) => (globalThis.___calls[name] || 0) > 1) + .map(([name, time]) => [name, time / (globalThis.___calls[name] || 1)]) + // @ts-expect-error + .sort((a, b) => b[1] - a[1]) + .slice(0, 20) + .map(([name, time]) => ({ + name, + time: Number(Number(time).toFixed(2)), + calls: name && globalThis.___calls[name], + callers: name && globalThis.___callers[name] && Object.entries(globalThis.___callers[name]).sort((a, b) => b[1] - a[1]).map(([name, count]) => `${name.trim()} (${count})`).join(', '), + })) + + // eslint-disable-next-line no-console + console.log('Top 20 functions by average time:') + // eslint-disable-next-line no-console + console.table(topFunctionsAverageTime) +} + +export const trailing = `process.on("exit", ${onExit.toString().replace('metricsPath', JSON.stringify(metricsPath))})` + +/** @param {string} functionName */ +export function generateInitCode (functionName) { + return ` + ___calls[${JSON.stringify(functionName)}] = (___calls[${JSON.stringify(functionName)}] || 0) + 1; + ___timings[${JSON.stringify(functionName)}] ||= 0; + const ___now = Date.now();` +} + +/** @param {string} functionName */ +export function generateFinallyCode (functionName) { + return ` + ___timings[${JSON.stringify(functionName)}] += Date.now() - ___now; + try { + const ___callee = ___captureStackTrace()[1]?.function; + if (___callee) { + ___callers[${JSON.stringify(functionName)}] ||= {}; + ___callers[${JSON.stringify(functionName)}][' ' + ___callee] = (___callers[${JSON.stringify(functionName)}][' ' + ___callee] || 0) + 1; + } + } catch {}` +} + +export default declare((api) => { + api.assertVersion(7) + + return { + name: 'annotate-function-timings', + visitor: { + Program (path) { + path.unshiftContainer('body', t.expressionStatement(t.identifier(leading))) + path.pushContainer('body', t.expressionStatement(t.identifier(trailing))) + }, + FunctionDeclaration (path) { + const functionName = path.node.id?.name + + const start = path.get('body').get('body')[0] + const end = path.get('body').get('body').pop() + + if (!functionName || ['createJiti', '___captureStackTrace', '_interopRequireDefault'].includes(functionName) || !start || !end) { return } + + const initCode = generateInitCode(functionName) + const finallyCode = generateFinallyCode(functionName) + + const originalCode = path.get('body').get('body').map(statement => statement.node) + path.get('body').get('body').forEach(statement => statement.remove()) + + path.get('body').unshiftContainer('body', t.expressionStatement(t.identifier(initCode))) + path.get('body').pushContainer('body', t.tryStatement( + t.blockStatement(originalCode), + t.catchClause(t.identifier('e'), t.blockStatement([])), + t.blockStatement([t.expressionStatement(t.identifier(finallyCode))]), + )) + }, + }, + } +}) diff --git a/debug/plugins/timings-unbuild.ts b/debug/plugins/timings-unbuild.ts new file mode 100644 index 000000000000..d1fdd5df8de3 --- /dev/null +++ b/debug/plugins/timings-unbuild.ts @@ -0,0 +1,57 @@ +import type { Plugin } from 'rollup' +import { parse } from 'acorn' +import { type Node, walk } from 'estree-walker' +import MagicString from 'magic-string' +import tsBlankSpace from 'ts-blank-space' + +import { generateFinallyCode, generateInitCode, leading, trailing } from './timings-babel.mjs' + +declare global { +// eslint-disable-next-line no-var + var ___logged: boolean + // eslint-disable-next-line no-var + var ___timings: Record + // eslint-disable-next-line no-var + var ___calls: Record + // eslint-disable-next-line no-var + var ___callers: Record + // eslint-disable-next-line no-var + var ____writeFileSync: typeof import('fs').writeFileSync +} + +export function AnnotateFunctionTimingsPlugin () { + return { + name: 'timings', + transform: { + order: 'post', + handler (code, id) { + const s = new MagicString(code) + try { + const ast = parse(tsBlankSpace(code), { sourceType: 'module', ecmaVersion: 'latest', locations: true }) + walk(ast as Node, { + enter (node) { + if (node.type === 'FunctionDeclaration' && node.id && node.id.name) { + const functionName = node.id.name ? `${node.id.name} (${id.match(/[\\/]packages[\\/]([^/]+)[\\/]/)?.[1]})` : '' + const start = (node.body as Node & { start: number, end: number }).start + const end = (node.body as Node & { start: number, end: number }).end + + if (!functionName || ['createJiti', 'captureStackTrace', '___captureStackTrace', '_interopRequireDefault'].includes(functionName) || !start || !end) { return } + + s.prependLeft(start + 1, generateInitCode(functionName) + 'try {') + s.appendRight(end - 1, `} finally { ${generateFinallyCode(functionName)} }`) + } + }, + }) + code = s.toString() + if (!code.includes(leading)) { + code = [leading, code, trailing].join('\n') + } + return code + } catch (e) { + // eslint-disable-next-line no-console + console.log(e, code, id) + } + }, + }, + } satisfies Plugin +} diff --git a/docs/1.getting-started/11.testing.md b/docs/1.getting-started/11.testing.md index ac0bddb89916..ae022e59b7bd 100644 --- a/docs/1.getting-started/11.testing.md +++ b/docs/1.getting-started/11.testing.md @@ -174,6 +174,7 @@ Under the hood, `mountSuspended` wraps `mount` from `@vue/test-utils`, so you ca Например: ```ts twoslash +// @noErrors import { it, expect } from 'vitest' import type { Component } from 'vue' declare module '#components' { @@ -194,6 +195,7 @@ it('can mount some component', async () => { ``` ```ts twoslash +// @noErrors import { it, expect } from 'vitest' // ---cut--- // tests/components/SomeComponents.nuxt.spec.ts @@ -225,6 +227,7 @@ This should be used together with utilities from Testing Library, e.g. `screen` Примеры: ```ts twoslash +// @noErrors import { it, expect } from 'vitest' import type { Component } from 'vue' declare module '#components' { @@ -243,6 +246,7 @@ it('can render some component', async () => { ``` ```ts twoslash +// @noErrors import { it, expect } from 'vitest' // ---cut--- // tests/App.nuxt.spec.ts diff --git a/docs/1.getting-started/6.data-fetching.md b/docs/1.getting-started/6.data-fetching.md index 826560835778..536044eb58c3 100644 --- a/docs/1.getting-started/6.data-fetching.md +++ b/docs/1.getting-started/6.data-fetching.md @@ -203,6 +203,19 @@ const { data: discounts, status } = await useAsyncData('cart-discount', async () ``` +::note +`useAsyncData` is for fetching and caching data, not triggering side effects like calling Pinia actions, as this can cause unintended behavior such as repeated executions with nullish values. If you need to trigger side effects, use the [`callOnce`](/docs/api/utils/call-once) utility to do so. + +```vue + +``` +:: + ::read-more{to="/docs/api/composables/use-async-data"} Узнайте больше о `useAsyncData`. :: diff --git a/docs/2.guide/1.concepts/10.nuxt-lifecycle.md b/docs/2.guide/1.concepts/10.nuxt-lifecycle.md new file mode 100644 index 000000000000..743a9d9970ab --- /dev/null +++ b/docs/2.guide/1.concepts/10.nuxt-lifecycle.md @@ -0,0 +1,141 @@ +--- +title: 'Nuxt Lifecycle' +description: "Understanding the lifecycle of Nuxt applications can help you gain deeper insights into how the framework operates, especially for both server-side and client-side rendering." +--- + +The goal of this chapter is to provide a high-level overview of the different parts of the framework, their execution order, and how they work together. + +## Server + +On the server, the following steps are executed for every initial request to your application: + +### Step 1: Setup Nitro Server and Nitro Plugins (Once) + +Nuxt is powered by [Nitro](https://nitro.build/), a modern server engine. + +When Nitro starts, it initializes and executes the plugins under the `/server/plugins` directory. These plugins can: +- Capture and handle application-wide errors. +- Register hooks that execute when Nitro shuts down. +- Register hooks for request lifecycle events, such as modifying responses. + +::callout{icon="i-ph-lightbulb"} +Nitro plugins are executed only once when the server starts. In a serverless environment, the server boots on each incoming request, and so do the Nitro plugins. However, they are not awaited. +:: + +:read-more{to="/docs/guide/directory-structure/server#server-plugins"} + +### Step 2: Nitro Server Middleware + +After initializing the Nitro server, middleware under `server/middleware/` is executed for every request. Middleware can be used for tasks such as authentication, logging, or request transformation. + +::warning +Returning a value from middleware will terminate the request and send the returned value as the response. This behavior should generally be avoided to ensure proper request handling! +:: + +:read-more{to="/docs/guide/directory-structure/server#server-middleware"} + +### Step 3: Initialize Nuxt and Execute Nuxt App Plugins + +The Vue and Nuxt instances are created first. Afterward, Nuxt executes its server plugins. This includes: +- Built-in plugins, such as Vue Router and `unhead`. +- Custom plugins located in the `plugins/` directory, including those without a suffix (e.g., `myPlugin.ts`) and those with the `.server` suffix (e.g., `myServerPlugin.server.ts`). + +Plugins execute in a specific order and may have dependencies on one another. For more details, including execution order and parallelism, refer to the [Plugins documentation](/docs/guide/directory-structure/plugins). + +::callout{icon="i-ph-lightbulb"} +After this step, Nuxt calls the [`app:created`](/docs/api/advanced/hooks#app-hooks-runtime) hook, which can be used to execute additional logic. +:: + +:read-more{to="/docs/guide/directory-structure/plugins"} + +### Step 4: Route Validation + +After initializing plugins and before executing middleware, Nuxt calls the `validate` method if it is defined in the `definePageMeta` function. The `validate` method, which can be synchronous or asynchronous, is often used to validate dynamic route parameters. + +- The `validate` function should return `true` if the parameters are valid. +- If validation fails, it should return `false` or an object containing a `statusCode` and/or `statusMessage` to terminate the request. + +For more information, see the [Route Validation documentation](/docs/getting-started/routing#route-validation). + +:read-more{to="/docs/getting-started/routing#route-validation"} + +### Step 5: Execute Nuxt App Middleware + +Middleware allows you to run code before navigating to a particular route. It is often used for tasks such as authentication, redirection, or logging. + +In Nuxt, there are three types of middleware: +- **Global route middleware** +- **Named route middleware** +- **Anonymous (or inline) route middleware** + +Nuxt automatically executes global middleware for first time enter to the application and every time before route navigation. Named and anonymous middleware are executed only on the routes specified in the middleware property of the page(route) meta defined in the corresponding page components. + +For details about each type and examples, see the [Middleware documentation](/docs/guide/directory-structure/middleware). + +Any redirection on the server will result in a `Location:` header being sent to the browser; the browser then makes a fresh request to this new location. All application state will be reset when this happens, unless persisted in a cookie. + +:read-more{to="/docs/guide/directory-structure/middleware"} + +### Step 6: Setup Page and Components + +Nuxt initializes the page and its components during this step and fetches any required data with `useFetch` and `useAsyncData`. Since there are no dynamic updates and no DOM operations occur on the server, Vue lifecycle hooks such as `onBeforeMount`, `onMounted`, and subsequent hooks are **NOT** executed during SSR. + +::important +You should avoid code that produces side effects that need cleanup in root scope of ` + ``` diff --git a/docs/2.guide/2.directory-structure/2.env.md b/docs/2.guide/2.directory-structure/2.env.md index 85fe922adc42..4fb33bc2d322 100644 --- a/docs/2.guide/2.directory-structure/2.env.md +++ b/docs/2.guide/2.directory-structure/2.env.md @@ -28,7 +28,7 @@ MY_ENV_VARIABLE=hello Если вы хотите использовать разные файлы - например, `.env.local` или `.env.production` - вы можете передавать флаг `--dotenv` при использовании `nuxi`. ```bash [Terminal] -npx nuxi dev --dotenv .env.local +npx nuxi dev -- --dotenv .env.local ``` При обновлении `.env` во время разработки, экземпляр Nuxt автоматически перезапускается, чтобы применить новые значения к `process.env`. diff --git a/docs/2.guide/3.going-further/9.debugging.md b/docs/2.guide/3.going-further/9.debugging.md index fdc341d3e9be..4b285262f188 100644 --- a/docs/2.guide/3.going-further/9.debugging.md +++ b/docs/2.guide/3.going-further/9.debugging.md @@ -60,7 +60,7 @@ nuxi dev --inspect "request": "launch", "name": "server: nuxt", "outputCapture": "std", - "program": "${workspaceFolder}/node_modules/nuxi/bin/nuxi.mjs", + "program": "${workspaceFolder}/node_modules/nuxt/bin/nuxt.mjs", "args": [ "dev" ], @@ -98,7 +98,7 @@ nuxi dev --inspect - + diff --git a/docs/2.guide/4.recipes/3.custom-usefetch.md b/docs/2.guide/4.recipes/3.custom-usefetch.md index f15212bced9d..39d1ef5ef88d 100644 --- a/docs/2.guide/4.recipes/3.custom-usefetch.md +++ b/docs/2.guide/4.recipes/3.custom-usefetch.md @@ -116,6 +116,8 @@ export function useAPI( Этот пример демонстрирует использование пользовательского `useFetch`, но та же структура идентична и для пользовательского `useAsyncData`. :: +:link-example{to="/docs/examples/advanced/use-custom-fetch-composable"} + ::callout{icon="i-simple-icons-youtube" color="red" to="https://www.youtube.com/watch?v=jXH8Tr-exhI"} Посмотрите видео о пользовательских `$fetch` и Паттерне Репозитория в Nuxt. :: diff --git a/docs/3.api/1.components/1.client-only.md b/docs/3.api/1.components/1.client-only.md index a36c14184e8f..151f6c792c2d 100644 --- a/docs/3.api/1.components/1.client-only.md +++ b/docs/3.api/1.components/1.client-only.md @@ -51,3 +51,26 @@ links: ``` + +## Examples + +### Accessing HTML Elements + +Components inside `` are rendered only after being mounted. To access the rendered elements in the DOM, you can watch a template ref: + +```vue [pages/example.vue] + + + +``` diff --git a/docs/3.api/1.components/2.nuxt-page.md b/docs/3.api/1.components/2.nuxt-page.md index 4680c07dbb1f..6356bdef213a 100644 --- a/docs/3.api/1.components/2.nuxt-page.md +++ b/docs/3.api/1.components/2.nuxt-page.md @@ -11,26 +11,45 @@ links: `` - это встроенный компонент, который идет вместе с Nuxt. Он позволяет отображать страницы верхнего уровня или вложенные страницы, расположенные в директории [`pages/`](/docs/guide/directory-structure/pages). ::note -`` - это обертка для компонента [``](https://router.vuejs.org/api/interfaces/RouterViewProps.html#interface-routerviewprops) из Vue Router. :br -Он принимает те же входные параметры: `name` и `route`. +`` is a wrapper around [``](https://router.vuejs.org/api/interfaces/RouterViewProps.html#interface-routerviewprops) from Vue Router. It should be used instead of `` because the former takes additional care of internal states. Otherwise, `useRoute()` may return incorrect paths. :: +`` includes the following components: + +```vue + +``` + +By default, Nuxt does not enable `` and ``. You can enable them in the nuxt.config file or by setting the `transition` and `keepalive` properties on ``. If you want to define a specific page, you can set it in `definePageMeta` in the page component. + ::warning -`` следует использовать вместо ``, поскольку требуется дополнительная обработка внутренних состояний. В противном случае `useRoute()` может возвращать неверные пути. +If you enable `` in your page component, ensure that the page has a single root element. :: ## Входные параметры -- `name`: указывает `RouterView` отобразить компонент с соответствующим именем в опции components записи соответствующего маршрута. +- `name`: указывает `` отобразить компонент с соответствующим именем в опции components записи соответствующего маршрута. - тип: `string` - `route`: местоположение маршрута, в котором есть все его компоненты. - тип: `RouteLocationNormalized` - `pageKey`: контролирует, когда будет произведен повторный рендер компонента `NuxtPage`. - тип: `string` или `function` - `transition`: определяет глобальные переходы для всех страниц, отрендеренных с помощью `NuxtPage`. - - тип: `boolean` или `TransitionProps` + - тип: `boolean` или [`TransitionProps`](https://vuejs.org/api/built-in-components#transition) - `keepalive`: контролирует сохранение состояния страниц, отрендеренных с помощью `NuxtPage`. - - тип: `boolean` или `KeepAliveProps` + - тип: `boolean` или [`KeepAliveProps`](https://vuejs.org/api/built-in-components#keepalive) ::tip Nuxt автоматически определяет `name` и `route`, сканируя и отображая все файлы компонентов Vue, найденные в директории `/pages`. @@ -38,7 +57,7 @@ Nuxt автоматически определяет `name` и `route`, скан ## Пример -Например, при передаче ключа `static`, компонент `NuxtPage` рендерится только один раз при монтировании. +For example, if you pass a key that never changes, the `` component will be rendered only once - when it is first mounted. ```vue [app.vue] ``` -## Атрибуты `target` и `rel` +## Атрибуты `rel` и `noRel` -Атрибут `rel` с `noopener noreferrer` применяется по умолчанию к абсолютным ссылкам и ссылкам, которые открываются в новых вкладках. +A `rel` attribute of `noopener noreferrer` is applied by default to links with a `target` attribute or to absolute links (e.g., links starting with `http://`, `https://`, or `//`). - `noopener` устраняет [ошибку безопасности](https://mathiasbynens.github.io/rel-noopener/) в старых браузерах. - `noreferrer` улучшает конфиденциальность для ваших пользователей, не отправляя заголовок `Referer` на сайт, на который дана ссылка. Эти значения по умолчанию не оказывают негативного влияния на SEO и считаются [лучшей практикой](https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener). -Когда вам нужно перезаписать это поведение, вы можете использовать входные параметры `rel` и `noRel`. +Когда вам нужно перезаписать это поведение, вы можете использовать входные параметры `rel` или `noRel`. ```vue [app.vue] +``` + +A `noRel` prop can be used to prevent the default `rel` attribute from being added to the absolute links. + +```vue [app.vue] + +``` + +::note +`noRel` and `rel` cannot be used together. `rel` will be ignored. +:: + +## Prefetch Links + +Nuxt automatically includes smart prefetching. That means it detects when a link is visible (by default), either in the viewport or when scrolling and prefetches the JavaScript for those pages so that they are ready when the user clicks the link. Nuxt only loads the resources when the browser isn't busy and skips prefetching if your connection is offline or if you only have 2g connection. + +```vue [pages/index.vue] +About page not pre-fetched +About page not pre-fetched +``` + +### Custom Prefetch Triggers + +We now support custom prefetch triggers for `` after `v3.13.0`. You can use the `prefetchOn` prop to control when to prefetch links. - - Contact page opens in another tab +```vue + +``` + +- `visibility`: Prefetches when the link becomes visible in the viewport. Monitors the element's intersection with the viewport using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). Prefetching is triggered when the element is scrolled into view. +- `interaction`: Prefetches when the link is hovered or focused. This approach listens for `pointerenter` and `focus` events, proactively prefetching resources when the user indicates intent to interact. + +You can also use an object to configure `prefetchOn`: + +```vue + ``` +That you probably don't want both enabled! + +```vue + +``` + +This configuration will observe when the element enters the viewport and also listen for `pointerenter` and `focus` events. This may result in unnecessary resource usage or redundant prefetching, as both triggers can prefetch the same resource under different conditions. + +### Enable Cross-origin Prefetch + +To enable cross-origin prefetching, you can set the `crossOriginPrefetch` option in your `nuxt.config`. This will enable cross-origin prefetching using the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API). + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + crossOriginPrefetch: true, + }, +}) +``` + +### Disable prefetch globally + +It's also possible to enable/disable prefetching all links globally for your app. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + defaults: { + nuxtLink: { + prefetch: false, + }, + }, + }, +}) +``` + ## Входные параметры ### RouterLink Если не используется `external`, `` поддерживает все [входные параметры `RouterLink`](https://router.vuejs.org/api/interfaces/RouterLinkProps.html) от Vue Router -- `to`: Любой URL-адрес или [объект расположения маршрута](https://router.vuejs.org/api/#RouteLocation) из Vue Router -- `custom`: `` должен обернуть свое содержимое в элемент ``. Это позволяет полностью контролировать отображение ссылки и работу навигации при нажатии на нее. Работает так же, как и [входной параметр `custom` у Vue Router](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom) -- `exactActiveClass`: Класс, который будет применяться к точным активным ссылкам. Работает так же, как и [входной параметр `exact-active-class` у Vue Router](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-exactActiveClass) для внутренних ссылок. По умолчанию используется значение по умолчанию от Vue Router (`"router-link-exact-active"`) -- `replace`: Работает так же, как и [входной параметр `replace` у Vue Router](https://router.vuejs.org/api/interfaces/RouteLocationOptions.html#Properties-replace) для внутренних ссылок -- `ariaCurrentValue`: Значение атрибута `aria-current`, которое будет применяться к конкретным активным ссылкам. Работает так же, как и [входной параметр `aria-current-value` у Vue Router](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-ariaCurrentValue) для внутренних ссылок -- `activeClass`: Класс, применяемый к активным ссылкам. Работает так же, как и [входной параметр `active-class` у Vue Router](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-activeClass) для внутренних ссылок. По умолчанию используется значение по умолчанию от Vue Router (`"router-link-active"`) +- `to`: Any URL or a [route location object](https://router.vuejs.org/api/#RouteLocation) from Vue Router +- `custom`: Whether `` should wrap its content in an `` element. It allows taking full control of how a link is rendered and how navigation works when it is clicked. Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom) +- `exactActiveClass`: A class to apply on exact active links. Works the same as [Vue Router's `exactActiveClass` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-exactActiveClass) on internal links. Defaults to Vue Router's default (`"router-link-exact-active"`) +- `activeClass`: A class to apply on active links. Works the same as [Vue Router's `activeClass` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-activeClass) on internal links. Defaults to Vue Router's default (`"router-link-active"`) +- `replace`: Works the same as [Vue Router's `replace` prop](https://router.vuejs.org/api/interfaces/RouteLocationOptions.html#Properties-replace) on internal links +- `ariaCurrentValue`: An `aria-current` attribute value to apply on exact active links. Works the same as [Vue Router's `ariaCurrentValue` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-ariaCurrentValue) on internal links ### NuxtLink - `href`: Алиас для `to`. При использовании `to`, `href` будет проигнорирован -- `noRel`: Если установлено в `true`, атрибут `rel` не будет добавлен к ссылке +- `noRel`: Если установлено в `true`, атрибут `rel` не будет добавлен к внешней ссылке - `external`: Заставляет ссылку рендериться как тег `a` вместо `RouterLink` у Vue Router. - `prefetch`: Когда включено, будет выполняться предварительная загрузка middleware, layouts и payloads (при использовании [payloadExtraction](/docs/api/nuxt-config#crossoriginprefetch)) ссылок в области просмотра. Используется в экспериментальной конфигурации [crossOriginPrefetch](/docs/api/nuxt-config#crossoriginprefetch). - `prefetchOn`: Allows custom control of when to prefetch links. Possible options are `interaction` and `visibility` (default). You can also pass an object for full control, for example: `{ interaction: true, visibility: true }`. This prop is only used when `prefetch` is enabled (default) and `noPrefetch` is not set. @@ -158,7 +269,9 @@ export default defineNuxtConfig({ activeClass: 'router-link-active', exactActiveClass: 'router-link-exact-active', prefetchedClass: undefined, // может быть любым допустимым строковым именем класса - trailingSlash: undefined // может быть 'append' или 'remove' + trailingSlash: undefined, // может быть 'append' или 'remove' + prefetch: true, + prefetchOn: { visibility: true } } } } diff --git a/docs/3.api/2.composables/use-async-data.md b/docs/3.api/2.composables/use-async-data.md index 59c8f4c1f750..3019144f3fc2 100644 --- a/docs/3.api/2.composables/use-async-data.md +++ b/docs/3.api/2.composables/use-async-data.md @@ -63,16 +63,25 @@ const { data: posts } = await useAsyncData( - `key`: уникальный ключ, который гарантирует, что получение данных может быть правильно дедуплицировано между запросами. Если вы не предоставляете ключ, то ключ, уникальный для имени файла и номера строки экземпляра `useAsyncData`, будет сгенерирован для вас. - `handler`: асинхронная функция, которая должна возвращать истинное значение (например, она не должна быть `undefined` или `null`), иначе запрос может быть дублирован на клиенте. +::warning +The `handler` function should be **side-effect free** to ensure predictable behavior during SSR and CSR hydration. If you need to trigger side effects, use the [`callOnce`](/docs/api/utils/call-once) utility to do so. +:: - `options`: - `server`: параметр, определяющий, следует ли получать данные на сервере (по умолчанию `true`) - `lazy`: параметр, определяющий, следует ли разрешать асинхронную функцию после загрузки маршрута, вместо блокировки навигации на клиенте. (по умолчанию `false`) - `immediate`: если установить значение `false`, это предотвратит немедленное выполнение запроса. (по умолчанию `true`) - `default`: функция-фабрика для установки значения по умолчанию для data перед тем, как асинхронная функция будет разрешена. Это полезно при использовании параметров `lazy: true` или `immediate: false`. - `transform`: функция, которая может быть использована для изменения результата функции-обработчика после ее разрешения. - - `getCachedData`: функция, которая возвращает кэшированные данные. Возвращаемое значение _null_ или _undefined_ будет вызывать выборку данных. По умолчанию это: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, которая кэширует данные, только если включено `payloadExtraction`. - - `pick`: выбрать из результата функции `handler` только указанные ключи в этом массиве - - `watch`: следить за реактивными источниками для автоматического обновления - - `deep`: возвращать данные в виде глубокого ref-объекта. Можно установить значение `false`, чтобы возвращать данные в виде объекта с неглубокой реактивностью, что может повысить производительность, если ваши данные не нуждаются в этом. + - `getCachedData`: Provide a function which returns cached data. A `null` or `undefined` return value will trigger a fetch. By default, this is: + ```ts + const getDefaultCachedData = (key) => nuxtApp.isHydrating + ? nuxtApp.payload.data[key] + : nuxtApp.static.data[key] + ``` + Which only caches data when `experimental.payloadExtraction` of `nuxt.config` is enabled. + - `pick`: only pick specified keys in this array from the `handler` function result + - `watch`: watch reactive sources to auto-refresh + - `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive. - `dedupe`: избегайте получения одного и того же ключа более одного раза за раз (по умолчанию `cancel`). Возможные параметры: - `cancel` - отменяет существующие запросы при поступлении нового - `defer` - вообще не делает новых запросов, если есть отложенный запрос @@ -94,8 +103,14 @@ const { data: posts } = await useAsyncData( - `data`: результат работы переданной асинхронной функции. - `refresh`/`execute`: функция, которая может быть использована для обновления данных, возвращенных функцией `handler`. - `error`: объект ошибки, если получение данных не удалось. -- `status`: строка, указывающая на статус запроса данных (`"idle"`, `"pending"`, `"success"`, `"error"`). -- `clear`: функция, которая установит `data` в `undefined`, `error` в `null`, `pending` в `false`, `status` в `idle`, и отметит любые текущие ожидающие запросы как отмененные. +- `status`: строка, указывающая на статус запроса данных: + - `idle`: when the request has not started, such as: + - when `execute` has not yet been called and `{ immediate: false }` is set + - when rendering HTML on the server and `{ server: false }` is set + - `pending`: the request is in progress + - `success`: the request has completed successfully + - `error`: the request has failed +- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled. По умолчанию Nuxt ждет, пока `refresh` не будет завершен, прежде чем его можно будет выполнить снова. diff --git a/docs/3.api/2.composables/use-fetch.md b/docs/3.api/2.composables/use-fetch.md index b2720901cccd..de355158c071 100644 --- a/docs/3.api/2.composables/use-fetch.md +++ b/docs/3.api/2.composables/use-fetch.md @@ -102,20 +102,26 @@ const { data, status, error, refresh, clear } = await useFetch('/api/auth/login' Все параметры запроса могут быть `computed` или `ref`. Они будут отслеживаться, и новые запросы будут автоматически выполняться с новыми значениями, если они будут обновлены. :: -- `Options` (из [`useAsyncData`](/docs/api/composables/use-async-data)): +- `Options` (from [`useAsyncData`](/docs/api/composables/use-async-data)): - `key`: Уникальный ключ для обеспечения правильной дедупликации данных в запросах. Если ключ не указан, он будет сгенерирован автоматически на основе URL и параметров запроса. - `server`: Следует ли получать данные на сервере (по умолчанию `true`). - `lazy`: Разрешать ли async-функцию после загрузки маршрута, чтобы не блокировать навигацию на стороне клиента (по умолчанию `false`). - `immediate`: Если установить значение `false`, то запрос не будет выполняться немедленно. (по умолчанию `true`). - `default`: Фабричная функция для установки значения по умолчанию для `data` перед разрешением async-функции - полезно при использовании опции `lazy: true` или `immediate: false`. - `transform`: Функция, которая может быть использована для изменения результата функции `handler` после разрешения. - - `getCachedData`: Функция, которая возвращает кэшированные данные. Возвращаемое значение _null_ или _undefined_ будет перевыполнять запрос. По умолчанию это: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, которая кэширует данные, только если включено `payloadExtraction`. - - `pick`: Выбор из результата функции `handler` только указанныx в этом массиве ключей. - - `watch`: Следит за массивом реактивных источников и автоматически обновляет данные при их изменении. По умолчанию отслеживаются параметры запроса и URL. Вы можете полностью игнорировать реактивные источники, используя `watch: false`. Вместе с `immediate: false` это позволяет использовать `useFetch` полностью в ручном режиме. (Пример использования `watch` можно посмотреть [здесь](/docs/getting-started/data-fetching#watch)). - - `deep`: Возвращает данные в виде глубокого ref-объекта. Для повышения производительности по умолчанию используется значение `false` для возврата данных в виде shallow-ref объекта. - - `dedupe`: Позволяет избегать получения одного и того же ключа более одного раза за вызов (по умолчанию `cancel`). Возможные опции: - - `cancel` - Отменяет существующие запросы при выполнении нового. - - `defer` - не делает новых запросов вообще, если есть ожидающий запрос. + - `getCachedData`: Provide a function which returns cached data. A `null` or `undefined` return value will trigger a fetch. By default, this is: + ```ts + const getDefaultCachedData = (key) => nuxtApp.isHydrating + ? nuxtApp.payload.data[key] + : nuxtApp.static.data[key] + ``` + Which only caches data when `experimental.payloadExtraction` of `nuxt.config` is enabled. + - `pick`: only pick specified keys in this array from the `handler` function result + - `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.) + - `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive. + - `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options: + - `cancel` - cancels existing requests when a new one is made + - `defer` - does not make new requests at all if there is a pending request ::note Если вы предоставите функцию или ref в качестве параметра `url`, или если вы предоставите функции в качестве аргументов параметра `options`, то вызов `useFetch` не будет соответствовать другим вызовам `useFetch` в других местах вашей кодовой базы, даже если опции кажутся идентичными. Если вы хотите, чтобы совпадение было принудительным, вы можете указать свой собственный ключ в `options`. @@ -134,8 +140,14 @@ const { data, status, error, refresh, clear } = await useFetch('/api/auth/login' - `data`: результат работы переданной асинхронной функции. - `refresh`/`execute`: функция, которая может быть использована для обновления данных, возвращенных функцией `handler`. - `error`: объект ошибки, если запрос данных не удался. -- `status`: строка, указывающая на статус запроса данных (`"idle"`, `"pending"`, `"success"`, `"error"`). -- `clear`: функция, которая установит `data` в `undefined`, `error` в `null`, `pending` в `false`, `status` в `"idle"`, и пометит все текущие запросы как отмененные. +- `status`: a string indicating the status of the data request: + - `idle`: when the request has not started, such as: + - when `execute` has not yet been called and `{ immediate: false }` is set + - when rendering HTML on the server and `{ server: false }` is set + - `pending`: the request is in progress + - `success`: the request has completed successfully + - `error`: the request has failed +- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled. По умолчанию Nuxt ждет, пока `refresh` не будет завершен, прежде чем его можно будет выполнить снова. @@ -147,7 +159,7 @@ const { data, status, error, refresh, clear } = await useFetch('/api/auth/login' ```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-head-safe.md b/docs/3.api/2.composables/use-head-safe.md index b93734fcc617..e73c20009239 100644 --- a/docs/3.api/2.composables/use-head-safe.md +++ b/docs/3.api/2.composables/use-head-safe.md @@ -4,7 +4,7 @@ description: Рекомендуемый способ предоставлени links: - label: Исходники icon: i-simple-icons-github - to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useHeadSafe.ts + to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts size: xs --- diff --git a/docs/3.api/2.composables/use-head.md b/docs/3.api/2.composables/use-head.md index 6190b3334c5b..62cbebfaeb36 100644 --- a/docs/3.api/2.composables/use-head.md +++ b/docs/3.api/2.composables/use-head.md @@ -4,7 +4,7 @@ description: useHead настраивает свойства заголовка links: - label: Исходники icon: i-simple-icons-github - to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useHead.ts + to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts size: xs --- diff --git a/docs/3.api/2.composables/use-nuxt-data.md b/docs/3.api/2.composables/use-nuxt-data.md index b2ca3ac9f4f3..75e81ffeb77b 100644 --- a/docs/3.api/2.composables/use-nuxt-data.md +++ b/docs/3.api/2.composables/use-nuxt-data.md @@ -9,12 +9,28 @@ links: --- ::note -`useNuxtData` предоставляет доступ к текущему кэшированному значению [`useAsyncData`](/docs/api/composables/use-async-data), `useLazyAsyncData`, [`useFetch`](/docs/api/composables/use-fetch) и [`useLazyFetch`](/docs/api/composables/use-lazy-fetch) с явно указанным ключом. +`useNuxtData` предоставляет доступ к текущему кэшированному значению [`useAsyncData`](/docs/api/composables/use-async-data), [`useLazyAsyncData`](/docs/api/composables/use-lazy-async-data), [`useFetch`](/docs/api/composables/use-fetch) и [`useLazyFetch`](/docs/api/composables/use-lazy-fetch) с явно указанным ключом. :: ## Использование -В приведенном ниже примере показано, как вы можете использовать кэшированные данные в качестве placeholder при загрузке самых последних данных с сервера. +The `useNuxtData` composable is used to access the current cached value of data-fetching composables such as `useAsyncData`, `useLazyAsyncData`, `useFetch`, and `useLazyFetch`. By providing the key used during the data fetch, you can retrieve the cached data and use it as needed. + +This is particularly useful for optimizing performance by reusing already-fetched data or implementing features like Optimistic Updates or cascading data updates. + +To use `useNuxtData`, ensure that the data-fetching composable (`useFetch`, `useAsyncData`, etc.) has been called with an explicitly provided key. + +## Params + +- `key`: The unique key that identifies the cached data. This key should match the one used during the original data fetch. + +## Return Values + +- `data`: A reactive reference to the cached data associated with the provided key. If no cached data exists, the value will be `null`. This `Ref` automatically updates if the cached data changes, allowing seamless reactivity in your components. + +## Example + +The example below shows how you can use cached data as a placeholder while the most recent data is being fetched from the server. ```vue [pages/posts.vue] ``` diff --git a/docs/3.api/2.composables/use-seo-meta.md b/docs/3.api/2.composables/use-seo-meta.md index b9ada973b6a8..08bd4e16263b 100644 --- a/docs/3.api/2.composables/use-seo-meta.md +++ b/docs/3.api/2.composables/use-seo-meta.md @@ -4,7 +4,7 @@ description: Композабл useSeoMeta позволяет вам опред links: - label: Исходники icon: i-simple-icons-github - to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useSeoMeta.ts + to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts size: xs --- diff --git a/docs/3.api/2.composables/use-server-seo-meta.md b/docs/3.api/2.composables/use-server-seo-meta.md index 2a0c87b6621b..c4fae18fb052 100644 --- a/docs/3.api/2.composables/use-server-seo-meta.md +++ b/docs/3.api/2.composables/use-server-seo-meta.md @@ -4,7 +4,7 @@ description: Композабл useServerSeoMeta позволяет опреде links: - label: Исходники icon: i-simple-icons-github - to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useServerSeoMeta.ts + to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts size: xs --- diff --git a/docs/3.api/3.utils/add-route-middleware.md b/docs/3.api/3.utils/add-route-middleware.md index 4ffdfc5d9aef..3f06540913a5 100644 --- a/docs/3.api/3.utils/add-route-middleware.md +++ b/docs/3.api/3.utils/add-route-middleware.md @@ -15,7 +15,12 @@ Middleware маршрутов - это защитники навигации, х ## Тип ```ts -addRouteMiddleware (name: string | RouteMiddleware, middleware?: RouteMiddleware, options: AddRouteMiddlewareOptions = {}) +function addRouteMiddleware (name: string, middleware: RouteMiddleware, options?: AddRouteMiddlewareOptions): void +function addRouteMiddleware (middleware: RouteMiddleware): void + +interface AddRouteMiddlewareOptions { + global?: boolean +} ``` ## Параметры @@ -42,44 +47,42 @@ Middleware маршрута может быть либо строкой, либ ## Примеры -### Анонимные Middleware маршрута +### Named Route Middleware -Анонимные Middleware маршрута не имеют имени. Они принимают функцию в качестве первого аргумента, что делает второй аргумент middleware ненужным: +Named route middleware is defined by providing a string as the first argument and a function as the second: ```ts [plugins/my-plugin.ts] export default defineNuxtPlugin(() => { - addRouteMiddleware((to, from) => { - if (to.path === '/forbidden') { - return false - } + addRouteMiddleware('named-middleware', () => { + console.log('Middleware маршрута с именем, добавленный в плагин Nuxt') }) }) ``` -### Middleware маршрута с именем +When defined in a plugin, it overrides any existing middleware of the same name located in the `middleware/` directory. -Middleware маршрута с именем принимает строку в качестве первого аргумента и функцию в качестве второго. +### Глобальный Middleware маршрута -Когда он определен в плагине, он переопределяет любой существующий middleware с тем же именем, расположенный в директории `middleware/`: +Global route middleware can be defined in two ways: -```ts [plugins/my-plugin.ts] -export default defineNuxtPlugin(() => { - addRouteMiddleware('named-middleware', () => { - console.log('Middleware маршрута с именем, добавленный в плагин Nuxt') - }) -}) -``` +- Pass a function directly as the first argument without a name. It will automatically be treated as global middleware and applied on every route change. -### Глобальный Middleware маршрута + ```ts [plugins/my-plugin.ts] + export default defineNuxtPlugin(() => { + addRouteMiddleware((to, from) => { + console.log('anonymous global middleware that runs on every route change') + }) + }) + ``` Задайте необязательный третий аргумент `{ global: true }`, чтобы указать, является ли middleware маршрута глобальным: -```ts [plugins/my-plugin.ts] -export default defineNuxtPlugin(() => { - addRouteMiddleware('global-middleware', (to, from) => { - console.log('Глобальный middleware, который выполняется при каждом изменении маршрута') - }, - { global: true } - ) -}) -``` + ```ts [plugins/my-plugin.ts] + export default defineNuxtPlugin(() => { + addRouteMiddleware('global-middleware', (to, from) => { + console.log('global middleware that runs on every route change') + }, + { global: true } + ) + }) + ``` diff --git a/docs/3.api/3.utils/call-once.md b/docs/3.api/3.utils/call-once.md index 5ec2310cac3e..20a780f5f020 100644 --- a/docs/3.api/3.utils/call-once.md +++ b/docs/3.api/3.utils/call-once.md @@ -25,6 +25,8 @@ links: ## Использование +The default mode of `callOnce` is to run code only once. For example, if the code runs on the server it won't run again on the client. It also won't run again if you `callOnce` more than once on the client, for example by navigating back to this page. + ```vue [app.vue] ``` +It is also possible to run on every navigation while still avoiding the initial server/client double load. For this, it is possible to use the `navigation` mode: + +```vue [app.vue] + +``` + +::important +`navigation` mode is available since [Nuxt v3.15](/blog/v3-15). +:: + ::tip{to="/docs/getting-started/state-management#usage-with-pinia"} `callOnce` полезен в сочетании с [модулем Pinia](/modules/pinia) для вызова действий хранилища. :: @@ -53,9 +72,22 @@ await callOnce(async () => { ## Тип ```ts -callOnce(fn?: () => any | Promise): Promise -callOnce(key: string, fn?: () => any | Promise): Promise +callOnce (key?: string, fn?: (() => any | Promise), options?: CallOnceOptions): Promise +callOnce(fn?: (() => any | Promise), options?: CallOnceOptions): Promise + +type CallOnceOptions = { + /** + * Execution mode for the callOnce function + * @default 'render' + */ + mode?: 'navigation' | 'render' +} ``` +## Parameters + - `key`: Уникальный ключ, гарантирующий, что код выполняется один раз. Если вы не предоставите ключ, то будет сгенерирован ключ, уникальный для файла и номера строки экземпляра `callOnce`. - `fn`: Функция, которую нужно выполнить один раз. Эта функция также может возвращать `Promise` и значение. +- `options`: Setup the mode, either to re-execute on navigation (`navigation`) or just once for the lifetime of the app (`render`). Defaults to `render`. + - `render`: Executes once during initial render (either SSR or CSR) - Default mode + - `navigation`: Executes once during initial render and once per subsequent client-side navigation diff --git a/docs/3.api/3.utils/navigate-to.md b/docs/3.api/3.utils/navigate-to.md index dc141bf2b1a5..e37d722e7eab 100644 --- a/docs/3.api/3.utils/navigate-to.md +++ b/docs/3.api/3.utils/navigate-to.md @@ -8,16 +8,16 @@ links: size: xs --- -::note -`navigateTo` доступен как на сервере, так и на клиенте (но не в маршрутах Nitro). -:: - ## Использование Функция `navigateTo` доступна как на сервере, так и на клиенте. Ее можно использовать внутри [Nuxt-контекста](/docs/guide/going-further/nuxt-app#the-nuxt-context) или напрямую для осуществления постраничной навигации. -::tip -Чтобы отправить редирект с эндпоинта сервера, используйте вместо этого [`sendRedirect`](https://h3.unjs.io/utils/response#sendredirectevent-location-code). +::warning +Make sure to always use `await` or `return` on result of `navigateTo` when calling it. +:: + +::note +`navigateTo` cannot be used within Nitro routes. To perform a server-side redirect in Nitro routes, use [`sendRedirect`](https://h3.unjs.io/utils/response#sendredirectevent-location-code) instead. :: ### Внутри компонента Vue @@ -52,9 +52,25 @@ export default defineNuxtRouteMiddleware((to, from) => { }) ``` +When using `navigateTo` within route middleware, you must **return its result** to ensure the middleware execution flow works correctly. + +For example, the following implementation **will not work as expected**: + +```ts +export default defineNuxtRouteMiddleware((to, from) => { + if (to.path !== '/search') { + // ❌ This will not work as expected + navigateTo('/search', { redirectCode: 301 }) + return + } +}) +``` + +In this case, `navigateTo` will be executed but not returned, which may lead to unexpected behavior. + :read-more{to="/docs/guide/directory-structure/middleware"} -### Внешний URL +### Navigating to an External URL Параметр `external` в `navigateTo` влияет на то, как будет осуществляться переход по URL: @@ -81,7 +97,7 @@ await navigateTo('https://nuxt.com', { ``` -### Использование open() +### Opening a Page in a New Tab ```vue ` - , 'hello.server.vue') + `, + 'hello.server.vue') expect(normalizeLineEndings(result)).toMatchInlineSnapshot(` "