mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-22 11:22:43 +00:00
Merge branch 'main' into feat/unhead-v2
This commit is contained in:
commit
0ce81cba96
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
@ -188,6 +188,31 @@ jobs:
|
||||
- name: Test (runtime unit)
|
||||
run: pnpm test:runtime
|
||||
|
||||
test-size:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
|
||||
- name: Check bundle size
|
||||
run: pnpm vitest run bundle
|
||||
|
||||
test-fixtures:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs:
|
||||
@ -253,7 +278,7 @@ jobs:
|
||||
TEST_MANIFEST: ${{ matrix.manifest }}
|
||||
TEST_CONTEXT: ${{ matrix.context }}
|
||||
TEST_PAYLOAD: ${{ matrix.payload }}
|
||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
|
||||
SKIP_BUNDLE_SIZE: true
|
||||
|
||||
- uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
||||
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
||||
|
@ -1,8 +1,12 @@
|
||||
// @ts-check
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { declare } from '@babel/helper-plugin-utils'
|
||||
import { types as t } from '@babel/core'
|
||||
|
||||
const metricsPath = fileURLToPath(new URL('../../debug-timings.json', import.meta.url))
|
||||
|
||||
// inlined from https://github.com/danielroe/errx
|
||||
function captureStackTrace () {
|
||||
const IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[a-z]:[/\\]/i
|
||||
@ -46,6 +50,7 @@ function captureStackTrace () {
|
||||
}
|
||||
|
||||
export const leading = `
|
||||
import { writeFileSync as ____writeFileSync } from 'node:fs'
|
||||
const ___captureStackTrace = ${captureStackTrace.toString()};
|
||||
globalThis.___calls ||= {};
|
||||
globalThis.___timings ||= {};
|
||||
@ -55,6 +60,16 @@ function onExit () {
|
||||
if (globalThis.___logged) { return }
|
||||
globalThis.___logged = true
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
____writeFileSync(metricsPath, JSON.stringify(Object.fromEntries(Object.entries(globalThis.___timings).map(([name, time]) => [
|
||||
name,
|
||||
{
|
||||
time: Number(Number(time).toFixed(2)),
|
||||
calls: globalThis.___calls[name],
|
||||
callers: globalThis.___callers[name] ? Object.fromEntries(Object.entries(globalThis.___callers[name]).map(([name, count]) => [name.trim(), count]).sort((a, b) => typeof b[0] === 'string' && typeof a[0] === 'string' ? a[0].localeCompare(b[0]) : 0)) : undefined,
|
||||
},
|
||||
]).sort((a, b) => typeof b[0] === 'string' && typeof a[0] === 'string' ? a[0].localeCompare(b[0]) : 0)), null, 2))
|
||||
|
||||
// worst by total time
|
||||
const timings = Object.entries(globalThis.___timings)
|
||||
|
||||
@ -93,7 +108,7 @@ function onExit () {
|
||||
console.table(topFunctionsAverageTime)
|
||||
}
|
||||
|
||||
export const trailing = `process.on("exit", ${onExit.toString()})`
|
||||
export const trailing = `process.on("exit", ${onExit.toString().replace('metricsPath', JSON.stringify(metricsPath))})`
|
||||
|
||||
/** @param {string} functionName */
|
||||
export function generateInitCode (functionName) {
|
||||
|
@ -15,6 +15,8 @@ declare global {
|
||||
var ___calls: Record<string, number>
|
||||
// eslint-disable-next-line no-var
|
||||
var ___callers: Record<string, number>
|
||||
// eslint-disable-next-line no-var
|
||||
var ____writeFileSync: typeof import('fs').writeFileSync
|
||||
}
|
||||
|
||||
export function AnnotateFunctionTimingsPlugin () {
|
||||
@ -29,7 +31,7 @@ export function AnnotateFunctionTimingsPlugin () {
|
||||
walk(ast as Node, {
|
||||
enter (node) {
|
||||
if (node.type === 'FunctionDeclaration' && node.id && node.id.name) {
|
||||
const functionName = node.id.name ? `${node.id.name} (${id.match(/\/packages\/([^/]+)\//)?.[1]})` : ''
|
||||
const functionName = node.id.name ? `${node.id.name} (${id.match(/[\\/]packages[\\/]([^/]+)[\\/]/)?.[1]})` : ''
|
||||
const start = (node.body as Node & { start: number, end: number }).start
|
||||
const end = (node.body as Node & { start: number, end: number }).end
|
||||
|
||||
|
@ -51,3 +51,26 @@ The content of the default slot will be tree-shaken out of the server build. (Th
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Accessing HTML Elements
|
||||
|
||||
Components inside `<ClientOnly>` are rendered only after being mounted. To access the rendered elements in the DOM, you can watch a template ref:
|
||||
|
||||
```vue [pages/example.vue]
|
||||
<script setup lang="ts">
|
||||
const nuxtWelcomeRef = ref()
|
||||
|
||||
// The watch will be triggered when the component is available
|
||||
watch(nuxtWelcomeRef, () => {
|
||||
console.log('<NuxtWelcome /> mounted')
|
||||
}, { once: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<NuxtWelcome ref="nuxtWelcomeRef" />
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
|
@ -24,6 +24,8 @@ This is useful for code that should be executed only once, such as logging an ev
|
||||
|
||||
## Usage
|
||||
|
||||
The default mode of `callOnce` is to run code only once. For example, if the code runs on the server it won't run again on the client. It also won't run again if you `callOnce` more than once on the client, for example by navigating back to this page.
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const websiteConfig = useState('config')
|
||||
@ -35,6 +37,23 @@ await callOnce(async () => {
|
||||
</script>
|
||||
```
|
||||
|
||||
It is also possible to run on every navigation while still avoiding the initial server/client double load. For this, it is possible to use the `navigation` mode:
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const websiteConfig = useState('config')
|
||||
|
||||
await callOnce(async () => {
|
||||
console.log('This will only be logged once and then on every client side navigation')
|
||||
websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
|
||||
}, { mode: 'navigation' })
|
||||
</script>
|
||||
```
|
||||
|
||||
::important
|
||||
`navigation` mode is available since [Nuxt v3.15](/blog/v3-15).
|
||||
::
|
||||
|
||||
::tip{to="/docs/getting-started/state-management#usage-with-pinia"}
|
||||
`callOnce` is useful in combination with the [Pinia module](/modules/pinia) to call store actions.
|
||||
::
|
||||
@ -52,9 +71,22 @@ Note that `callOnce` doesn't return anything. You should use [`useAsyncData`](/d
|
||||
## Type
|
||||
|
||||
```ts
|
||||
callOnce(fn?: () => any | Promise<any>): Promise<void>
|
||||
callOnce(key: string, fn?: () => any | Promise<any>): Promise<void>
|
||||
callOnce (key?: string, fn?: (() => any | Promise<any>), options?: CallOnceOptions): Promise<void>
|
||||
callOnce(fn?: (() => any | Promise<any>), options?: CallOnceOptions): Promise<void>
|
||||
|
||||
type CallOnceOptions = {
|
||||
/**
|
||||
* Execution mode for the callOnce function
|
||||
* @default 'render'
|
||||
*/
|
||||
mode?: 'navigation' | 'render'
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `key`: A unique key ensuring that the code is run once. If you do not provide a key, then a key that is unique to the file and line number of the instance of `callOnce` will be generated for you.
|
||||
- `fn`: The function to run once. This function can also return a `Promise` and a value.
|
||||
- `options`: Setup the mode, either to re-execute on navigation (`navigation`) or just once for the lifetime of the app (`render`). Defaults to `render`.
|
||||
- `render`: Executes once during initial render (either SSR or CSR) - Default mode
|
||||
- `navigation`: Executes once during initial render and once per subsequent client-side navigation
|
||||
|
@ -65,7 +65,7 @@
|
||||
"unbuild": "3.3.1",
|
||||
"unhead": "2.0.0-alpha.7",
|
||||
"unimport": "3.14.6",
|
||||
"vite": "6.0.10",
|
||||
"vite": "6.0.11",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -50,11 +50,11 @@
|
||||
"untyped": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/core": "1.1.8",
|
||||
"@rspack/core": "1.2.0",
|
||||
"@types/semver": "7.5.8",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||
"unbuild": "3.3.1",
|
||||
"vite": "6.0.10",
|
||||
"vite": "6.0.11",
|
||||
"vitest": "3.0.2",
|
||||
"webpack": "5.97.1"
|
||||
},
|
||||
|
@ -14,6 +14,11 @@ const builderMap = {
|
||||
'@nuxt/webpack-builder': 'webpack',
|
||||
}
|
||||
|
||||
export function checkNuxtVersion (version: string, nuxt: Nuxt = useNuxt()) {
|
||||
const nuxtVersion = getNuxtVersion(nuxt)
|
||||
return satisfies(normalizeSemanticVersion(nuxtVersion), version, { includePrerelease: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Check version constraints and return incompatibility issues as an array
|
||||
*/
|
||||
@ -23,7 +28,7 @@ export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nu
|
||||
// Nuxt version check
|
||||
if (constraints.nuxt) {
|
||||
const nuxtVersion = getNuxtVersion(nuxt)
|
||||
if (!satisfies(normalizeSemanticVersion(nuxtVersion), constraints.nuxt, { includePrerelease: true })) {
|
||||
if (!checkNuxtVersion(constraints.nuxt, nuxt)) {
|
||||
issues.push({
|
||||
name: 'nuxt',
|
||||
message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``,
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { kebabCase, pascalCase } from 'scule'
|
||||
import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
import { assertNuxtCompatibility } from './compatibility'
|
||||
import { checkNuxtVersion } from './compatibility'
|
||||
import { logger } from './logger'
|
||||
import { MODE_RE } from './utils'
|
||||
|
||||
/**
|
||||
* Register a directory to be scanned for components and imported only when used.
|
||||
*/
|
||||
export async function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
||||
export function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
||||
const nuxt = useNuxt()
|
||||
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
||||
if (!checkNuxtVersion('>=2.13', nuxt)) {
|
||||
throw new Error(`\`addComponentsDir\` requires Nuxt 2.13 or higher.`)
|
||||
}
|
||||
nuxt.options.components ||= []
|
||||
dir.priority ||= 0
|
||||
nuxt.hook('components:dirs', (dirs) => { dirs[opts.prepend ? 'unshift' : 'push'](dir) })
|
||||
@ -23,9 +25,12 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial<E
|
||||
/**
|
||||
* Register a component by its name and filePath.
|
||||
*/
|
||||
export async function addComponent (opts: AddComponentOptions) {
|
||||
export function addComponent (opts: AddComponentOptions) {
|
||||
const nuxt = useNuxt()
|
||||
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
||||
if (!checkNuxtVersion('>=2.13', nuxt)) {
|
||||
throw new Error(`\`addComponent\` requires Nuxt 2.13 or higher.`)
|
||||
}
|
||||
|
||||
nuxt.options.components ||= []
|
||||
|
||||
if (!opts.mode) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { existsSync, promises as fsp } from 'node:fs'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'pathe'
|
||||
import { globby } from 'globby'
|
||||
@ -38,97 +38,30 @@ export interface ResolvePathOptions {
|
||||
* If path could not be resolved, normalized input path will be returned
|
||||
*/
|
||||
export async function resolvePath (path: string, opts: ResolvePathOptions = {}): Promise<string> {
|
||||
// Always normalize input
|
||||
const _path = path
|
||||
path = normalize(path)
|
||||
const res = await _resolvePathGranularly(path, opts)
|
||||
|
||||
// Fast return if the path exists
|
||||
if (isAbsolute(path)) {
|
||||
if (opts?.virtual && existsInVFS(path)) {
|
||||
return path
|
||||
}
|
||||
if (existsSync(path) && !(await isDirectory(path))) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// Use current nuxt options
|
||||
const nuxt = tryUseNuxt()
|
||||
const cwd = opts.cwd || (nuxt ? nuxt.options.rootDir : process.cwd())
|
||||
const extensions = opts.extensions || (nuxt ? nuxt.options.extensions : ['.ts', '.mjs', '.cjs', '.json'])
|
||||
const modulesDir = nuxt ? nuxt.options.modulesDir : []
|
||||
|
||||
// Resolve aliases
|
||||
path = resolveAlias(path)
|
||||
|
||||
// Resolve relative to cwd
|
||||
if (!isAbsolute(path)) {
|
||||
path = resolve(cwd, path)
|
||||
}
|
||||
|
||||
// Check if resolvedPath is a file
|
||||
if (opts?.virtual && existsInVFS(path, nuxt)) {
|
||||
return path
|
||||
}
|
||||
|
||||
let _isDir = false
|
||||
if (existsSync(path)) {
|
||||
_isDir = await isDirectory(path)
|
||||
if (!_isDir) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// Check possible extensions
|
||||
for (const ext of extensions) {
|
||||
// path.[ext]
|
||||
const pathWithExt = path + ext
|
||||
if (opts?.virtual && existsInVFS(pathWithExt, nuxt)) {
|
||||
return pathWithExt
|
||||
}
|
||||
if (existsSync(pathWithExt)) {
|
||||
return pathWithExt
|
||||
}
|
||||
// path/index.[ext]
|
||||
const pathWithIndex = join(path, 'index' + ext)
|
||||
if (opts?.virtual && existsInVFS(pathWithIndex, nuxt)) {
|
||||
return pathWithIndex
|
||||
}
|
||||
if (_isDir && existsSync(pathWithIndex)) {
|
||||
return pathWithIndex
|
||||
}
|
||||
}
|
||||
|
||||
// Try to resolve as module id
|
||||
const resolveModulePath = await _resolvePath(_path, { url: [cwd, ...modulesDir] }).catch(() => null)
|
||||
if (resolveModulePath) {
|
||||
return resolveModulePath
|
||||
if (res.type === 'file') {
|
||||
return res.path
|
||||
}
|
||||
|
||||
// Return normalized input
|
||||
return opts.fallbackToOriginal ? _path : path
|
||||
return opts.fallbackToOriginal ? path : res.path
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve first existing file in paths
|
||||
*/
|
||||
export async function findPath (paths: string | string[], opts?: ResolvePathOptions, pathType: 'file' | 'dir' = 'file'): Promise<string | null> {
|
||||
const nuxt = opts?.virtual ? tryUseNuxt() : undefined
|
||||
|
||||
for (const path of toArray(paths)) {
|
||||
const rPath = await resolvePath(path, opts)
|
||||
const res = await _resolvePathGranularly(path, opts)
|
||||
|
||||
// Check VFS
|
||||
if (opts?.virtual && existsInVFS(rPath, nuxt)) {
|
||||
return rPath
|
||||
if (!res.type || (pathType && res.type !== pathType)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check file system
|
||||
if (await existsSensitive(rPath)) {
|
||||
const _isDir = await isDirectory(rPath)
|
||||
if (!pathType || (pathType === 'file' && !_isDir) || (pathType === 'dir' && _isDir)) {
|
||||
return rPath
|
||||
}
|
||||
if (res.virtual || await existsSensitive(res.path)) {
|
||||
return res.path
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -186,15 +119,106 @@ export async function resolveNuxtModule (base: string, paths: string[]): Promise
|
||||
|
||||
// --- Internal ---
|
||||
|
||||
async function existsSensitive (path: string) {
|
||||
if (!existsSync(path)) { return false }
|
||||
const dirFiles = await fsp.readdir(dirname(path))
|
||||
return dirFiles.includes(basename(path))
|
||||
interface PathResolution {
|
||||
path: string
|
||||
type?: 'file' | 'dir'
|
||||
virtual?: boolean
|
||||
}
|
||||
|
||||
// Usage note: We assume path existence is already ensured
|
||||
async function isDirectory (path: string) {
|
||||
return (await fsp.lstat(path)).isDirectory()
|
||||
async function _resolvePathType (path: string, opts: ResolvePathOptions = {}, skipFs = false): Promise<PathResolution | undefined> {
|
||||
if (opts?.virtual && existsInVFS(path)) {
|
||||
return {
|
||||
path,
|
||||
type: 'file',
|
||||
virtual: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (skipFs) {
|
||||
return
|
||||
}
|
||||
|
||||
const fd = await fsp.open(path, 'r').catch(() => null)
|
||||
try {
|
||||
const stats = await fd?.stat()
|
||||
if (stats) {
|
||||
return {
|
||||
path,
|
||||
type: stats.isFile() ? 'file' : 'dir',
|
||||
virtual: false,
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fd?.close()
|
||||
}
|
||||
}
|
||||
|
||||
async function _resolvePathGranularly (path: string, opts: ResolvePathOptions = {}): Promise<PathResolution> {
|
||||
// Always normalize input
|
||||
const _path = path
|
||||
path = normalize(path)
|
||||
|
||||
// Fast return if the path exists
|
||||
if (isAbsolute(path)) {
|
||||
const res = await _resolvePathType(path, opts)
|
||||
if (res && res.type === 'file') {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// Use current nuxt options
|
||||
const nuxt = tryUseNuxt()
|
||||
const cwd = opts.cwd || (nuxt ? nuxt.options.rootDir : process.cwd())
|
||||
const extensions = opts.extensions || (nuxt ? nuxt.options.extensions : ['.ts', '.mjs', '.cjs', '.json'])
|
||||
const modulesDir = nuxt ? nuxt.options.modulesDir : []
|
||||
|
||||
// Resolve aliases
|
||||
path = resolveAlias(path)
|
||||
|
||||
// Resolve relative to cwd
|
||||
if (!isAbsolute(path)) {
|
||||
path = resolve(cwd, path)
|
||||
}
|
||||
|
||||
const res = await _resolvePathType(path, opts)
|
||||
if (res && res.type === 'file') {
|
||||
return res
|
||||
}
|
||||
|
||||
// Check possible extensions
|
||||
for (const ext of extensions) {
|
||||
// path.[ext]
|
||||
const extPath = await _resolvePathType(path + ext, opts)
|
||||
if (extPath && extPath.type === 'file') {
|
||||
return extPath
|
||||
}
|
||||
|
||||
// path/index.[ext]
|
||||
const indexPath = await _resolvePathType(join(path, 'index' + ext), opts, res?.type !== 'dir' /* skip checking if parent is not a directory */)
|
||||
if (indexPath && indexPath.type === 'file') {
|
||||
return indexPath
|
||||
}
|
||||
}
|
||||
|
||||
// Try to resolve as module id
|
||||
const resolvedModulePath = await _resolvePath(_path, { url: [cwd, ...modulesDir] }).catch(() => null)
|
||||
if (resolvedModulePath) {
|
||||
return {
|
||||
path: resolvedModulePath,
|
||||
type: 'file',
|
||||
virtual: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Return normalized input
|
||||
return {
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
async function existsSensitive (path: string) {
|
||||
const dirFiles = await fsp.readdir(dirname(path)).catch(() => null)
|
||||
return dirFiles && dirFiles.includes(basename(path))
|
||||
}
|
||||
|
||||
function existsInVFS (path: string, nuxt = tryUseNuxt()) {
|
||||
|
@ -117,7 +117,7 @@
|
||||
"unenv": "^1.10.0",
|
||||
"unimport": "^3.14.6",
|
||||
"unplugin": "^2.1.2",
|
||||
"unplugin-vue-router": "^0.10.9",
|
||||
"unplugin-vue-router": "^0.11.0",
|
||||
"unstorage": "^1.14.4",
|
||||
"untyped": "^1.5.2",
|
||||
"vue": "^3.5.13",
|
||||
@ -132,7 +132,7 @@
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
"unbuild": "3.3.1",
|
||||
"vite": "6.0.10",
|
||||
"vite": "6.0.11",
|
||||
"vitest": "3.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, resolveAlias, resolvePath } from '@nuxt/kit'
|
||||
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { distDir } from '../dirs'
|
||||
@ -198,24 +198,6 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
tsConfig.compilerOptions!.paths['#components'] = [resolve(nuxt.options.buildDir, 'components')]
|
||||
})
|
||||
|
||||
// Watch for changes
|
||||
nuxt.hook('builder:watch', async (event, relativePath) => {
|
||||
if (!['add', 'unlink'].includes(event)) {
|
||||
return
|
||||
}
|
||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||
if (componentDirs.some(dir => path.startsWith(dir.path + '/'))) {
|
||||
await updateTemplates({
|
||||
filter: template => [
|
||||
'components.plugin.mjs',
|
||||
'components.d.ts',
|
||||
'components.server.mjs',
|
||||
'components.client.mjs',
|
||||
].includes(template.filename),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
addBuildPlugin(TreeShakeTemplatePlugin({ sourcemap: !!nuxt.options.sourcemap.server, getComponents }), { client: false })
|
||||
|
||||
const sharedLoaderOptions = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { existsSync, readdirSync } from 'node:fs'
|
||||
import { mkdir, readFile } from 'node:fs/promises'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, defineNuxtModule, findPath, resolvePath, updateTemplates, useNitro } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, defineNuxtModule, findPath, resolvePath, useNitro } from '@nuxt/kit'
|
||||
import { dirname, join, relative, resolve } from 'pathe'
|
||||
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
||||
import { joinURL } from 'ufo'
|
||||
@ -93,10 +93,8 @@ export default defineNuxtModule({
|
||||
addPlugin(resolve(runtimeDir, 'plugins/check-if-page-unused'))
|
||||
}
|
||||
|
||||
nuxt.hook('app:templates', async (app) => {
|
||||
app.pages = await resolvePagesRoutes(nuxt)
|
||||
|
||||
if (!nuxt.options.ssr && app.pages.some(p => p.mode === 'server')) {
|
||||
nuxt.hook('app:templates', (app) => {
|
||||
if (!nuxt.options.ssr && app.pages?.some(p => p.mode === 'server')) {
|
||||
logger.warn('Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`.')
|
||||
}
|
||||
})
|
||||
@ -269,6 +267,13 @@ export default defineNuxtModule({
|
||||
if (!pages) { return false }
|
||||
return pages.some(page => page.file === file) || pages.some(page => page.children && isPage(file, page.children))
|
||||
}
|
||||
|
||||
nuxt.hooks.hookOnce('app:templates', async (app) => {
|
||||
if (!app.pages) {
|
||||
app.pages = await resolvePagesRoutes(nuxt)
|
||||
}
|
||||
})
|
||||
|
||||
nuxt.hook('builder:watch', async (event, relativePath) => {
|
||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||
const shouldAlwaysRegenerate = nuxt.options.experimental.scanPageMeta && isPage(path)
|
||||
@ -276,9 +281,7 @@ export default defineNuxtModule({
|
||||
if (event === 'change' && !shouldAlwaysRegenerate) { return }
|
||||
|
||||
if (shouldAlwaysRegenerate || updateTemplatePaths.some(dir => path.startsWith(dir))) {
|
||||
await updateTemplates({
|
||||
filter: template => template.filename === 'routes.mjs',
|
||||
})
|
||||
nuxt.apps.default!.pages = await resolvePagesRoutes(nuxt)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
"dependencies": {
|
||||
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@rspack/core": "^1.1.8",
|
||||
"@rspack/core": "^1.2.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
|
@ -60,7 +60,7 @@
|
||||
"unctx": "2.4.1",
|
||||
"unimport": "3.14.6",
|
||||
"untyped": "1.5.2",
|
||||
"vite": "6.0.10",
|
||||
"vite": "6.0.11",
|
||||
"vue": "3.5.13",
|
||||
"vue-bundle-renderer": "2.1.1",
|
||||
"vue-loader": "17.4.2",
|
||||
|
@ -30,6 +30,6 @@
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
"unocss": "65.4.2",
|
||||
"vite": "6.0.10"
|
||||
"vite": "6.0.11"
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"vite": "^6.0.10",
|
||||
"vite": "^6.0.11",
|
||||
"vite-node": "^3.0.2",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vue-bundle-renderer": "^2.1.1"
|
||||
|
@ -6,9 +6,8 @@ import { isAbsolute, join, normalize, resolve } from 'pathe'
|
||||
// import { addDevServerHandler } from '@nuxt/kit'
|
||||
import { isFileServingAllowed } from 'vite'
|
||||
import type { ModuleNode, Plugin as VitePlugin } from 'vite'
|
||||
import { getQuery, withTrailingSlash } from 'ufo'
|
||||
import { getQuery } from 'ufo'
|
||||
import { normalizeViteManifest } from 'vue-bundle-renderer'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import { distDir } from './dirs'
|
||||
import type { ViteBuildContext } from './vite'
|
||||
import { isCSS } from './utils'
|
||||
@ -120,10 +119,6 @@ function createViteNodeApp (ctx: ViteBuildContext, invalidates: Set<string> = ne
|
||||
/^#/,
|
||||
/\?/,
|
||||
],
|
||||
external: [
|
||||
'#shared',
|
||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared)))),
|
||||
],
|
||||
},
|
||||
transformMode: {
|
||||
ssr: [/.*/],
|
||||
|
@ -212,13 +212,13 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
||||
|
||||
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer, env) => {
|
||||
// Invalidate virtual modules when templates are re-generated
|
||||
ctx.nuxt.hook('app:templatesGenerated', (_app, changedTemplates) => {
|
||||
for (const template of changedTemplates) {
|
||||
ctx.nuxt.hook('app:templatesGenerated', async (_app, changedTemplates) => {
|
||||
await Promise.all(changedTemplates.map(async (template) => {
|
||||
for (const mod of server.moduleGraph.getModulesByFile(`virtual:nuxt:${encodeURIComponent(template.dst)}`) || []) {
|
||||
server.moduleGraph.invalidateModule(mod)
|
||||
server.reloadModule(mod)
|
||||
}
|
||||
await server.reloadModule(mod)
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
if (nuxt.options.vite.warmupEntry !== false) {
|
||||
|
@ -73,7 +73,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@rspack/core": "1.1.8",
|
||||
"@rspack/core": "1.2.0",
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
|
549
pnpm-lock.yaml
549
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -123,7 +123,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(pagesRootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"302k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"303k"`)
|
||||
|
||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1382k"`)
|
||||
|
Loading…
Reference in New Issue
Block a user