mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(nuxt,schema): support new Nuxt folder structure (#27029)
This commit is contained in:
parent
061fbd4bd6
commit
2c39b3ce61
@ -56,6 +56,10 @@ export default defineNuxtConfig({
|
||||
compatibilityVersion: 4,
|
||||
},
|
||||
// To re-enable _all_ Nuxt v3 behaviour, set the following options:
|
||||
srcDir: '.',
|
||||
dir: {
|
||||
app: 'app'
|
||||
},
|
||||
experimental: {
|
||||
compileTemplate: true,
|
||||
templateUtils: true,
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { resolve } from 'pathe'
|
||||
import type { JSValue } from 'untyped'
|
||||
import { applyDefaults } from 'untyped'
|
||||
import type { LoadConfigOptions } from 'c12'
|
||||
import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
|
||||
import { loadConfig } from 'c12'
|
||||
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
|
||||
import { NuxtConfigSchema } from '@nuxt/schema'
|
||||
|
||||
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
|
||||
|
||||
const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
|
||||
const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key)))
|
||||
|
||||
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
||||
(globalThis as any).defineNuxtConfig = (c: any) => c
|
||||
const result = await loadConfig<NuxtConfig>({
|
||||
@ -28,15 +30,20 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
|
||||
nuxtConfig._nuxtConfigFile = configFile
|
||||
nuxtConfig._nuxtConfigFiles = [configFile]
|
||||
|
||||
// Resolve `rootDir` & `srcDir` of layers
|
||||
const _layers: ConfigLayer<NuxtConfig, ConfigLayerMeta>[] = []
|
||||
for (const layer of layers) {
|
||||
// Resolve `rootDir` & `srcDir` of layers
|
||||
layer.config = layer.config || {}
|
||||
layer.config.rootDir = layer.config.rootDir ?? layer.cwd
|
||||
layer.config.srcDir = resolve(layer.config.rootDir!, layer.config.srcDir!)
|
||||
|
||||
// Normalise layer directories
|
||||
layer.config = await applyDefaults(layerSchema, layer.config as NuxtConfig & Record<string, JSValue>) as unknown as NuxtConfig
|
||||
|
||||
// Filter layers
|
||||
if (!layer.configFile || layer.configFile.endsWith('.nuxtrc')) { continue }
|
||||
_layers.push(layer)
|
||||
}
|
||||
|
||||
// Filter layers
|
||||
const _layers = layers.filter(layer => layer.configFile && !layer.configFile.endsWith('.nuxtrc'))
|
||||
;(nuxtConfig as any)._layers = _layers
|
||||
|
||||
// Ensure at least one layer remains (without nuxt.config)
|
||||
|
@ -154,7 +154,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
baseURL: nuxt.options.app.buildAssetsDir,
|
||||
},
|
||||
...nuxt.options._layers
|
||||
.map(layer => join(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.public || 'public'))
|
||||
.map(layer => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.public || 'public'))
|
||||
.filter(dir => existsSync(dir))
|
||||
.map(dir => ({ dir })),
|
||||
],
|
||||
@ -570,9 +570,9 @@ async function spaLoadingTemplatePath (nuxt: Nuxt) {
|
||||
return resolve(nuxt.options.srcDir, nuxt.options.spaLoadingTemplate)
|
||||
}
|
||||
|
||||
const possiblePaths = nuxt.options._layers.map(layer => join(layer.config.srcDir, 'app/spa-loading-template.html'))
|
||||
const possiblePaths = nuxt.options._layers.map(layer => resolve(layer.config.srcDir, layer.config.dir?.app || 'app', 'spa-loading-template.html'))
|
||||
|
||||
return await findPath(possiblePaths) ?? resolve(nuxt.options.srcDir, 'app/spa-loading-template.html')
|
||||
return await findPath(possiblePaths) ?? resolve(nuxt.options.srcDir, nuxt.options.dir?.app || 'app', 'spa-loading-template.html')
|
||||
}
|
||||
|
||||
async function spaLoadingTemplate (nuxt: Nuxt) {
|
||||
|
@ -491,6 +491,12 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
}
|
||||
}
|
||||
|
||||
// Restart Nuxt when new `app/` dir is added
|
||||
if (event === 'addDir' && path === resolve(nuxt.options.srcDir, 'app')) {
|
||||
logger.info(`\`${path}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
|
||||
return nuxt.callHook('restart', { hard: true })
|
||||
}
|
||||
|
||||
// Core Nuxt files: app.vue, error.vue and app.config.ts
|
||||
const isFileChange = ['add', 'unlink'].includes(event)
|
||||
if (isFileChange && RESTART_RE.test(path)) {
|
||||
|
@ -34,7 +34,7 @@ export default defineNuxtModule({
|
||||
}
|
||||
|
||||
for (const layer of nuxt.options._layers) {
|
||||
const path = await findPath(resolve(layer.config.srcDir, 'app/router.options'))
|
||||
const path = await findPath(resolve(layer.config.srcDir, layer.config.dir?.app || 'app', 'router.options'))
|
||||
if (path) { context.files.unshift({ path }) }
|
||||
}
|
||||
|
||||
@ -86,8 +86,8 @@ export default defineNuxtModule({
|
||||
const restartPaths = nuxt.options._layers.flatMap((layer) => {
|
||||
const pagesDir = (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages'
|
||||
return [
|
||||
join(layer.config.srcDir || layer.cwd, 'app/router.options.ts'),
|
||||
join(layer.config.srcDir || layer.cwd, pagesDir),
|
||||
resolve(layer.config.srcDir || layer.cwd, layer.config.dir?.app || 'app', 'router.options.ts'),
|
||||
resolve(layer.config.srcDir || layer.cwd, pagesDir),
|
||||
]
|
||||
})
|
||||
|
||||
@ -228,9 +228,9 @@ export default defineNuxtModule({
|
||||
const updateTemplatePaths = nuxt.options._layers.flatMap((l) => {
|
||||
const dir = (l.config.rootDir === nuxt.options.rootDir ? nuxt.options : l.config).dir
|
||||
return [
|
||||
join(l.config.srcDir || l.cwd, dir?.pages || 'pages') + '/',
|
||||
join(l.config.srcDir || l.cwd, dir?.layouts || 'layouts') + '/',
|
||||
join(l.config.srcDir || l.cwd, dir?.middleware || 'middleware') + '/',
|
||||
resolve(l.config.srcDir || l.cwd, dir?.pages || 'pages') + '/',
|
||||
resolve(l.config.srcDir || l.cwd, dir?.layouts || 'layouts') + '/',
|
||||
resolve(l.config.srcDir || l.cwd, dir?.middleware || 'middleware') + '/',
|
||||
]
|
||||
})
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { defineUntypedSchema } from 'untyped'
|
||||
import { join, relative, resolve } from 'pathe'
|
||||
import { isDebug, isDevelopment, isTest } from 'std-env'
|
||||
@ -88,7 +89,32 @@ export default defineUntypedSchema({
|
||||
* ```
|
||||
*/
|
||||
srcDir: {
|
||||
$resolve: async (val: string | undefined, get): Promise<string> => resolve(await get('rootDir') as string, val || '.'),
|
||||
$resolve: async (val: string | undefined, get): Promise<string> => {
|
||||
if (val) {
|
||||
return resolve(await get('rootDir') as string, val)
|
||||
}
|
||||
|
||||
const [rootDir, isV4] = await Promise.all([
|
||||
get('rootDir') as Promise<string>,
|
||||
(get('future') as Promise<Record<string, unknown>>).then(r => r.compatibilityVersion === 4),
|
||||
])
|
||||
|
||||
if (!isV4) {
|
||||
return rootDir
|
||||
}
|
||||
|
||||
const srcDir = resolve(rootDir, 'app')
|
||||
if (!existsSync(srcDir)) {
|
||||
const keys = ['assets', 'layouts', 'middleware', 'pages', 'plugins'] as const
|
||||
const dirs = await Promise.all(keys.map(key => get(`dir.${key}`) as Promise<string>))
|
||||
for (const dir of dirs) {
|
||||
if (existsSync(resolve(rootDir, dir))) {
|
||||
return rootDir
|
||||
}
|
||||
}
|
||||
}
|
||||
return srcDir
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
@ -99,7 +125,11 @@ export default defineUntypedSchema({
|
||||
*
|
||||
*/
|
||||
serverDir: {
|
||||
$resolve: async (val: string | undefined, get): Promise<string> => resolve(await get('rootDir') as string, val || resolve(await get('srcDir') as string, 'server')),
|
||||
$resolve: async (val: string | undefined, get): Promise<string> => {
|
||||
const isV4 = ((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||
|
||||
return resolve(await get('rootDir') as string, (val || isV4) ? 'server' : resolve(await get('srcDir') as string, 'server'))
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
@ -219,6 +249,15 @@ export default defineUntypedSchema({
|
||||
* It is better to stick with defaults unless needed.
|
||||
*/
|
||||
dir: {
|
||||
app: {
|
||||
$resolve: async (val: string | undefined, get) => {
|
||||
const isV4 = (await get('future') as Record<string, unknown>).compatibilityVersion === 4
|
||||
if (isV4) {
|
||||
return resolve(await get('srcDir') as string, val || '.')
|
||||
}
|
||||
return val || 'app'
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The assets directory (aliased as `~assets` in your build).
|
||||
*/
|
||||
@ -237,7 +276,15 @@ export default defineUntypedSchema({
|
||||
/**
|
||||
* The modules directory, each file in which will be auto-registered as a Nuxt module.
|
||||
*/
|
||||
modules: 'modules',
|
||||
modules: {
|
||||
$resolve: async (val: string | undefined, get) => {
|
||||
const isV4 = (await get('future') as Record<string, unknown>).compatibilityVersion === 4
|
||||
if (isV4) {
|
||||
return resolve(await get('rootDir') as string, val || 'modules')
|
||||
}
|
||||
return val || 'modules'
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The directory which will be processed to auto-generate your application page routes.
|
||||
@ -254,7 +301,13 @@ export default defineUntypedSchema({
|
||||
* and copied across into your `dist` folder when your app is generated.
|
||||
*/
|
||||
public: {
|
||||
$resolve: async (val, get) => val || await get('dir.static') || 'public',
|
||||
$resolve: async (val: string | undefined, get) => {
|
||||
const isV4 = (await get('future') as Record<string, unknown>).compatibilityVersion === 4
|
||||
if (isV4) {
|
||||
return resolve(await get('rootDir') as string, val || await get('dir.static') as string || 'public')
|
||||
}
|
||||
return val || await get('dir.static') as string || 'public'
|
||||
},
|
||||
},
|
||||
|
||||
static: {
|
||||
|
@ -21,6 +21,10 @@ export default defineUntypedSchema({
|
||||
* compatibilityVersion: 4,
|
||||
* },
|
||||
* // To re-enable _all_ Nuxt v3 behaviour, set the following options:
|
||||
* srcDir: '.',
|
||||
* dir: {
|
||||
* app: 'app'
|
||||
* },
|
||||
* experimental: {
|
||||
* compileTemplate: true,
|
||||
* templateUtils: true,
|
||||
|
90
packages/schema/test/folder-structure.spec.ts
Normal file
90
packages/schema/test/folder-structure.spec.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { applyDefaults } from 'untyped'
|
||||
|
||||
import { NuxtConfigSchema } from '../src'
|
||||
import type { NuxtOptions } from '../src'
|
||||
|
||||
describe('nuxt folder structure', () => {
|
||||
it('should resolve directories for v3 setup correctly', async () => {
|
||||
const result = await applyDefaults(NuxtConfigSchema, {})
|
||||
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dir": {
|
||||
"app": "app",
|
||||
"modules": "modules",
|
||||
"public": "public",
|
||||
},
|
||||
"rootDir": "<cwd>",
|
||||
"serverDir": "<cwd>/server",
|
||||
"srcDir": "<cwd>",
|
||||
"workspaceDir": "<cwd>",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('should resolve directories with a custom `srcDir` and `rootDir`', async () => {
|
||||
const result = await applyDefaults(NuxtConfigSchema, { srcDir: 'src/', rootDir: '/test' })
|
||||
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dir": {
|
||||
"app": "app",
|
||||
"modules": "modules",
|
||||
"public": "public",
|
||||
},
|
||||
"rootDir": "/test",
|
||||
"serverDir": "/test/src/server",
|
||||
"srcDir": "/test/src",
|
||||
"workspaceDir": "/test",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('should resolve directories when opting-in to v4 schema', async () => {
|
||||
const result = await applyDefaults(NuxtConfigSchema, { future: { compatibilityVersion: 4 } })
|
||||
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dir": {
|
||||
"app": "<cwd>/app",
|
||||
"modules": "<cwd>/modules",
|
||||
"public": "<cwd>/public",
|
||||
},
|
||||
"rootDir": "<cwd>",
|
||||
"serverDir": "<cwd>/server",
|
||||
"srcDir": "<cwd>/app",
|
||||
"workspaceDir": "<cwd>",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('should resolve directories when opting-in to v4 schema with a custom `srcDir` and `rootDir`', async () => {
|
||||
const result = await applyDefaults(NuxtConfigSchema, { future: { compatibilityVersion: 4 }, srcDir: 'customApp/', rootDir: '/test' })
|
||||
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dir": {
|
||||
"app": "/test/customApp",
|
||||
"modules": "/test/modules",
|
||||
"public": "/test/public",
|
||||
},
|
||||
"rootDir": "/test",
|
||||
"serverDir": "/test/server",
|
||||
"srcDir": "/test/customApp",
|
||||
"workspaceDir": "/test",
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
function getDirs (options: NuxtOptions) {
|
||||
const stripRoot = (dir: string) => dir.replace(process.cwd(), '<cwd>')
|
||||
return {
|
||||
rootDir: stripRoot(options.rootDir),
|
||||
serverDir: stripRoot(options.serverDir),
|
||||
srcDir: stripRoot(options.srcDir),
|
||||
dir: {
|
||||
app: stripRoot(options.dir.app),
|
||||
modules: stripRoot(options.dir.modules),
|
||||
public: stripRoot(options.dir.public),
|
||||
},
|
||||
workspaceDir: stripRoot(options.workspaceDir!),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user