mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
Merge remote-tracking branch 'origin/main' into feat-add-auto-import-directives
This commit is contained in:
commit
cf14552c5e
@ -1,4 +1,4 @@
|
||||
FROM node:lts@sha256:a5e0ed56f2c20b9689e0f7dd498cac7e08d2a3a283e92d9304e7b9b83e3c6ff3
|
||||
FROM node:lts@sha256:de4c8be8232b7081d8846360d73ce6dbff33c6636f2259cd14d82c0de1ac3ff2
|
||||
|
||||
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 && \
|
||||
|
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -19,4 +19,4 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5
|
||||
uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0
|
||||
|
2
.github/workflows/docs-check-links.yml
vendored
2
.github/workflows/docs-check-links.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Lychee link checker
|
||||
uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede # for v1.8.0
|
||||
uses: lycheeverse/lychee-action@ae4699150ab670dcfb64cc74e8680e776d9caae2 # for v1.8.0
|
||||
with:
|
||||
# arguments with file types to check
|
||||
args: >-
|
||||
|
@ -33,6 +33,7 @@ You don't have to use TypeScript to build an application with Nuxt. However, it
|
||||
You can configure fully typed, per-environment overrides in your nuxt.config
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
// @errors: 2353
|
||||
export default defineNuxtConfig({
|
||||
$production: {
|
||||
routeRules: {
|
||||
@ -41,10 +42,17 @@ export default defineNuxtConfig({
|
||||
},
|
||||
$development: {
|
||||
//
|
||||
}
|
||||
},
|
||||
$myCustomName: {
|
||||
//
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
To select an environment when running a Nuxt CLI command, simply pass the name to the `--envName` flag, like so: `nuxi build --envName myCustomName`.
|
||||
|
||||
To learn more about the mechanism behind these overrides, please refer to the `c12` documentation on [environment-specific configuration](https://github.com/unjs/c12?tab=readme-ov-file#environment-specific-configuration).
|
||||
|
||||
::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=DFZI2iVCrNc" target="_blank"}
|
||||
Watch a video from Alexander Lichter about the env-aware `nuxt.config.ts`.
|
||||
::
|
||||
|
@ -88,6 +88,37 @@ It is recommended to use `$fetch` for client-side interactions (event based) or
|
||||
Read more about `$fetch`.
|
||||
::
|
||||
|
||||
### Pass Client Headers to the API
|
||||
|
||||
During server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it won't include the user's browser cookies.
|
||||
|
||||
We can use [`useRequestHeaders`](/docs/api/composables/use-request-headers) to access and proxy cookies to the API from server-side.
|
||||
|
||||
The example below adds the request headers to an isomorphic `$fetch` call to ensure that the API endpoint has access to the same `cookie` header originally sent by the user.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const headers = useRequestHeaders(['cookie'])
|
||||
|
||||
async function getCurrentUser() {
|
||||
return await $fetch('/api/me', { headers: headers.value })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
::caution
|
||||
Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
|
||||
|
||||
- `host`, `accept`
|
||||
- `content-length`, `content-md5`, `content-type`
|
||||
- `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-proto`
|
||||
- `cf-connecting-ip`, `cf-ray`
|
||||
::
|
||||
|
||||
::tip
|
||||
You can also use [`useRequestFetch`](/docs/api/composables/use-request-fetch) to proxy headers to the call automatically.
|
||||
:::
|
||||
|
||||
## `useFetch`
|
||||
|
||||
The [`useFetch`](/docs/api/composables/use-fetch) composable uses `$fetch` under-the-hood to make SSR-safe network calls in the setup function.
|
||||
@ -117,8 +148,8 @@ Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
|
||||
The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved.
|
||||
|
||||
::tip
|
||||
`useFetch(url)` is nearly equivalent to `useAsyncData(url, () => $fetch(url))`. :br
|
||||
It's developer experience sugar for the most common use case.
|
||||
`useFetch(url)` is nearly equivalent to `useAsyncData(url, () => event.$fetch(url))`. :br
|
||||
It's developer experience sugar for the most common use case. (You can find out more about `event.fetch` at [`useRequestFetch`](/docs/api/composables/use-request-fetch).)
|
||||
::
|
||||
|
||||
::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=0X-aOpSGabA" target="_blank"}
|
||||
@ -458,32 +489,13 @@ For finer control, the `status` variable can be:
|
||||
- `error` when the fetch fails
|
||||
- `success` when the fetch is completed successfully
|
||||
|
||||
## Passing Headers and cookies
|
||||
## Passing Headers and Cookies
|
||||
|
||||
When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API. But during server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it doesn't include the user's browser cookies, nor does it pass on cookies from the fetch response.
|
||||
When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API.
|
||||
|
||||
### Pass Client Headers to the API
|
||||
Normally, during server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it wouldn't include the user's browser cookies, nor pass on cookies from the fetch response.
|
||||
|
||||
We can use [`useRequestHeaders`](/docs/api/composables/use-request-headers) to access and proxy cookies to the API from server-side.
|
||||
|
||||
The example below adds the request headers to an isomorphic `$fetch` call to ensure that the API endpoint has access to the same `cookie` header originally sent by the user.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const headers = useRequestHeaders(['cookie'])
|
||||
|
||||
const { data } = await useFetch('/api/me', { headers })
|
||||
</script>
|
||||
```
|
||||
|
||||
::caution
|
||||
Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
|
||||
|
||||
- `host`, `accept`
|
||||
- `content-length`, `content-md5`, `content-type`
|
||||
- `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-proto`
|
||||
- `cf-connecting-ip`, `cf-ray`
|
||||
::
|
||||
However, when calling `useFetch` on the server, Nuxt will use [`useRequestFetch`](/docs/api/composables/use-request-fetch) to proxy headers and cookies (with the exception of headers not meant to be forwarded, like `host`).
|
||||
|
||||
### Pass Cookies From Server-side API Calls on SSR Response
|
||||
|
||||
|
@ -323,6 +323,10 @@ You may define a name for this page's route.
|
||||
|
||||
You may define a path matcher, if you have a more complex pattern than can be expressed with the file name. See [the `vue-router` docs](https://router.vuejs.org/guide/essentials/route-matching-syntax.html#custom-regex-in-params) for more information.
|
||||
|
||||
#### `props`
|
||||
|
||||
Allows accessing the route `params` as props passed to the page component. See[the `vue-router` docs](https://router.vuejs.org/guide/essentials/passing-props) for more information.
|
||||
|
||||
### Typing Custom Metadata
|
||||
|
||||
If you add custom metadata for your pages, you may wish to do so in a type-safe way. It is possible to augment the type of the object accepted by `definePageMeta`:
|
||||
|
@ -59,14 +59,16 @@ This feature will likely be removed in a near future.
|
||||
|
||||
## emitRouteChunkError
|
||||
|
||||
Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a hard reload of the new route when a chunk fails to load.
|
||||
Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a reload of the new route on navigation to a new route when a chunk fails to load.
|
||||
|
||||
If you set this to `'automatic-immediate'` Nuxt will reload the current route immediatly, instead of waiting for a navigation. This is useful for chunk errors that are not triggered by navigation, e.g., when your Nuxt app fails to load a [lazy component](/docs/guide/directory-structure/components#dynamic-imports). A potential downside of this behavior is undesired reloads, e.g., when your app does not need the chunk that caused the error.
|
||||
|
||||
You can disable automatic handling by setting this to `false`, or handle chunk errors manually by setting it to `manual`.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
emitRouteChunkError: 'automatic' // or 'manual' or false
|
||||
emitRouteChunkError: 'automatic' // or 'automatic-immediate', 'manual' or false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
@ -77,7 +77,7 @@ export function useAPI<T>(
|
||||
) {
|
||||
return useFetch(url, {
|
||||
...options,
|
||||
$fetch: useNuxtApp().$api
|
||||
$fetch: useNuxtApp().$api as typeof $fetch
|
||||
})
|
||||
}
|
||||
```
|
||||
|
43
docs/3.api/2.composables/use-runtime-hook.md
Normal file
43
docs/3.api/2.composables/use-runtime-hook.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: useRuntimeHook
|
||||
description: Registers a runtime hook in a Nuxt application and ensures it is properly disposed of when the scope is destroyed.
|
||||
links:
|
||||
- label: Source
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/runtime-hook.ts
|
||||
size: xs
|
||||
---
|
||||
|
||||
::important
|
||||
This composable is available in Nuxt v3.14+.
|
||||
::
|
||||
|
||||
```ts [signature]
|
||||
function useRuntimeHook<THookName extends keyof RuntimeNuxtHooks>(
|
||||
name: THookName,
|
||||
fn: RuntimeNuxtHooks[THookName] extends HookCallback ? RuntimeNuxtHooks[THookName] : never
|
||||
): void
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Parameters
|
||||
|
||||
- `name`: The name of the runtime hook to register. You can see the full list of [runtime Nuxt hooks here](/docs/api/advanced/hooks#app-hooks-runtime).
|
||||
- `fn`: The callback function to execute when the hook is triggered. The function signature varies based on the hook name.
|
||||
|
||||
### Returns
|
||||
|
||||
The composable doesn't return a value, but it automatically unregisters the hook when the component's scope is destroyed.
|
||||
|
||||
## Example
|
||||
|
||||
```vue twoslash [pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
// Register a hook that runs every time a link is prefetched, but which will be
|
||||
// automatically cleaned up (and not called again) when the component is unmounted
|
||||
useRuntimeHook('link:prefetch', (link) => {
|
||||
console.log('Prefetching', link)
|
||||
})
|
||||
</script>
|
||||
```
|
@ -52,7 +52,8 @@ Hook | Arguments | Description
|
||||
`build:manifest` | `manifest` | Called during the manifest build by Vite and webpack. This allows customizing the manifest that Nitro will use to render `<script>` and `<link>` tags in the final HTML.
|
||||
`builder:generateApp` | `options` | Called before generating the app.
|
||||
`builder:watch` | `event, path` | Called at build time in development when the watcher spots a change to a file or directory in the project.
|
||||
`pages:extend` | `pages` | Called after pages routes are resolved.
|
||||
`pages:extend` | `pages` | Called after page routes are scanned from the file system.
|
||||
`pages:resolved` | `pages` | Called after page routes have been augmented with scanned metadata.
|
||||
`pages:routerOptions` | `{ files: Array<{ path: string, optional?: boolean }> }` | Called when resolving `router.options` files. Later items in the array override earlier ones.
|
||||
`server:devHandler` | `handler` | Called when the dev middleware is being registered on the Nitro dev server.
|
||||
`imports:sources` | `presets` | Called at setup allowing modules to extend sources.
|
||||
|
36
package.json
36
package.json
@ -40,19 +40,19 @@
|
||||
"@nuxt/ui-templates": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@types/node": "20.17.0",
|
||||
"@types/node": "22.8.6",
|
||||
"@vue/compiler-core": "3.5.12",
|
||||
"@vue/compiler-dom": "3.5.12",
|
||||
"@vue/shared": "3.5.12",
|
||||
"c12": "2.0.1",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "2.3.3",
|
||||
"jiti": "2.4.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxt": "workspace:*",
|
||||
"ohash": "1.1.4",
|
||||
"postcss": "8.4.47",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.3",
|
||||
"send": ">=1.1.0",
|
||||
"typescript": "5.6.3",
|
||||
"ufo": "1.5.4",
|
||||
@ -61,20 +61,20 @@
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.13.0",
|
||||
"@nuxt/eslint-config": "0.6.0",
|
||||
"@eslint/js": "9.14.0",
|
||||
"@nuxt/eslint-config": "0.6.1",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/test-utils": "3.14.4",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@types/node": "20.17.0",
|
||||
"@types/node": "22.8.6",
|
||||
"@types/semver": "7.5.8",
|
||||
"@unhead/schema": "1.11.10",
|
||||
"@unhead/schema": "1.11.11",
|
||||
"@unhead/vue": "1.11.10",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitest/coverage-v8": "2.1.3",
|
||||
"@vitest/coverage-v8": "2.1.4",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"autoprefixer": "10.4.20",
|
||||
"case-police": "0.7.0",
|
||||
@ -83,36 +83,36 @@
|
||||
"cssnano": "7.0.6",
|
||||
"destr": "2.0.3",
|
||||
"devalue": "5.1.1",
|
||||
"eslint": "9.13.0",
|
||||
"eslint": "9.14.0",
|
||||
"eslint-plugin-no-only-tests": "3.3.0",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-typegen": "0.3.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"happy-dom": "15.7.4",
|
||||
"jiti": "2.3.3",
|
||||
"happy-dom": "15.8.0",
|
||||
"jiti": "2.4.0",
|
||||
"markdownlint-cli": "0.42.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxi": "3.15.0",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.1.1",
|
||||
"ofetch": "1.4.1",
|
||||
"pathe": "1.1.2",
|
||||
"playwright-core": "1.48.1",
|
||||
"playwright-core": "1.48.2",
|
||||
"rimraf": "6.0.1",
|
||||
"semver": "7.6.3",
|
||||
"sherif": "1.0.1",
|
||||
"std-env": "3.7.0",
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyglobby": "0.2.9",
|
||||
"tinyglobby": "0.2.10",
|
||||
"typescript": "5.6.3",
|
||||
"ufo": "1.5.4",
|
||||
"vitest": "2.1.3",
|
||||
"vitest": "2.1.4",
|
||||
"vitest-environment-nuxt": "1.0.1",
|
||||
"vue": "3.5.12",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-tsc": "2.1.6"
|
||||
"vue-tsc": "2.1.10"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.2",
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
"engines": {
|
||||
"node": "^16.10.0 || >=18.0.0"
|
||||
},
|
||||
|
@ -35,7 +35,7 @@
|
||||
"globby": "^14.0.2",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^6.0.2",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"klona": "^2.0.6",
|
||||
"mlly": "^1.7.2",
|
||||
"pathe": "^1.1.2",
|
||||
@ -51,11 +51,11 @@
|
||||
"@rspack/core": "1.0.14",
|
||||
"@types/hash-sum": "1.0.2",
|
||||
"@types/semver": "7.5.8",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.10",
|
||||
"vitest": "2.1.3",
|
||||
"webpack": "5.95.0"
|
||||
"vitest": "2.1.4",
|
||||
"webpack": "5.96.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
|
@ -65,12 +65,12 @@
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/telemetry": "^2.6.0",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@unhead/dom": "^1.11.10",
|
||||
"@unhead/shared": "^1.11.10",
|
||||
"@unhead/dom": "^1.11.11",
|
||||
"@unhead/shared": "^1.11.11",
|
||||
"@unhead/ssr": "^1.11.10",
|
||||
"@unhead/vue": "^1.11.10",
|
||||
"@vue/shared": "^3.5.12",
|
||||
"acorn": "8.13.0",
|
||||
"acorn": "8.14.0",
|
||||
"c12": "^2.0.1",
|
||||
"chokidar": "^4.0.1",
|
||||
"compatx": "^0.1.8",
|
||||
@ -88,13 +88,13 @@
|
||||
"hookable": "^5.5.3",
|
||||
"ignore": "^6.0.2",
|
||||
"impound": "^0.2.0",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"mlly": "^1.7.2",
|
||||
"nanotar": "^0.1.1",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxi": "^3.15.0",
|
||||
"nypm": "^0.3.12",
|
||||
"ofetch": "^1.4.1",
|
||||
@ -107,7 +107,7 @@
|
||||
"semver": "^7.6.3",
|
||||
"std-env": "^3.7.0",
|
||||
"strip-literal": "^2.1.0",
|
||||
"tinyglobby": "0.2.9",
|
||||
"tinyglobby": "0.2.10",
|
||||
"ufo": "^1.5.4",
|
||||
"ultrahtml": "^1.5.3",
|
||||
"uncrypto": "^0.1.3",
|
||||
@ -115,9 +115,9 @@
|
||||
"unenv": "^1.10.0",
|
||||
"unhead": "^1.11.10",
|
||||
"unimport": "^3.13.1",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin": "^1.15.0",
|
||||
"unplugin-vue-router": "^0.10.8",
|
||||
"unstorage": "^1.12.0",
|
||||
"unstorage": "^1.13.1",
|
||||
"untyped": "^1.5.1",
|
||||
"vue": "^3.5.12",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
@ -133,7 +133,7 @@
|
||||
"@vue/compiler-sfc": "3.5.12",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.10",
|
||||
"vitest": "2.1.3"
|
||||
"vitest": "2.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@parcel/watcher": "^2.1.0",
|
||||
|
@ -38,3 +38,4 @@ export { useRequestURL } from './url'
|
||||
export { usePreviewMode } from './preview'
|
||||
export { useId } from './id'
|
||||
export { useRouteAnnouncer } from './route-announcer'
|
||||
export { useRuntimeHook } from './runtime-hook'
|
||||
|
21
packages/nuxt/src/app/composables/runtime-hook.ts
Normal file
21
packages/nuxt/src/app/composables/runtime-hook.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { onScopeDispose } from 'vue'
|
||||
import type { HookCallback } from 'hookable'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import type { RuntimeNuxtHooks } from '../nuxt'
|
||||
|
||||
/**
|
||||
* Registers a runtime hook in a Nuxt application and ensures it is properly disposed of when the scope is destroyed.
|
||||
* @param name - The name of the hook to register.
|
||||
* @param fn - The callback function to be executed when the hook is triggered.
|
||||
* @since 3.14.0
|
||||
*/
|
||||
export function useRuntimeHook<THookName extends keyof RuntimeNuxtHooks> (
|
||||
name: THookName,
|
||||
fn: RuntimeNuxtHooks[THookName] extends HookCallback ? RuntimeNuxtHooks[THookName] : never,
|
||||
): void {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
const unregister = nuxtApp.hook(name, fn)
|
||||
|
||||
onScopeDispose(unregister)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt'
|
||||
export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt'
|
||||
|
||||
export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index'
|
||||
export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta, useRuntimeHook } from './composables/index'
|
||||
export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index'
|
||||
|
||||
export { defineNuxtLink } from './components/index'
|
||||
|
@ -45,7 +45,7 @@ export interface RuntimeNuxtHooks {
|
||||
'app:chunkError': (options: { error: any }) => HookResult
|
||||
'app:data:refresh': (keys?: string[]) => HookResult
|
||||
'app:manifest:update': (meta?: NuxtAppManifestMeta) => HookResult
|
||||
'dev:ssr-logs': (logs: LogObject[]) => void | Promise<void>
|
||||
'dev:ssr-logs': (logs: LogObject[]) => HookResult
|
||||
'link:prefetch': (link: string) => HookResult
|
||||
'page:start': (Component?: VNode) => HookResult
|
||||
'page:finish': (Component?: VNode) => HookResult
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { defineNuxtPlugin } from '../nuxt'
|
||||
import { reloadNuxtApp } from '../composables/chunk'
|
||||
import { addRouteMiddleware } from '../composables/router'
|
||||
|
||||
const reloadNuxtApp_ = (path: string) => { reloadNuxtApp({ persistState: true, path }) }
|
||||
|
||||
// See https://github.com/nuxt/nuxt/issues/23612 for more context
|
||||
export default defineNuxtPlugin({
|
||||
name: 'nuxt:chunk-reload-immediate',
|
||||
setup (nuxtApp) {
|
||||
// Remember `to.path` when navigating to a new path: A `chunkError` may occur during navigation, we then want to then reload at `to.path`
|
||||
let currentlyNavigationTo: null | string = null
|
||||
addRouteMiddleware((to) => {
|
||||
currentlyNavigationTo = to.path
|
||||
})
|
||||
|
||||
// Reload when a `chunkError` is thrown
|
||||
nuxtApp.hook('app:chunkError', () => reloadNuxtApp_(currentlyNavigationTo ?? nuxtApp._route.path))
|
||||
|
||||
// Reload when the app manifest updates
|
||||
nuxtApp.hook('app:manifest:update', () => reloadNuxtApp_(nuxtApp._route.path))
|
||||
},
|
||||
})
|
@ -17,7 +17,7 @@ import { version as nuxtVersion } from '../../package.json'
|
||||
import { distDir } from '../dirs'
|
||||
import { toArray } from '../utils'
|
||||
import { template as defaultSpaLoadingTemplate } from '../../../ui-templates/dist/templates/spa-loading-icon'
|
||||
import { nuxtImportProtections } from './plugins/import-protection'
|
||||
import { createImportProtectionPatterns } from './plugins/import-protection'
|
||||
import { EXTENSION_RE } from './utils'
|
||||
|
||||
const logLevelMapReverse = {
|
||||
@ -49,6 +49,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
.map(m => m.entryPath!),
|
||||
)
|
||||
|
||||
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
|
||||
|
||||
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
|
||||
debug: nuxt.options.debug,
|
||||
rootDir: nuxt.options.rootDir,
|
||||
@ -66,6 +68,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
},
|
||||
imports: {
|
||||
autoImport: nuxt.options.imports.autoImport as boolean,
|
||||
dirs: isNuxtV4
|
||||
? [
|
||||
resolve(nuxt.options.rootDir, 'shared', 'utils'),
|
||||
resolve(nuxt.options.rootDir, 'shared', 'types'),
|
||||
]
|
||||
: [],
|
||||
imports: [
|
||||
{
|
||||
as: '__buildAssetsURL',
|
||||
@ -362,11 +370,20 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
// Register nuxt protection patterns
|
||||
nitroConfig.rollupConfig!.plugins = await nitroConfig.rollupConfig!.plugins || []
|
||||
nitroConfig.rollupConfig!.plugins = toArray(nitroConfig.rollupConfig!.plugins)
|
||||
|
||||
const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared))
|
||||
const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))
|
||||
const sharedPatterns = [/^#shared\//, new RegExp('^' + escapeRE(sharedDir)), new RegExp('^' + escapeRE(relativeSharedDir))]
|
||||
nitroConfig.rollupConfig!.plugins!.push(
|
||||
ImpoundPlugin.rollup({
|
||||
cwd: nuxt.options.rootDir,
|
||||
patterns: nuxtImportProtections(nuxt, { isNitro: true }),
|
||||
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/],
|
||||
include: sharedPatterns,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'shared' }),
|
||||
}),
|
||||
ImpoundPlugin.rollup({
|
||||
cwd: nuxt.options.rootDir,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'nitro-app' }),
|
||||
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/, ...sharedPatterns],
|
||||
}),
|
||||
)
|
||||
|
||||
|
@ -18,7 +18,6 @@ import type { DateString } from 'compatx'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||
import { ImpoundPlugin } from 'impound'
|
||||
import type { ImpoundOptions } from 'impound'
|
||||
import defu from 'defu'
|
||||
import { gt, satisfies } from 'semver'
|
||||
import { hasTTY, isCI } from 'std-env'
|
||||
@ -32,7 +31,7 @@ import { distDir, pkgDir } from '../dirs'
|
||||
import { version } from '../../package.json'
|
||||
import { scriptsStubsPreset } from '../imports/presets'
|
||||
import { resolveTypePath } from './utils/types'
|
||||
import { nuxtImportProtections } from './plugins/import-protection'
|
||||
import { createImportProtectionPatterns } from './plugins/import-protection'
|
||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||
import { TreeShakeComposablesPlugin } from './plugins/tree-shake'
|
||||
import { DevOnlyPlugin } from './plugins/dev-only'
|
||||
@ -249,16 +248,28 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
// Add plugin normalization plugin
|
||||
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
|
||||
|
||||
// shared folder import protection
|
||||
const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared))
|
||||
const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))
|
||||
const sharedPatterns = [/^#shared\//, new RegExp('^' + escapeRE(sharedDir)), new RegExp('^' + escapeRE(relativeSharedDir))]
|
||||
const sharedProtectionConfig = {
|
||||
cwd: nuxt.options.rootDir,
|
||||
include: sharedPatterns,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'shared' }),
|
||||
}
|
||||
addVitePlugin(() => ImpoundPlugin.vite(sharedProtectionConfig), { server: false })
|
||||
addWebpackPlugin(() => ImpoundPlugin.webpack(sharedProtectionConfig), { server: false })
|
||||
|
||||
// Add import protection
|
||||
const config: ImpoundOptions = {
|
||||
const nuxtProtectionConfig = {
|
||||
cwd: nuxt.options.rootDir,
|
||||
// Exclude top-level resolutions by plugins
|
||||
exclude: [join(nuxt.options.srcDir, 'index.html')],
|
||||
patterns: nuxtImportProtections(nuxt),
|
||||
exclude: [relative(nuxt.options.rootDir, join(nuxt.options.srcDir, 'index.html')), ...sharedPatterns],
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'nuxt-app' }),
|
||||
}
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...config, error: false }), { name: 'nuxt:import-protection' }), { client: false })
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...config, error: true }), { name: 'nuxt:import-protection' }), { server: false })
|
||||
addWebpackPlugin(() => ImpoundPlugin.webpack(config))
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...nuxtProtectionConfig, error: false }), { name: 'nuxt:import-protection' }), { client: false })
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...nuxtProtectionConfig, error: true }), { name: 'nuxt:import-protection' }), { server: false })
|
||||
addWebpackPlugin(() => ImpoundPlugin.webpack(nuxtProtectionConfig))
|
||||
|
||||
// add resolver for modules used in virtual files
|
||||
addVitePlugin(() => resolveDeepImportsPlugin(nuxt), { client: false })
|
||||
@ -565,6 +576,11 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
if (nuxt.options.experimental.emitRouteChunkError === 'automatic') {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/chunk-reload.client'))
|
||||
}
|
||||
// Add experimental immediate page reload support
|
||||
if (nuxt.options.experimental.emitRouteChunkError === 'automatic-immediate') {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/chunk-reload-immediate.client'))
|
||||
}
|
||||
|
||||
// Add experimental session restoration support
|
||||
if (nuxt.options.experimental.restoreState) {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/restore-state.client'))
|
||||
|
@ -9,12 +9,17 @@ interface ImportProtectionOptions {
|
||||
exclude?: Array<RegExp | string>
|
||||
}
|
||||
|
||||
export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: { isNitro?: boolean } = {}) => {
|
||||
interface NuxtImportProtectionOptions {
|
||||
context: 'nuxt-app' | 'nitro-app' | 'shared'
|
||||
}
|
||||
|
||||
export const createImportProtectionPatterns = (nuxt: { options: NuxtOptions }, options: NuxtImportProtectionOptions) => {
|
||||
const patterns: ImportProtectionOptions['patterns'] = []
|
||||
const context = contextFlags[options.context]
|
||||
|
||||
patterns.push([
|
||||
/^(nuxt|nuxt3|nuxt-nightly)$/,
|
||||
'`nuxt`, `nuxt3` or `nuxt-nightly` cannot be imported directly.' + (options.isNitro ? '' : ' Instead, import runtime Nuxt composables from `#app` or `#imports`.'),
|
||||
`\`nuxt\`, or \`nuxt-nightly\` cannot be imported directly in ${context}.` + (options.context === 'nuxt-app' ? ' Instead, import runtime Nuxt composables from `#app` or `#imports`.' : ''),
|
||||
])
|
||||
|
||||
patterns.push([
|
||||
@ -26,27 +31,33 @@ export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: {
|
||||
|
||||
for (const mod of nuxt.options.modules.filter(m => typeof m === 'string')) {
|
||||
patterns.push([
|
||||
new RegExp(`^${escapeRE(mod as string)}$`),
|
||||
new RegExp(`^${escapeRE(mod)}$`),
|
||||
'Importing directly from module entry-points is not allowed.',
|
||||
])
|
||||
}
|
||||
|
||||
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?runtime|types)/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
||||
patterns.push([i, 'This module cannot be imported' + (options.isNitro ? ' in server runtime.' : ' in the Vue part of your app.')])
|
||||
patterns.push([i, `This module cannot be imported in ${context}.`])
|
||||
}
|
||||
|
||||
if (options.isNitro) {
|
||||
if (options.context === 'nitro-app' || options.context === 'shared') {
|
||||
for (const i of ['#app', /^#build(\/|$)/]) {
|
||||
patterns.push([i, 'Vue app aliases are not allowed in server runtime.'])
|
||||
patterns.push([i, `Vue app aliases are not allowed in ${context}.`])
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.isNitro) {
|
||||
if (options.context === 'nuxt-app' || options.context === 'shared') {
|
||||
patterns.push([
|
||||
new RegExp(escapeRE(relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, nuxt.options.serverDir || 'server'))) + '\\/(api|routes|middleware|plugins)\\/'),
|
||||
'Importing from server is not allowed in the Vue part of your app.',
|
||||
`Importing from server is not allowed in ${context}.`,
|
||||
])
|
||||
}
|
||||
|
||||
return patterns
|
||||
}
|
||||
|
||||
const contextFlags = {
|
||||
'nitro-app': 'server runtime',
|
||||
'nuxt-app': 'the Vue part of your app',
|
||||
'shared': 'the #shared directory',
|
||||
} as const
|
||||
|
@ -106,6 +106,8 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
|
||||
|
||||
await nuxt.callHook('imports:context', ctx)
|
||||
|
||||
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
|
||||
|
||||
// composables/ dirs from all layers
|
||||
let composablesDirs: string[] = []
|
||||
if (options.scan) {
|
||||
@ -117,6 +119,12 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
|
||||
composablesDirs.push(resolve(layer.config.srcDir, 'composables'))
|
||||
composablesDirs.push(resolve(layer.config.srcDir, 'utils'))
|
||||
composablesDirs.push(resolve(layer.config.srcDir, 'directives'))
|
||||
|
||||
if (isNuxtV4) {
|
||||
composablesDirs.push(resolve(layer.config.rootDir, 'shared', 'utils'))
|
||||
composablesDirs.push(resolve(layer.config.rootDir, 'shared', 'types'))
|
||||
}
|
||||
|
||||
for (const dir of (layer.config.imports?.dirs ?? [])) {
|
||||
if (!dir) {
|
||||
continue
|
||||
|
@ -109,6 +109,10 @@ const granularAppPresets: InlinePreset[] = [
|
||||
imports: ['useRouteAnnouncer'],
|
||||
from: '#app/composables/route-announcer',
|
||||
},
|
||||
{
|
||||
imports: ['useRuntimeHook'],
|
||||
from: '#app/composables/runtime-hook',
|
||||
},
|
||||
]
|
||||
|
||||
export const scriptsStubsPreset = {
|
||||
@ -216,9 +220,6 @@ const vuePreset = defineUnimportPreset({
|
||||
'hasInjectionContext',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'defineModel',
|
||||
'defineOptions',
|
||||
'defineSlots',
|
||||
'mergeModels',
|
||||
'toValue',
|
||||
'useModel',
|
||||
|
@ -507,13 +507,14 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
|
||||
const route: NormalizedRoute = {
|
||||
path: serializeRouteValue(page.path),
|
||||
props: serializeRouteValue(page.props),
|
||||
name: serializeRouteValue(page.name),
|
||||
meta: serializeRouteValue(metaFiltered, skipMeta),
|
||||
alias: serializeRouteValue(toArray(page.alias), skipAlias),
|
||||
redirect: serializeRouteValue(page.redirect),
|
||||
}
|
||||
|
||||
for (const key of ['path', 'name', 'meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
|
||||
for (const key of ['path', 'props', 'name', 'meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
|
||||
if (route[key] === undefined) {
|
||||
delete route[key]
|
||||
}
|
||||
@ -542,7 +543,7 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
const metaRoute: NormalizedRoute = {
|
||||
name: `${metaImportName}?.name ?? ${route.name}`,
|
||||
path: `${metaImportName}?.path ?? ${route.path}`,
|
||||
props: `${metaImportName}?.props ?? false`,
|
||||
props: `${metaImportName}?.props ?? ${route.props ?? false}`,
|
||||
meta: `${metaImportName} || {}`,
|
||||
alias: `${metaImportName}?.alias || []`,
|
||||
redirect: `${metaImportName}?.redirect`,
|
||||
|
@ -36,7 +36,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "page-with-props"",
|
||||
"path": "mockMeta?.path ?? "/page-with-props"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"props": "mockMeta?.props ?? true",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
|
@ -29,6 +29,7 @@
|
||||
"component": "() => import("pages/page-with-props.vue")",
|
||||
"name": ""page-with-props"",
|
||||
"path": ""/page-with-props"",
|
||||
"props": "true",
|
||||
},
|
||||
],
|
||||
"should allow pages with `:` in their path": [
|
||||
|
@ -86,7 +86,10 @@ const excludedVueHelpers = [
|
||||
// Already globally registered
|
||||
'defineEmits',
|
||||
'defineExpose',
|
||||
'defineModel',
|
||||
'defineOptions',
|
||||
'defineProps',
|
||||
'defineSlots',
|
||||
'withDefaults',
|
||||
'stop',
|
||||
//
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { normalize } from 'pathe'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ImpoundPlugin } from 'impound'
|
||||
import { nuxtImportProtections } from '../src/core/plugins/import-protection'
|
||||
import { createImportProtectionPatterns } from '../src/core/plugins/import-protection'
|
||||
import type { NuxtOptions } from '../schema'
|
||||
|
||||
const testsToTriggerOn = [
|
||||
@ -28,7 +28,7 @@ const testsToTriggerOn = [
|
||||
|
||||
describe('import protection', () => {
|
||||
it.each(testsToTriggerOn)('should protect %s', async (id, importer, isProtected) => {
|
||||
const result = await transformWithImportProtection(id, importer)
|
||||
const result = await transformWithImportProtection(id, importer, 'nuxt-app')
|
||||
if (!isProtected) {
|
||||
expect(result).toBeNull()
|
||||
} else {
|
||||
@ -38,16 +38,16 @@ describe('import protection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
const transformWithImportProtection = (id: string, importer: string) => {
|
||||
const transformWithImportProtection = (id: string, importer: string, context: 'nitro-app' | 'nuxt-app' | 'shared') => {
|
||||
const plugin = ImpoundPlugin.rollup({
|
||||
cwd: '/root',
|
||||
patterns: nuxtImportProtections({
|
||||
patterns: createImportProtectionPatterns({
|
||||
options: {
|
||||
modules: ['some-nuxt-module'],
|
||||
srcDir: '/root/src/',
|
||||
serverDir: '/root/src/server',
|
||||
} satisfies Partial<NuxtOptions> as NuxtOptions,
|
||||
}),
|
||||
}, { context }),
|
||||
})
|
||||
|
||||
return (plugin as any).resolveId.call({ error: () => {} }, id, importer)
|
||||
|
@ -45,7 +45,7 @@
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"hash-sum": "^2.0.0",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"knitwork": "^1.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"magic-string": "^0.30.12",
|
||||
@ -64,7 +64,7 @@
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin": "^1.15.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
@ -81,7 +81,7 @@
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.3",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
|
@ -39,16 +39,16 @@
|
||||
"@types/file-loader": "5.0.4",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/sass-loader": "8.0.9",
|
||||
"@unhead/schema": "1.11.10",
|
||||
"@unhead/schema": "1.11.11",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "4.0.1",
|
||||
"@vue/compiler-core": "3.5.12",
|
||||
"@vue/compiler-sfc": "3.5.12",
|
||||
"@vue/language-core": "2.1.6",
|
||||
"@vue/language-core": "2.1.10",
|
||||
"esbuild-loader": "4.2.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"ignore": "6.0.2",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"ofetch": "1.4.1",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"unctx": "2.3.1",
|
||||
@ -58,7 +58,7 @@
|
||||
"vue-bundle-renderer": "2.1.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue-router": "4.4.5",
|
||||
"webpack": "5.95.0",
|
||||
"webpack": "5.96.1",
|
||||
"webpack-dev-middleware": "7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -355,6 +355,11 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
plugins: 'plugins',
|
||||
|
||||
/**
|
||||
* The shared directory. This directory is shared between the app and the server.
|
||||
*/
|
||||
shared: 'shared',
|
||||
|
||||
/**
|
||||
* The directory containing your static files, which will be directly accessible via the Nuxt server
|
||||
* and copied across into your `dist` folder when your app is generated.
|
||||
@ -424,12 +429,13 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
alias: {
|
||||
$resolve: async (val: Record<string, string>, get): Promise<Record<string, string>> => {
|
||||
const [srcDir, rootDir, assetsDir, publicDir, buildDir] = await Promise.all([get('srcDir'), get('rootDir'), get('dir.assets'), get('dir.public'), get('buildDir')]) as [string, string, string, string, string]
|
||||
const [srcDir, rootDir, assetsDir, publicDir, buildDir, sharedDir] = await Promise.all([get('srcDir'), get('rootDir'), get('dir.assets'), get('dir.public'), get('buildDir'), get('dir.shared')]) as [string, string, string, string, string, string]
|
||||
return {
|
||||
'~': srcDir,
|
||||
'@': srcDir,
|
||||
'~~': rootDir,
|
||||
'@@': rootDir,
|
||||
'#shared': resolve(rootDir, sharedDir),
|
||||
[basename(assetsDir)]: resolve(srcDir, assetsDir),
|
||||
[basename(publicDir)]: resolve(srcDir, publicDir),
|
||||
'#build': buildDir,
|
||||
|
@ -116,13 +116,16 @@ export default defineUntypedSchema({
|
||||
* Emit `app:chunkError` hook when there is an error loading vite/webpack
|
||||
* chunks.
|
||||
*
|
||||
* By default, Nuxt will also perform a hard reload of the new route
|
||||
* when a chunk fails to load when navigating to a new route.
|
||||
* By default, Nuxt will also perform a reload of the new route
|
||||
* when a chunk fails to load when navigating to a new route (`automatic`).
|
||||
*
|
||||
* Setting `automatic-immediate` will lead Nuxt to perform a reload of the current route
|
||||
* right when a chunk fails to load (instead of waiting for navigation).
|
||||
*
|
||||
* You can disable automatic handling by setting this to `false`, or handle
|
||||
* chunk errors manually by setting it to `manual`.
|
||||
* @see [Nuxt PR #19038](https://github.com/nuxt/nuxt/pull/19038)
|
||||
* @type {false | 'manual' | 'automatic'}
|
||||
* @type {false | 'manual' | 'automatic' | 'automatic-immediate'}
|
||||
*/
|
||||
emitRouteChunkError: {
|
||||
$resolve: (val) => {
|
||||
|
@ -22,13 +22,13 @@
|
||||
"critters": "0.0.25",
|
||||
"html-validate": "8.24.2",
|
||||
"htmlnano": "2.1.1",
|
||||
"jiti": "2.3.3",
|
||||
"jiti": "2.4.0",
|
||||
"knitwork": "1.1.0",
|
||||
"pathe": "1.1.2",
|
||||
"prettier": "3.3.3",
|
||||
"scule": "1.3.0",
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyglobby": "0.2.9",
|
||||
"tinyglobby": "0.2.10",
|
||||
"unocss": "0.63.6",
|
||||
"vite": "5.4.10"
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@types/clear": "0.1.4",
|
||||
"@types/estree": "1.0.6",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.3",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
@ -47,7 +47,7 @@
|
||||
"externality": "^1.0.2",
|
||||
"get-port-please": "^3.1.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"mlly": "^1.7.2",
|
||||
@ -61,9 +61,9 @@
|
||||
"strip-literal": "^2.1.0",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin": "^1.15.0",
|
||||
"vite": "^5.4.10",
|
||||
"vite-node": "^2.1.3",
|
||||
"vite-node": "^2.1.4",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vue-bundle-renderer": "^2.1.1"
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||
import type { ViteConfig } from '@nuxt/schema'
|
||||
import defu from 'defu'
|
||||
import type { Nitro } from 'nitro/types'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import type { ViteBuildContext } from './vite'
|
||||
import { createViteLogger } from './utils/logger'
|
||||
import { initViteNodeServer } from './vite-node'
|
||||
@ -80,7 +81,13 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
ssr: true,
|
||||
rollupOptions: {
|
||||
input: { server: entry },
|
||||
external: ['nitro/runtime', '#internal/nuxt/paths', '#internal/nuxt/app-config'],
|
||||
external: [
|
||||
'nitro/runtime',
|
||||
'#internal/nuxt/paths',
|
||||
'#internal/nuxt/app-config',
|
||||
'#shared',
|
||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared)))),
|
||||
],
|
||||
output: {
|
||||
entryFileNames: '[name].mjs',
|
||||
format: 'module',
|
||||
|
@ -1,9 +1,13 @@
|
||||
import type { ExternalsOptions } from 'externality'
|
||||
import { ExternalsDefaults, isExternal } from 'externality'
|
||||
import type { ViteDevServer } from 'vite'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Nuxt } from 'nuxt/schema'
|
||||
import { resolve } from 'pathe'
|
||||
import { toArray } from '.'
|
||||
|
||||
export function createIsExternal (viteServer: ViteDevServer, rootDir: string, modulesDirs?: string[]) {
|
||||
export function createIsExternal (viteServer: ViteDevServer, nuxt: Nuxt) {
|
||||
const externalOpts: ExternalsOptions = {
|
||||
inline: [
|
||||
/virtual:/,
|
||||
@ -16,15 +20,17 @@ export function createIsExternal (viteServer: ViteDevServer, rootDir: string, mo
|
||||
),
|
||||
],
|
||||
external: [
|
||||
'#shared',
|
||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))),
|
||||
...(viteServer.config.ssr.external as string[]) || [],
|
||||
/node_modules/,
|
||||
],
|
||||
resolve: {
|
||||
modules: modulesDirs,
|
||||
modules: nuxt.options.modulesDir,
|
||||
type: 'module',
|
||||
extensions: ['.ts', '.js', '.json', '.vue', '.mjs', '.jsx', '.tsx', '.wasm'],
|
||||
},
|
||||
}
|
||||
|
||||
return (id: string) => isExternal(id, rootDir, externalOpts)
|
||||
return (id: string) => isExternal(id, nuxt.options.rootDir, externalOpts)
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ function createViteNodeApp (ctx: ViteBuildContext, invalidates: Set<string> = ne
|
||||
},
|
||||
})
|
||||
|
||||
const isExternal = createIsExternal(viteServer, ctx.nuxt.options.rootDir, ctx.nuxt.options.modulesDir)
|
||||
const isExternal = createIsExternal(viteServer, ctx.nuxt)
|
||||
node.shouldExternalize = async (id: string) => {
|
||||
const result = await isExternal(id)
|
||||
if (result?.external) {
|
||||
|
@ -44,12 +44,12 @@
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"hash-sum": "^2.0.0",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"knitwork": "^1.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"magic-string": "^0.30.12",
|
||||
"memfs": "^4.14.0",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"mlly": "^1.7.2",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
@ -64,11 +64,11 @@
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin": "^1.15.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack": "^5.96.1",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-dev-middleware": "^7.4.2",
|
||||
"webpack-hot-middleware": "^2.26.1",
|
||||
@ -82,7 +82,7 @@
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.3",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isAbsolute } from 'pathe'
|
||||
import { isAbsolute, resolve } from 'pathe'
|
||||
import ForkTSCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
|
||||
import { logger } from '@nuxt/kit'
|
||||
import type { WebpackConfigContext } from '../utils/config'
|
||||
@ -53,7 +53,11 @@ function serverStandalone (ctx: WebpackConfigContext) {
|
||||
'#',
|
||||
...ctx.options.build.transpile,
|
||||
]
|
||||
const external = ['nitro/runtime']
|
||||
const external = [
|
||||
'nitro/runtime',
|
||||
'#shared',
|
||||
resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared),
|
||||
]
|
||||
if (!ctx.nuxt.options.dev) {
|
||||
external.push('#internal/nuxt/paths', '#internal/nuxt/app-config')
|
||||
}
|
||||
|
1789
pnpm-lock.yaml
1789
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -2374,7 +2374,7 @@ describe('component islands', () => {
|
||||
"link": [],
|
||||
"style": [
|
||||
{
|
||||
"innerHTML": "pre[data-v-xxxxx]{color:blue}",
|
||||
"innerHTML": "pre[data-v-xxxxx]{color:#00f}",
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -2743,7 +2743,11 @@ function normaliseIslandResult (result: NuxtIslandResponse) {
|
||||
for (const style of result.head.style) {
|
||||
if (typeof style !== 'string') {
|
||||
if (style.innerHTML) {
|
||||
style.innerHTML = (style.innerHTML as string).replace(/data-v-[a-z0-9]+/g, 'data-v-xxxxx')
|
||||
style.innerHTML =
|
||||
(style.innerHTML as string)
|
||||
.replace(/data-v-[a-z0-9]+/g, 'data-v-xxxxx')
|
||||
// Vite 6 enables CSS minify by default for SSR
|
||||
.replace(/blue/, '#00f')
|
||||
}
|
||||
if (style.key) {
|
||||
style.key = style.key.replace(/-[a-z0-9]+$/i, '')
|
||||
|
@ -21,8 +21,8 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const [clientStats, clientStatsInlined] = await Promise.all((['.output', '.output-inline'])
|
||||
.map(outputDir => analyzeSizes(['**/*.js'], join(rootDir, outputDir, 'public'))))
|
||||
|
||||
expect.soft(roundToKilobytes(clientStats!.totalBytes)).toMatchInlineSnapshot(`"115k"`)
|
||||
expect.soft(roundToKilobytes(clientStatsInlined!.totalBytes)).toMatchInlineSnapshot(`"115k"`)
|
||||
expect.soft(roundToKilobytes(clientStats!.totalBytes)).toMatchInlineSnapshot(`"119k"`)
|
||||
expect.soft(roundToKilobytes(clientStatsInlined!.totalBytes)).toMatchInlineSnapshot(`"119k"`)
|
||||
|
||||
const files = new Set([...clientStats!.files, ...clientStatsInlined!.files].map(f => f.replace(/\..*\.js/, '.js')))
|
||||
|
||||
|
@ -20,6 +20,7 @@ import { callOnce } from '#app/composables/once'
|
||||
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
||||
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||
import { encodeURL, resolveRouteObject } from '#app/composables/router'
|
||||
import { useRuntimeHook } from '#app/composables/runtime-hook'
|
||||
|
||||
registerEndpoint('/api/test', defineEventHandler(event => ({
|
||||
method: event.method,
|
||||
@ -93,6 +94,7 @@ describe('composables', () => {
|
||||
'abortNavigation',
|
||||
'setPageLayout',
|
||||
'defineNuxtComponent',
|
||||
'useRuntimeHook',
|
||||
]
|
||||
const skippedComposables: string[] = [
|
||||
'addRouteMiddleware',
|
||||
@ -577,6 +579,36 @@ describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', (
|
||||
})
|
||||
})
|
||||
|
||||
describe('useRuntimeHook', () => {
|
||||
it('types work', () => {
|
||||
// @ts-expect-error should not allow unknown hooks
|
||||
useRuntimeHook('test', () => {})
|
||||
useRuntimeHook('app:beforeMount', (_app) => {
|
||||
// @ts-expect-error argument should be typed
|
||||
_app = 'test'
|
||||
})
|
||||
})
|
||||
|
||||
it('should call hooks', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
let called = 1
|
||||
const wrapper = await mountSuspended(defineNuxtComponent({
|
||||
setup () {
|
||||
useRuntimeHook('test-hook' as any, () => {
|
||||
called++
|
||||
})
|
||||
},
|
||||
render: () => h('div', 'hi there'),
|
||||
}))
|
||||
expect(called).toBe(1)
|
||||
await nuxtApp.callHook('test-hook' as any)
|
||||
expect(called).toBe(2)
|
||||
wrapper.unmount()
|
||||
await nuxtApp.callHook('test-hook' as any)
|
||||
expect(called).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('routing utilities: `navigateTo`', () => {
|
||||
it('navigateTo should disallow navigation to external URLs by default', () => {
|
||||
expect(() => navigateTo('https://test.com')).toThrowErrorMatchingInlineSnapshot('[Error: Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.]')
|
||||
|
Loading…
Reference in New Issue
Block a user