diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 68d3ffb9d..411c41292 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts +FROM node:lts@sha256:48db4f6ea21d134be744207225753a1730c4bc1b4cdf836d44511c36bf0e34d7 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/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 7de0a3890..780ffed43 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -36,7 +36,7 @@ body: validations: required: true - type: textarea - id: additonal + id: additional attributes: label: Additional context description: If applicable, add any other context about the problem here diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 000000000..1ab482ad6 --- /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/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index b71b0e9ba..bda394c56 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 78474f90e..5a3063f19 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,6 +17,10 @@ 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 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43840589d..d9ca20647 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: pnpm build - name: Cache dist - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: retention-days: 3 name: dist @@ -70,8 +70,6 @@ jobs: actions: read contents: read security-events: write - needs: - - build steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -81,25 +79,26 @@ jobs: node-version: 20 cache: "pnpm" - - name: Install dependencies - run: pnpm install - - name: Initialize CodeQL - uses: github/codeql-action/init@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 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@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: - category: "/language:javascript" + category: "/language:javascript-typescript" typecheck: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/check-links.yml b/.github/workflows/docs-check-links.yml similarity index 90% rename from .github/workflows/check-links.yml rename to .github/workflows/docs-check-links.yml index f07aa5b89..cc4260e8b 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/docs-check-links.yml @@ -1,4 +1,4 @@ -name: Check links with Lychee +name: docs on: pull_request: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Lychee link checker - uses: lycheeverse/lychee-action@25a231001d1723960a301b7d4c82884dc7ef857d # for v1.8.0 + uses: lycheeverse/lychee-action@c38ba4f281730ee0d64e6963f49b708e01567b86 # for v1.8.0 with: # arguments with file types to check args: >- diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index c4ad6fb3d..206d15e8d 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -1,11 +1,11 @@ -name: Deploy docs +name: docs on: push: paths: - "docs/**" branches: - - main + - 3.x # Remove default permissions of GITHUB_TOKEN for security # https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d63689f9c..bd12e4f3e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -name: Docs +name: docs on: push: diff --git a/.github/workflows/label-issue.yml b/.github/workflows/label-issue.yml new file mode 100644 index 000000000..ebc2e3921 --- /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 c173cee01..e0469e757 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -1,4 +1,4 @@ -name: Label PR +name: chore on: pull_request_target: @@ -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 new file mode 100644 index 000000000..7774d8909 --- /dev/null +++ b/.github/workflows/lint-sherif.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + paths: + - "**/package.json" + branches: + - main + - 3.x + pull_request: + paths: + - "**/package.json" + branches: + - main + - 3.x + - "!v[0-9]*" + +permissions: + contents: read + +jobs: + lint-monorepo: + runs-on: ubuntu-latest + + 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 + - name: Lint monorepo + run: pnpm sherif -r multiple-dependency-versions diff --git a/.github/workflows/introspect.yml b/.github/workflows/lint-workflows.yml similarity index 71% rename from .github/workflows/introspect.yml rename to .github/workflows/lint-workflows.yml index 0820e5407..219ef1967 100644 --- a/.github/workflows/introspect.yml +++ b/.github/workflows/lint-workflows.yml @@ -26,6 +26,6 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # 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.1@sha256:435ecdb63b1169e80ca3e136290072548c07fc4d76a044cf5541021712f8f344 + with: + args: -color diff --git a/.github/workflows/notify-nuxt-bridge.yml b/.github/workflows/notify-nuxt-bridge.yml index fa97b12b9..b1f67c050 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 7850e0f26..9f38c8c9d 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -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,7 +47,7 @@ jobs: exit 1 fi - echo "head_sha=$head_sha" >> $GITHUB_OUTPUT + echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ steps.pr.outputs.head_sha }} diff --git a/.github/workflows/reproduire-sur-stackblitz.yml b/.github/workflows/reproduire-sur-stackblitz.yml deleted file mode 100644 index e5dd5a9fa..000000000 --- a/.github/workflows/reproduire-sur-stackblitz.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: reproduire-sur-stackblitz -on: - issues: - types: - opened - -permissions: - issues: write - -jobs: - reproduire-sur-stackblitz: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: huang-julien/reproduire-sur-stackblitz@v1.0.0 - with: - reproduction-heading: '### Reproduction' \ No newline at end of file diff --git a/.github/workflows/reproduire.yml b/.github/workflows/reproduire.yml index e38add83d..4f02b2ce3 100644 --- a/.github/workflows/reproduire.yml +++ b/.github/workflows/reproduire.yml @@ -1,4 +1,4 @@ -name: Reproduire +name: chore on: issues: types: [labeled] diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 66d795115..9a776f2e2 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -2,7 +2,7 @@ # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. -name: Scorecard supply-chain security +name: ossf on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection @@ -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@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.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@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 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 927a2b41a..e569c4acc 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -1,4 +1,4 @@ -name: Semantic pull request +name: chore on: pull_request_target: @@ -7,12 +7,12 @@ on: - edited - synchronize -permissions: - contents: read +permissions: {} jobs: - main: + 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') diff --git a/.github/workflows/stackblitz-link.yml b/.github/workflows/stackblitz-link.yml new file mode 100644 index 000000000..997a48e84 --- /dev/null +++ b/.github/workflows/stackblitz-link.yml @@ -0,0 +1,17 @@ +name: chore +on: + issues: + types: + opened + +permissions: + issues: write + +jobs: + stackblitz: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: huang-julien/reproduire-sur-stackblitz@9ceccbfbb0f2f9a9a8db2d1f0dd909cf5cfe67aa # v1.0.2 + with: + reproduction-heading: '### Reproduction' diff --git a/.github/workflows/reproduire-close.yml b/.github/workflows/stale.yml similarity index 96% rename from .github/workflows/reproduire-close.yml rename to .github/workflows/stale.yml index cf72e9e0e..23dccb624 100644 --- a/.github/workflows/reproduire-close.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,4 @@ -name: Close incomplete issues +name: chore on: workflow_dispatch: schedule: diff --git a/docs/1.getting-started/4.styling.md b/docs/1.getting-started/4.styling.md index 11244f3fd..61ae9da78 100644 --- a/docs/1.getting-started/4.styling.md +++ b/docs/1.getting-started/4.styling.md @@ -427,8 +427,8 @@ Nuxt comes with postcss built-in. You can configure it in your `nuxt.config` fil export default defineNuxtConfig({ postcss: { plugins: { - 'postcss-nested': {} - "postcss-custom-media": {} + 'postcss-nested': {}, + 'postcss-custom-media': {} } } }) diff --git a/docs/1.getting-started/8.error-handling.md b/docs/1.getting-started/8.error-handling.md index 9f5624659..e72484dca 100644 --- a/docs/1.getting-started/8.error-handling.md +++ b/docs/1.getting-started/8.error-handling.md @@ -55,7 +55,7 @@ This includes: You cannot currently define a server-side handler for these errors, but can render an error page, see the [Render an Error Page](#error-page) section. -## Errors with JS chunks +## Errors with JS Chunks You might encounter chunk loading errors due to a network connectivity failure or a new deployment (which invalidates your old, hashed JS chunk URLs). Nuxt provides built-in support for handling chunk loading errors by performing a hard reload when a chunk fails to load during route navigation. diff --git a/docs/2.guide/1.concepts/4.server-engine.md b/docs/2.guide/1.concepts/4.server-engine.md index 8d6b111ca..f72129947 100644 --- a/docs/2.guide/1.concepts/4.server-engine.md +++ b/docs/2.guide/1.concepts/4.server-engine.md @@ -7,7 +7,7 @@ While building Nuxt 3, we created a new server engine: [Nitro](https://nitro.unj It is shipped with many features: -- Cross-platform support for Node.js, Browsers, service-workers and more. +- Cross-platform support for Node.js, browsers, service workers and more. - Serverless support out-of-the-box. - API routes support. - Automatic code-splitting and async-loaded chunks. 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 a027077a3..3e97d0e88 100644 --- a/docs/2.guide/2.directory-structure/3.app-config.md +++ b/docs/2.guide/2.directory-structure/3.app-config.md @@ -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/3.going-further/1.experimental-features.md b/docs/2.guide/3.going-further/1.experimental-features.md index fd9c065ba..136cadf80 100644 --- a/docs/2.guide/3.going-further/1.experimental-features.md +++ b/docs/2.guide/3.going-further/1.experimental-features.md @@ -359,3 +359,34 @@ export default defineNuxtConfig({ ::read-more{icon="i-simple-icons-mdnwebdocs" color="gray" to="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore" target="_blank"} Read more about the **CookieStore**. :: + +## buildCache + +Caches Nuxt build artifacts based on a hash of the configuration and source files. + +```ts twoslash [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + buildCache: true + } +}) +``` + +When enabled, changes to the following files will trigger a full rebuild: + +```bash [Directory structure] +.nuxtrc +.npmrc +package.json +package-lock.json +yarn.lock +pnpm-lock.yaml +tsconfig.json +bun.lockb +``` + +In addition, any changes to files within `srcDir` will trigger a rebuild of the Vue client/server bundle. Nitro will always be rebuilt (though work is in progress to allow Nitro to announce its cacheable artifacts and their hashes). + +::note +A maximum of 10 cache tarballs are kept. +:: diff --git a/docs/2.guide/4.recipes/1.custom-routing.md b/docs/2.guide/4.recipes/1.custom-routing.md index 7b1b24e32..f9b191768 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/3.api/1.components/10.nuxt-picture.md b/docs/3.api/1.components/10.nuxt-picture.md index 85bdbb97a..65f30ec04 100644 --- a/docs/3.api/1.components/10.nuxt-picture.md +++ b/docs/3.api/1.components/10.nuxt-picture.md @@ -4,7 +4,7 @@ description: "Nuxt provides a component to handle automatic image links: - label: Source icon: i-simple-icons-github - to: https://github.com/nuxt/image/blob/main/src/runtime/components/nuxt-picture.ts + to: https://github.com/nuxt/image/blob/main/src/runtime/components/NuxtPicture.vue size: xs --- diff --git a/docs/3.api/1.components/9.nuxt-img.md b/docs/3.api/1.components/9.nuxt-img.md index 8585db81f..4b9644246 100644 --- a/docs/3.api/1.components/9.nuxt-img.md +++ b/docs/3.api/1.components/9.nuxt-img.md @@ -4,7 +4,7 @@ description: "Nuxt provides a component to handle automatic image opti links: - label: Source icon: i-simple-icons-github - to: https://github.com/nuxt/image/blob/main/src/runtime/components/nuxt-img.ts + to: https://github.com/nuxt/image/blob/main/src/runtime/components/NuxtImg.vue size: xs --- diff --git a/docs/3.api/2.composables/use-async-data.md b/docs/3.api/2.composables/use-async-data.md index a29f09d36..7619dd1eb 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 4ca72835f..5471f6b66 100644 --- a/docs/3.api/2.composables/use-fetch.md +++ b/docs/3.api/2.composables/use-fetch.md @@ -70,6 +70,10 @@ 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`. :: +::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-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"} Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way! :: diff --git a/docs/3.api/2.composables/use-route.md b/docs/3.api/2.composables/use-route.md index 853fecf83..cbaabb176 100644 --- a/docs/3.api/2.composables/use-route.md +++ b/docs/3.api/2.composables/use-route.md @@ -38,6 +38,7 @@ Apart from dynamic parameters and query parameters, `useRoute()` also provides t - `fullPath`: encoded URL associated with the current route that contains path, query and hash - `hash`: decoded hash section of the URL that starts with a # +- `query`: access route query parameters - `matched`: array of normalized matched routes with current route location - `meta`: custom data attached to the record - `name`: unique name for the route record diff --git a/docs/3.api/6.advanced/1.hooks.md b/docs/3.api/6.advanced/1.hooks.md index d2b039ae3..894f10472 100644 --- a/docs/3.api/6.advanced/1.hooks.md +++ b/docs/3.api/6.advanced/1.hooks.md @@ -34,7 +34,7 @@ Hook | Arguments | Environment | Description ## Nuxt Hooks (build time) -Check the [schema source code](https://github.com/nuxt/nuxt/blob/main/packages/schema/src/types/hooks.ts#L53) for all available hooks. +Check the [schema source code](https://github.com/nuxt/nuxt/blob/main/packages/schema/src/types/hooks.ts#L83) for all available hooks. Hook | Arguments | Description -------------------------|----------------------------|------------- diff --git a/docs/5.community/5.framework-contribution.md b/docs/5.community/5.framework-contribution.md index 2f70b77f9..a96de2e74 100644 --- a/docs/5.community/5.framework-contribution.md +++ b/docs/5.community/5.framework-contribution.md @@ -25,9 +25,9 @@ To contribute to Nuxt, you need to set up a local environment. ```bash [Terminal] corepack enable ``` -4. Run `pnpm install` to Install the dependencies with pnpm: +4. Run `pnpm install --frozen-lockfile` to Install the dependencies with pnpm: ```bash [Terminal] - pnpm install + pnpm install --frozen-lockfile ``` ::note If you are adding a dependency, please use `pnpm add`. :br diff --git a/docs/5.community/6.roadmap.md b/docs/5.community/6.roadmap.md index b0209259b..423057024 100644 --- a/docs/5.community/6.roadmap.md +++ b/docs/5.community/6.roadmap.md @@ -40,7 +40,7 @@ In addition to the Nuxt framework, there are modules that are vital for the ecos Module | Status | Nuxt Support | Repository | Description ------------------------------------|---------------------|--------------|------------|------------------- -[Scripts](https://scripts.nuxt.com) | Public Preview | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management. +[Scripts](https://scripts.nuxt.com) | Public Beta | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management. A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255) Auth | Planned | 3.x | `nuxt/auth` to be announced | Support is planned after session support. Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices. diff --git a/docs/5.community/7.changelog.md b/docs/5.community/7.changelog.md index 5daf444f3..bc1d34cb7 100644 --- a/docs/5.community/7.changelog.md +++ b/docs/5.community/7.changelog.md @@ -73,7 +73,7 @@ navigation.icon: i-ph-notification-duotone target: _blank ui.icon.base: text-black dark:text-white --- - Nuxt Scripts releases. (Public Preview) + Nuxt Scripts releases. :: ::card --- diff --git a/nuxt.config.ts b/nuxt.config.ts index 694788ebb..54f547026 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,16 +1,21 @@ // For pnpm typecheck:docs to generate correct types -import { addPluginTemplate } from 'nuxt/kit' +import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit' export default defineNuxtConfig({ typescript: { shim: process.env.DOCS_TYPECHECK === 'true' }, pages: process.env.DOCS_TYPECHECK === 'true', modules: [ function () { + if (!process.env.DOCS_TYPECHECK) { return } addPluginTemplate({ filename: 'plugins/my-plugin.mjs', getContents: () => 'export default defineNuxtPlugin({ name: \'my-plugin\' })', }) + addRouteMiddleware({ + name: 'auth', + path: '#build/auth.js', + }) }, ], }) diff --git a/package.json b/package.json index 14c63da84..364dfc86c 100644 --- a/package.json +++ b/package.json @@ -39,74 +39,74 @@ "@nuxt/ui-templates": "workspace:*", "@nuxt/vite-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*", - "@types/node": "20.16.1", - "c12": "2.0.0-beta.1", + "@types/node": "20.16.5", + "c12": "2.0.0-beta.2", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "jiti": "2.0.0-beta.3", "magic-string": "^0.30.11", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", "nuxt": "workspace:*", - "rollup": "^4.21.0", - "typescript": "5.5.4", + "postcss": "8.4.45", + "rollup": "4.21.2", + "send": ">=0.19.0", + "typescript": "5.6.2", + "ufo": "1.5.4", "unbuild": "3.0.0-rc.7", - "vite": "5.4.1", - "vue": "3.5.0-beta.1", - "@vue/compiler-core": "3.5.0-beta.1", - "@vue/compiler-dom": "3.5.0-beta.1", - "@vue/compiler-sfc": "3.5.0-beta.1", - "@vue/compiler-ssr": "3.5.0-beta.1", - "@vue/shared": "3.5.0-beta.1" + "vite": "5.4.4", + "vue": "3.5.4" }, "devDependencies": { - "@eslint/js": "9.9.0", - "@nuxt/eslint-config": "0.5.1", + "@eslint/js": "9.10.0", + "@nuxt/eslint-config": "0.5.7", "@nuxt/kit": "workspace:*", - "@nuxt/test-utils": "3.14.1", + "@nuxt/test-utils": "3.14.2", "@nuxt/webpack-builder": "workspace:*", "@testing-library/vue": "8.1.0", "@types/eslint__js": "8.42.3", - "@types/node": "20.16.1", + "@types/node": "20.16.5", "@types/semver": "7.5.8", - "@unhead/schema": "1.9.16", - "@vitejs/plugin-vue": "5.1.2", + "@unhead/schema": "1.11.2", + "@unhead/vue": "1.11.2", + "@vitejs/plugin-vue": "5.1.3", "@vitest/coverage-v8": "2.0.5", "@vue/test-utils": "2.4.6", "autoprefixer": "10.4.20", "case-police": "0.7.0", "changelogen": "0.5.5", "consola": "3.2.3", - "cssnano": "7.0.5", + "cssnano": "7.0.6", "destr": "2.0.3", "devalue": "5.0.0", - "eslint": "9.9.0", + "eslint": "9.10.0", "eslint-plugin-no-only-tests": "3.3.0", - "eslint-plugin-perfectionist": "3.2.0", - "eslint-typegen": "0.3.1", - "execa": "9.3.1", - "globby": "14.0.2", + "eslint-plugin-perfectionist": "3.5.0", + "eslint-typegen": "0.3.2", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", - "happy-dom": "14.12.3", + "happy-dom": "15.7.3", "jiti": "2.0.0-beta.3", "markdownlint-cli": "0.41.0", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", - "nuxi": "3.12.0", + "nuxi": "3.13.1", "nuxt": "workspace:*", "nuxt-content-twoslash": "0.1.1", "ofetch": "1.3.4", "pathe": "1.1.2", - "playwright-core": "1.46.1", + "playwright-core": "1.47.0", "rimraf": "6.0.1", "semver": "7.6.3", + "sherif": "1.0.0", "std-env": "3.7.0", - "typescript": "5.5.4", + "tinyexec": "0.3.0", + "tinyglobby": "0.2.6", + "typescript": "5.6.2", "ufo": "1.5.4", "vitest": "2.0.5", "vitest-environment-nuxt": "1.0.1", - "vue": "3.4.38", - "vue-router": "4.4.3", - "vue-tsc": "2.0.29" + "vue": "3.5.4", + "vue-router": "4.4.4", + "vue-tsc": "2.1.6" }, - "packageManager": "pnpm@9.7.1", + "packageManager": "pnpm@9.10.0", "engines": { "node": "^16.10.0 || >=18.0.0" }, diff --git a/packages/kit/package.json b/packages/kit/package.json index 71d7fc292..ec4c09dd1 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@nuxt/schema": "workspace:*", - "c12": "^2.0.0-beta.1", + "c12": "^2.0.0-beta.2", "consola": "^3.2.3", "defu": "^6.1.4", "destr": "^2.0.3", @@ -39,12 +39,12 @@ "klona": "^2.0.6", "mlly": "^1.7.1", "pathe": "^1.1.2", - "pkg-types": "^1.1.3", + "pkg-types": "^1.2.0", "scule": "^1.3.0", "semver": "^7.6.3", "ufo": "^1.5.4", "unctx": "^2.3.1", - "unimport": "^3.10.0", + "unimport": "^3.11.1", "untyped": "^1.4.2" }, "devDependencies": { @@ -52,9 +52,9 @@ "@types/semver": "7.5.8", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", "unbuild": "3.0.0-rc.7", - "vite": "5.4.1", + "vite": "5.4.4", "vitest": "2.0.5", - "webpack": "5.93.0" + "webpack": "5.94.0" }, "engines": { "node": "^14.18.0 || >=16.10.0" diff --git a/packages/kit/src/components.ts b/packages/kit/src/components.ts index 1257a6a68..669d6ce41 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/layout.ts b/packages/kit/src/layout.ts index 3116d421d..65fd18316 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/loader/config.ts b/packages/kit/src/loader/config.ts index 444bf8c46..80139fb01 100644 --- a/packages/kit/src/loader/config.ts +++ b/packages/kit/src/loader/config.ts @@ -1,3 +1,4 @@ +import { existsSync } from 'node:fs' import type { JSValue } from 'untyped' import { applyDefaults } from 'untyped' import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12' @@ -6,6 +7,7 @@ import type { NuxtConfig, NuxtOptions } from '@nuxt/schema' import { NuxtConfigSchema } from '@nuxt/schema' import { globby } from 'globby' import defu from 'defu' +import { join } from 'pathe' export interface LoadNuxtConfigOptions extends Omit, 'overrides'> { // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type @@ -47,6 +49,11 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise[] = [] const processedLayers = new Set() for (const layer of layers) { diff --git a/packages/kit/src/module/define.ts b/packages/kit/src/module/define.ts index 06435f692..33ac6d015 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/template.ts b/packages/kit/src/template.ts index 86da4d1a4..903308497 100644 --- a/packages/kit/src/template.ts +++ b/packages/kit/src/template.ts @@ -320,11 +320,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/nuxt/package.json b/packages/nuxt/package.json index e35f8a401..d5520946a 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -60,17 +60,18 @@ }, "dependencies": { "@nuxt/devalue": "^2.0.2", - "@nuxt/devtools": "^1.3.9", + "@nuxt/devtools": "^1.4.2", "@nuxt/kit": "workspace:*", "@nuxt/schema": "workspace:*", - "@nuxt/telemetry": "^2.5.4", + "@nuxt/telemetry": "^2.6.0", "@nuxt/vite-builder": "workspace:*", - "@unhead/dom": "^1.9.16", - "@unhead/ssr": "^1.9.16", - "@unhead/vue": "^1.9.16", - "@vue/shared": "^3.4.38", + "@unhead/dom": "^1.11.2", + "@unhead/shared": "^1.11.2", + "@unhead/ssr": "^1.11.2", + "@unhead/vue": "^1.11.2", + "@vue/shared": "^3.5.4", "acorn": "8.12.1", - "c12": "^2.0.0-beta.1", + "c12": "^2.0.0-beta.2", "chokidar": "^3.6.0", "compatx": "^0.1.8", "consola": "^3.2.3", @@ -86,48 +87,52 @@ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "hookable": "^5.5.3", "ignore": "^5.3.2", + "impound": "^0.1.0", "jiti": "^2.0.0-beta.3", "klona": "^2.0.6", "knitwork": "^1.1.0", "magic-string": "^0.30.11", "mlly": "^1.7.1", + "nanotar": "^0.1.1", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", - "nuxi": "^3.12.0", - "nypm": "^0.3.9", + "nuxi": "^3.13.1", + "nypm": "^0.3.11", "ofetch": "^1.3.4", "ohash": "^1.1.3", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", - "pkg-types": "^1.1.3", + "pkg-types": "^1.2.0", "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.6", "ufo": "^1.5.4", "ultrahtml": "^1.5.3", "uncrypto": "^0.1.3", "unctx": "^2.3.1", "unenv": "^1.10.0", - "unimport": "^3.10.0", - "unplugin": "^1.12.2", - "unplugin-vue-router": "^0.10.7", - "unstorage": "^1.10.2", + "unhead": "^1.11.2", + "unimport": "^3.11.1", + "unplugin": "^1.14.1", + "unplugin-vue-router": "^0.10.8", + "unstorage": "^1.12.0", "untyped": "^1.4.2", - "vue": "^3.4.38", + "vue": "^3.5.4", "vue-bundle-renderer": "^2.1.0", "vue-devtools-stub": "^0.1.0", - "vue-router": "^4.4.3" + "vue-router": "^4.4.4" }, "devDependencies": { - "@nuxt/scripts": "0.6.6", + "@nuxt/scripts": "0.8.5", "@nuxt/ui-templates": "1.3.4", "@parcel/watcher": "2.4.1", "@types/estree": "1.0.5", - "@vitejs/plugin-vue": "5.1.2", - "@vue/compiler-sfc": "3.4.38", + "@vitejs/plugin-vue": "5.1.3", + "@vue/compiler-sfc": "3.5.4", "unbuild": "3.0.0-rc.7", - "vite": "5.4.1", + "vite": "5.4.4", "vitest": "2.0.5" }, "peerDependencies": { diff --git a/packages/nuxt/src/app/components/dev-only.ts b/packages/nuxt/src/app/components/dev-only.ts index a1446dd3d..94b5ae84e 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 59e767f86..189c980e5 100644 --- a/packages/nuxt/src/app/components/island-renderer.ts +++ b/packages/nuxt/src/app/components/island-renderer.ts @@ -1,12 +1,14 @@ import type { defineAsyncComponent } from 'vue' import { createVNode, defineComponent, onErrorCaptured } from 'vue' +import { injectHead } from '@unhead/vue' import { createError } from '../composables/error' // @ts-expect-error virtual file import { islandComponents } from '#build/components.islands.mjs' export default defineComponent({ + name: 'IslandRenderer', props: { context: { type: Object as () => { name: string, props?: Record }, @@ -14,6 +16,10 @@ export default defineComponent({ }, }, setup (props) { + // reset head - we don't want to have any head tags from plugin or anywhere else. + const head = injectHead() + head.headEntries().splice(0, head.headEntries().length) + const component = islandComponents[props.context.name] as ReturnType if (!component) { diff --git a/packages/nuxt/src/app/components/nuxt-error-boundary.ts b/packages/nuxt/src/app/components/nuxt-error-boundary.ts index ea54ff839..8fb88d830 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 14feb22ae..c41743972 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 93832a1ea..937f9d313 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -3,7 +3,7 @@ import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineCom import { debounce } from 'perfect-debounce' import { hash } from 'ohash' import { appendResponseHeader } from 'h3' -import { useHead } from '@unhead/vue' +import { injectHead } from '@unhead/vue' import { randomUUID } from 'uncrypto' import { joinURL, withQuery } from 'ufo' import type { FetchResponse } from 'ofetch' @@ -45,6 +45,7 @@ async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['c export default defineComponent({ name: 'NuxtIsland', + inheritAttrs: false, props: { name: { type: String, @@ -96,7 +97,7 @@ export default defineComponent({ if (result.props) { toRevive.props = result.props } if (result.slots) { toRevive.slots = result.slots } if (result.components) { toRevive.components = result.components } - + if (result.head) { toRevive.head = result.head } nuxtApp.payload.data[key] = { __nuxt_island: { key, @@ -158,8 +159,7 @@ export default defineComponent({ return html }) - const cHead = ref>>>({ link: [], style: [] }) - useHead(cHead) + const head = injectHead() async function _fetchComponent (force = false) { const key = `${props.name}_${hashId.value}` @@ -199,8 +199,7 @@ export default defineComponent({ } try { const res: NuxtIslandResponse = await nuxtApp[pKey][uid.value] - cHead.value.link = res.head.link - cHead.value.style = res.head.style + ssrHTML.value = res.html.replaceAll(DATA_ISLAND_UID_RE, `data-island-uid="${uid.value}"`) key.value++ error.value = null @@ -248,6 +247,14 @@ export default defineComponent({ await loadComponents(props.source, payloads.components) } + if (import.meta.server || nuxtApp.isHydrating) { + // re-push head into active head instance + const responseHead = (nuxtApp.payload.data[`${props.name}_${hashId.value}`] as NuxtIslandResponse)?.head + if (responseHead) { + head.push(responseHead) + } + } + return (_ctx: any, _cache: any) => { if (!html.value || error.value) { return [slots.fallback?.({ error: error.value }) ?? createVNode('div')] diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index ca64ed923..f5922c9be 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -253,10 +253,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { }, prefetchOn: { type: [String, Object] as PropType, - default: options.prefetchOn || { - visibility: true, - interaction: false, - } satisfies NuxtLinkProps['prefetchOn'], + default: undefined, required: false, }, noPrefetch: { @@ -384,13 +381,15 @@ export function defineNuxtLink (options: NuxtLinkOptions) { replace: props.replace, ariaCurrentValue: props.ariaCurrentValue, custom: props.custom, - onPointerenter: shouldPrefetch('interaction') ? prefetch.bind(null, undefined) : undefined, - onFocus: shouldPrefetch('interaction') ? prefetch.bind(null, undefined) : undefined, } // `custom` API cannot support fallthrough attributes as the slot // may render fragment or text root nodes (#14897, #19375) if (!props.custom) { + if (shouldPrefetch('interaction')) { + routerLinkProps.onPointerenter = prefetch.bind(null, undefined) + routerLinkProps.onFocus = prefetch.bind(null, undefined) + } if (prefetched.value) { routerLinkProps.class = props.prefetchedClass || options.prefetchedClass } @@ -430,6 +429,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { return slots.default({ href: href.value, navigate, + prefetch, get route () { if (!href.value) { return undefined } 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 e459781dd..875204ccf 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, 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 9f9fefc8b..7db804273 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/route-provider.ts b/packages/nuxt/src/app/components/route-provider.ts index 3f81d028a..16ac724bc 100644 --- a/packages/nuxt/src/app/components/route-provider.ts +++ b/packages/nuxt/src/app/components/route-provider.ts @@ -27,6 +27,7 @@ export const RouteProvider = defineComponent({ for (const key in props.route) { Object.defineProperty(route, key, { get: () => previousKey === props.renderKey ? props.route[key as keyof RouteLocationNormalizedLoaded] : previousRoute[key as keyof RouteLocationNormalizedLoaded], + enumerable: true, }) } diff --git a/packages/nuxt/src/app/components/test-component-wrapper.ts b/packages/nuxt/src/app/components/test-component-wrapper.ts index 8b0da2438..b2de69d8f 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/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts index 8c050f48e..2081b9630 100644 --- a/packages/nuxt/src/app/composables/asyncData.ts +++ b/packages/nuxt/src/app/composables/asyncData.ts @@ -1,5 +1,5 @@ import { computed, getCurrentInstance, getCurrentScope, onBeforeMount, onScopeDispose, onServerPrefetch, onUnmounted, ref, shallowRef, toRef, unref, watch } from 'vue' -import type { Ref, WatchSource } from 'vue' +import type { MultiWatchSources, Ref } from 'vue' import type { NuxtApp } from '../nuxt' import { useNuxtApp } from '../nuxt' import { toArray } from '../utils' @@ -34,7 +34,7 @@ export type KeysOf = Array< export type KeyOfRes = KeysOf> -export type MultiWatchSources = (WatchSource | object)[] +export type { MultiWatchSources } export type NoInfer = [T][T extends any ? 0 : never] diff --git a/packages/nuxt/src/app/composables/component.ts b/packages/nuxt/src/app/composables/component.ts index ebc234226..e8bd93e20 100644 --- a/packages/nuxt/src/app/composables/component.ts +++ b/packages/nuxt/src/app/composables/component.ts @@ -16,7 +16,7 @@ async function runLegacyAsyncData (res: Record | Promise '') : fetchKey) || ([_fetchKeyBase, route.fullPath, route.matched.findIndex(r => Object.values(r.components || {}).includes(vm.type))].join(':')) - const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => nuxtApp.runWithContext(() => fn(nuxtApp))) + const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => import.meta.server ? nuxtApp.runWithContext(() => fn(nuxtApp)) : fn(nuxtApp)) if (error.value) { throw createError(error.value) } diff --git a/packages/nuxt/src/app/composables/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts index fa93422de..47a929967 100644 --- a/packages/nuxt/src/app/composables/cookie.ts +++ b/packages/nuxt/src/app/composables/cookie.ts @@ -103,9 +103,18 @@ export function useCookie (name: string, _opts?: } if (store) { + /* event is of type CookieChangeEvent */ const changeHandler = (event: any) => { - const cookie = event.changed.find((c: any) => c.name === name) - if (cookie) { handleChange({ value: cookie.value }) } + const changedCookie = event.changed.find((c: any) => c.name === name) + const removedCookie = event.deleted.find((c: any) => c.name === name) + + if (changedCookie) { + handleChange({ value: changedCookie.value }) + } + + if (removedCookie) { + handleChange({ value: null }) + } } store.addEventListener('change', changeHandler) if (hasScope) { diff --git a/packages/nuxt/src/app/composables/error.ts b/packages/nuxt/src/app/composables/error.ts index c172007f0..8c5885e52 100644 --- a/packages/nuxt/src/app/composables/error.ts +++ b/packages/nuxt/src/app/composables/error.ts @@ -53,7 +53,7 @@ export const clearError = async (options: { redirect?: string } = {}) => { /** @since 3.0.0 */ export const isNuxtError = ( - error?: string | object, + error: unknown, ): error is NuxtError => !!error && typeof error === 'object' && NUXT_ERROR_SIGNATURE in error /** @since 3.0.0 */ diff --git a/packages/nuxt/src/app/config.ts b/packages/nuxt/src/app/config.ts index 4be4349fc..476f828ab 100644 --- a/packages/nuxt/src/app/config.ts +++ b/packages/nuxt/src/app/config.ts @@ -11,6 +11,15 @@ type DeepPartial = T extends Function ? T : T extends Record ? { // Workaround for vite HMR with virtual modules export const _getAppConfig = () => __appConfig as AppConfig +function isPojoOrArray (val: unknown): val is object { + return ( + Array.isArray(val) || + (!!val && + typeof val === 'object' && + val.constructor?.name === 'Object') + ) +} + function deepDelete (obj: any, newObj: any) { for (const key in obj) { const val = newObj[key] @@ -18,7 +27,7 @@ function deepDelete (obj: any, newObj: any) { delete (obj as any)[key] } - if (val !== null && typeof val === 'object') { + if (isPojoOrArray(val)) { deepDelete(obj[key], newObj[key]) } } @@ -27,7 +36,7 @@ function deepDelete (obj: any, newObj: any) { function deepAssign (obj: any, newObj: any) { for (const key in newObj) { const val = newObj[key] - if (val !== null && typeof val === 'object') { + if (isPojoOrArray(val)) { const defaultVal = Array.isArray(val) ? [] : {} obj[key] = obj[key] || defaultVal deepAssign(obj[key], val) diff --git a/packages/nuxt/src/app/entry.ts b/packages/nuxt/src/app/entry.ts index e60a48bd8..7f29843c8 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' diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index 6024fb570..e90befa4d 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' @@ -267,6 +266,7 @@ export function createNuxtApp (options: CreateOptions) { get vue () { return nuxtApp.vueApp.version }, }, payload: shallowReactive({ + ...options.ssrContext?.payload || {}, data: shallowReactive({}), state: reactive({}), once: new Set(), @@ -275,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)) } @@ -310,6 +310,20 @@ export function createNuxtApp (options: CreateOptions) { nuxtApp.payload.serverRendered = true } + if (import.meta.server && nuxtApp.ssrContext) { + nuxtApp.payload.path = nuxtApp.ssrContext.url + + // Expose nuxt to the renderContext + nuxtApp.ssrContext.nuxt = nuxtApp + nuxtApp.ssrContext.payload = nuxtApp.payload + + // Expose client runtime-config to the payload + nuxtApp.ssrContext.config = { + public: nuxtApp.ssrContext.runtimeConfig.public, + app: nuxtApp.ssrContext.runtimeConfig.app, + } + } + if (import.meta.client) { const __NUXT__ = multiApp ? window.__NUXT__?.[nuxtApp._id] : window.__NUXT__ // TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336 @@ -356,35 +370,14 @@ export function createNuxtApp (options: CreateOptions) { defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp) defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp) - if (import.meta.server) { - if (nuxtApp.ssrContext) { - // Expose nuxt to the renderContext - nuxtApp.ssrContext.nuxt = nuxtApp - // Expose payload types - nuxtApp.ssrContext._payloadReducers = {} - // Expose current path - nuxtApp.payload.path = nuxtApp.ssrContext.url - } - // Expose to server renderer to create payload - nuxtApp.ssrContext = nuxtApp.ssrContext || {} as any - if (nuxtApp.ssrContext!.payload) { - Object.assign(nuxtApp.payload, nuxtApp.ssrContext!.payload) - } - nuxtApp.ssrContext!.payload = nuxtApp.payload - - // Expose client runtime-config to the payload - nuxtApp.ssrContext!.config = { - public: options.ssrContext!.runtimeConfig.public, - app: options.ssrContext!.runtimeConfig.app, - } - } - - // 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 }) + 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 e8eb5e2e4..c43d43342 100644 --- a/packages/nuxt/src/app/plugins/chunk-reload.client.ts +++ b/packages/nuxt/src/app/plugins/chunk-reload.client.ts @@ -26,7 +26,7 @@ export default defineNuxtPlugin({ }) router.onError((error, to) => { - if (chunkErrors.has(error)) { + if (chunkErrors.has(error) || error.message.includes('Failed to fetch dynamically imported module')) { reloadAppAtPath(to) } }) diff --git a/packages/nuxt/src/app/plugins/dev-server-logs.ts b/packages/nuxt/src/app/plugins/dev-server-logs.ts index 398ab47cd..468ba80d3 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 24452437b..3ab2f211d 100644 --- a/packages/nuxt/src/app/plugins/revive-payload.client.ts +++ b/packages/nuxt/src/app/plugins/revive-payload.client.ts @@ -31,11 +31,6 @@ if (componentIslands) { } return { html: '', - state: {}, - head: { - link: [], - style: [], - }, ...result, } } diff --git a/packages/nuxt/src/app/plugins/revive-payload.server.ts b/packages/nuxt/src/app/plugins/revive-payload.server.ts index f2cb08983..773b8c77f 100644 --- a/packages/nuxt/src/app/plugins/revive-payload.server.ts +++ b/packages/nuxt/src/app/plugins/revive-payload.server.ts @@ -6,25 +6,25 @@ import { defineNuxtPlugin } from '../nuxt' // @ts-expect-error Virtual file. import { componentIslands } from '#build/nuxt.config.mjs' -const reducers: Record any> = { - NuxtError: data => isNuxtError(data) && data.toJSON(), - EmptyShallowRef: data => isRef(data) && isShallow(data) && !data.value && (typeof data.value === 'bigint' ? '0n' : (JSON.stringify(data.value) || '_')), - EmptyRef: data => isRef(data) && !data.value && (typeof data.value === 'bigint' ? '0n' : (JSON.stringify(data.value) || '_')), - ShallowRef: data => isRef(data) && isShallow(data) && data.value, - ShallowReactive: data => isReactive(data) && isShallow(data) && toRaw(data), - Ref: data => isRef(data) && data.value, - Reactive: data => isReactive(data) && toRaw(data), -} +const reducers: [string, (data: any) => any][] = [ + ['NuxtError', data => isNuxtError(data) && data.toJSON()], + ['EmptyShallowRef', data => isRef(data) && isShallow(data) && !data.value && (typeof data.value === 'bigint' ? '0n' : (JSON.stringify(data.value) || '_'))], + ['EmptyRef', data => isRef(data) && !data.value && (typeof data.value === 'bigint' ? '0n' : (JSON.stringify(data.value) || '_'))], + ['ShallowRef', data => isRef(data) && isShallow(data) && data.value], + ['ShallowReactive', data => isReactive(data) && isShallow(data) && toRaw(data)], + ['Ref', data => isRef(data) && data.value], + ['Reactive', data => isReactive(data) && toRaw(data)], +] if (componentIslands) { - reducers.Island = data => data && data?.__nuxt_island + reducers.push(['Island', data => data && data?.__nuxt_island]) } export default defineNuxtPlugin({ name: 'nuxt:revive-payload:server', setup () { - for (const reducer in reducers) { - definePayloadReducer(reducer, reducers[reducer as keyof typeof reducers]) + for (const [reducer, fn] of reducers) { + definePayloadReducer(reducer, fn) } }, }) diff --git a/packages/nuxt/src/app/plugins/view-transitions.client.ts b/packages/nuxt/src/app/plugins/view-transitions.client.ts index e23dc02a0..c24fb0e35 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/islandsTransform.ts b/packages/nuxt/src/components/islandsTransform.ts index e10f1cd95..d583e3425 100644 --- a/packages/nuxt/src/components/islandsTransform.ts +++ b/packages/nuxt/src/components/islandsTransform.ts @@ -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 = / + + diff --git a/test/fixtures/basic/pages/validate-custom-error.vue b/test/fixtures/basic/pages/validate-custom-error.vue new file mode 100644 index 000000000..74c2a09a1 --- /dev/null +++ b/test/fixtures/basic/pages/validate-custom-error.vue @@ -0,0 +1,12 @@ + + + diff --git a/test/fixtures/basic/plugins/chunk-error.ts b/test/fixtures/basic/plugins/chunk-error.ts deleted file mode 100644 index e23e5ecdd..000000000 --- a/test/fixtures/basic/plugins/chunk-error.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default defineNuxtPlugin((nuxtApp) => { - nuxtApp.hook('app:chunkError', () => { - console.log('caught chunk load error') - }) -}) diff --git a/test/fixtures/basic/plugins/invalid-plugin-2.ts b/test/fixtures/basic/plugins/invalid-plugin-2.ts deleted file mode 100644 index b5e18bd7e..000000000 --- a/test/fixtures/basic/plugins/invalid-plugin-2.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function previousPlugin (one, inject) { - inject() -} diff --git a/test/fixtures/minimal-types/package.json b/test/fixtures/minimal-types/package.json index dd31eb9fb..8cdd0337f 100644 --- a/test/fixtures/minimal-types/package.json +++ b/test/fixtures/minimal-types/package.json @@ -6,7 +6,6 @@ "test:types": "nuxi prepare && npx vue-tsc --noEmit" }, "dependencies": { - "nuxt": "workspace:*", - "vue": "latest" + "nuxt": "workspace:*" } } diff --git a/test/fixtures/minimal/package.json b/test/fixtures/minimal/package.json index 83b314f39..b3c2d8215 100644 --- a/test/fixtures/minimal/package.json +++ b/test/fixtures/minimal/package.json @@ -5,7 +5,6 @@ "build": "nuxi build" }, "dependencies": { - "nuxt": "workspace:*", - "vue": "latest" + "nuxt": "workspace:*" } } diff --git a/test/fixtures/remote-provider/package.json b/test/fixtures/remote-provider/package.json new file mode 100644 index 000000000..79fc01875 --- /dev/null +++ b/test/fixtures/remote-provider/package.json @@ -0,0 +1,5 @@ +{ + "name": "remote-provider", + "version": "0.0.0", + "private": true +} \ No newline at end of file diff --git a/test/fixtures/runtime-compiler/package.json b/test/fixtures/runtime-compiler/package.json index 0ee7c1d0a..cf133dcd9 100644 --- a/test/fixtures/runtime-compiler/package.json +++ b/test/fixtures/runtime-compiler/package.json @@ -6,11 +6,5 @@ }, "dependencies": { "nuxt": "workspace:*" - }, - "devDependencies": { - "@unhead/shared": "latest", - "@vue/devtools-api": "latest", - "@vue/shared": "latest", - "unhead": "latest" } } diff --git a/test/fixtures/suspense/package.json b/test/fixtures/suspense/package.json index 85cc733b0..44a373a43 100644 --- a/test/fixtures/suspense/package.json +++ b/test/fixtures/suspense/package.json @@ -5,14 +5,9 @@ "build": "nuxi build" }, "dependencies": { - "nuxt": "workspace:*", - "vue": "latest" + "nuxt": "workspace:*" }, "devDependencies": { - "@unhead/shared": "latest", - "@vue/devtools-api": "latest", - "@vue/shared": "latest", - "typescript": "latest", - "unhead": "latest" + "typescript": "latest" } } diff --git a/test/nuxt/composables.test.ts b/test/nuxt/composables.test.ts index 7342ebfa0..8eeaf1d91 100644 --- a/test/nuxt/composables.test.ts +++ b/test/nuxt/composables.test.ts @@ -29,23 +29,33 @@ registerEndpoint('/api/test', defineEventHandler(event => ({ describe('app config', () => { it('can be updated', () => { const appConfig = useAppConfig() - expect(appConfig).toMatchInlineSnapshot(` - { - "nuxt": {}, - } - `) - updateAppConfig({ + expect(appConfig).toStrictEqual({ nuxt: {} }) + + type UpdateAppConfig = Parameters[0] + + const initConfig: UpdateAppConfig = { new: 'value', nuxt: { nested: 42 }, + regExp: /foo/g, + date: new Date(1111, 11, 11), + arr: [1, 2, 3], + } + updateAppConfig(initConfig) + expect(appConfig).toStrictEqual(initConfig) + + const newConfig: UpdateAppConfig = { + nuxt: { anotherNested: 24 }, + regExp: /bar/g, + date: new Date(2222, 12, 12), + arr: [4, 5], + } + updateAppConfig(newConfig) + expect(appConfig).toStrictEqual({ + ...initConfig, + ...newConfig, + nuxt: { ...initConfig.nuxt, ...newConfig.nuxt }, + arr: [4, 5, 3], }) - expect(appConfig).toMatchInlineSnapshot(` - { - "new": "value", - "nuxt": { - "nested": 42, - }, - } - `) }) }) diff --git a/test/prepare.ts b/test/prepare.ts index e4442a18a..891ebdd49 100644 --- a/test/prepare.ts +++ b/test/prepare.ts @@ -1,12 +1,11 @@ import { fileURLToPath } from 'node:url' import { rm } from 'node:fs/promises' -import { globby } from 'globby' - -import { execa } from 'execa' +import { glob } from 'tinyglobby' +import { exec } from 'tinyexec' async function initTesting () { - const dirs = await globby('*', { + const dirs = await glob(['*'], { onlyDirectories: true, cwd: fileURLToPath(new URL('./fixtures', import.meta.url)), absolute: true, @@ -20,7 +19,7 @@ async function initTesting () { ]) await Promise.all( - dirs.map(dir => execa('pnpm', ['nuxi', 'prepare'], { cwd: dir })), + dirs.map(dir => exec('pnpm', ['nuxi', 'prepare'], { nodeOptions: { cwd: dir } })), ) } diff --git a/tsconfig.json b/tsconfig.json index e83f2d26d..c912996aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "strict": true, // TODO: enable noUncheckedIndexedAccess // "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, "forceConsistentCasingInFileNames": true, "noImplicitOverride": true, /* If NOT transpiling with TypeScript: */