diff --git a/packages/bridge/src/nitro.ts b/packages/bridge/src/nitro.ts index 97c69b3ddc..97abf9fd6b 100644 --- a/packages/bridge/src/nitro.ts +++ b/packages/bridge/src/nitro.ts @@ -15,6 +15,13 @@ export function setupNitroBridge () { throw new Error('[nitro] Please use `nuxt generate` for static target') } + // Handle legacy property name `assetsPath` + nuxt.options.app.buildAssetsDir = nuxt.options.app.buildAssetsDir || nuxt.options.app.assetsPath + nuxt.options.app.assetsPath = nuxt.options.app.buildAssetsDir + // Nitro expects app config on `config.app` rather than `config._app` + nuxt.options.publicRuntimeConfig.app = nuxt.options.publicRuntimeConfig.app || {} + Object.assign(nuxt.options.publicRuntimeConfig.app, nuxt.options.publicRuntimeConfig._app) + // Disable loading-screen // @ts-ignore nuxt.options.build.loadingScreen = false diff --git a/packages/bridge/src/vite/client.ts b/packages/bridge/src/vite/client.ts index 400a8b45bb..097c3ecb96 100644 --- a/packages/bridge/src/vite/client.ts +++ b/packages/bridge/src/vite/client.ts @@ -3,6 +3,7 @@ import * as vite from 'vite' import { createVuePlugin } from 'vite-plugin-vue2' import PluginLegacy from '@vitejs/plugin-legacy' import consola from 'consola' +import { joinURL } from 'ufo' import { devStyleSSRPlugin } from '../../../vite/src/plugins/dev-ssr-css' import { jsxPlugin } from './plugins/jsx' import { ViteBuildContext, ViteOptions } from './types' @@ -28,7 +29,6 @@ export async function buildClient (ctx: ViteBuildContext) { }, build: { outDir: resolve(ctx.nuxt.options.buildDir, 'dist/client'), - assetsDir: '.', rollupOptions: { input: resolve(ctx.nuxt.options.buildDir, 'client.js') }, @@ -39,7 +39,10 @@ export async function buildClient (ctx: ViteBuildContext) { jsxPlugin(), createVuePlugin(ctx.config.vue), PluginLegacy(), - devStyleSSRPlugin(ctx.nuxt.options.rootDir) + devStyleSSRPlugin({ + rootDir: ctx.nuxt.options.rootDir, + buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir) + }) ], server: { middlewareMode: true diff --git a/packages/bridge/src/vite/manifest.ts b/packages/bridge/src/vite/manifest.ts index 421536872e..4ea604ffce 100644 --- a/packages/bridge/src/vite/manifest.ts +++ b/packages/bridge/src/vite/manifest.ts @@ -46,7 +46,6 @@ export async function prepareManifests (ctx: ViteBuildContext) { export async function generateBuildManifest (ctx: ViteBuildContext) { const rDist = (...args: string[]): string => resolve(ctx.nuxt.options.buildDir, 'dist', ...args) - const publicPath = ctx.nuxt.options.app.assetsPath // Default: /nuxt/ const viteClientManifest = await fse.readJSON(rDist('client/manifest.json')) const clientEntries = Object.entries(viteClientManifest) @@ -59,12 +58,13 @@ export async function generateBuildManifest (ctx: ViteBuildContext) { const polyfillName = initialEntries.find(id => id.startsWith('polyfills-legacy.')) // @vitejs/plugin-legacy uses SystemJS which need to call `System.import` to load modules - const clientImports = initialJs.filter(id => id !== polyfillName).map(id => publicPath + id) + const clientImports = initialJs.filter(id => id !== polyfillName) const clientEntryCode = `var imports = ${JSON.stringify(clientImports)}\nimports.reduce((p, id) => p.then(() => System.import(id)), Promise.resolve())` const clientEntryName = 'entry-legacy.' + hash(clientEntryCode) + '.js' const clientManifest = { - publicPath, + // This publicPath will be ignored by Nitro and computed dynamically + publicPath: ctx.nuxt.options.app.buildAssetsDir, all: uniq([ polyfillName, clientEntryName, @@ -74,12 +74,12 @@ export async function generateBuildManifest (ctx: ViteBuildContext) { polyfillName, clientEntryName, ...initialAssets - ], + ].filter(Boolean), async: [ // We move initial entries to the client entry ...initialJs, ...asyncEntries - ], + ].filter(Boolean), modules: {}, assetsMapping: {} } diff --git a/packages/bridge/src/vite/server.ts b/packages/bridge/src/vite/server.ts index 7ef61b3a20..d3f5607485 100644 --- a/packages/bridge/src/vite/server.ts +++ b/packages/bridge/src/vite/server.ts @@ -51,7 +51,6 @@ export async function buildServer (ctx: ViteBuildContext) { }, build: { outDir: resolve(ctx.nuxt.options.buildDir, 'dist/server'), - assetsDir: ctx.nuxt.options.app.assetsPath.replace(/^\/|\/$/, ''), ssr: true, ssrManifest: true, rollupOptions: { diff --git a/packages/bridge/src/vite/vite.ts b/packages/bridge/src/vite/vite.ts index 4b614e1a57..aca836df7a 100644 --- a/packages/bridge/src/vite/vite.ts +++ b/packages/bridge/src/vite/vite.ts @@ -1,6 +1,7 @@ import { resolve } from 'pathe' import * as vite from 'vite' import consola from 'consola' +import { withoutLeadingSlash } from 'ufo' import { distDir } from '../dirs' import { warmupViteServer } from '../../../vite/src/utils/warmup' import { buildClient } from './client' @@ -73,6 +74,7 @@ async function bundle (nuxt: Nuxt, builder: any) { publicDir: resolve(nuxt.options.srcDir, nuxt.options.dir.static), clearScreen: false, build: { + assetsDir: withoutLeadingSlash(nuxt.options.app.buildAssetsDir), emptyOutDir: false }, plugins: [ diff --git a/packages/nitro/src/build.ts b/packages/nitro/src/build.ts index c2b3cad51f..1e8e9e5f7b 100644 --- a/packages/nitro/src/build.ts +++ b/packages/nitro/src/build.ts @@ -4,7 +4,7 @@ import * as rollup from 'rollup' import fse from 'fs-extra' import { printFSTree } from './utils/tree' import { getRollupConfig } from './rollup/config' -import { hl, prettyPath, serializeTemplate, writeFile, isDirectory, readDirRecursively, replaceAll } from './utils' +import { hl, prettyPath, serializeTemplate, writeFile, isDirectory, replaceAll } from './utils' import { NitroContext } from './context' import { scanMiddleware } from './server/middleware' @@ -30,20 +30,17 @@ async function cleanupDir (dir: string) { export async function generate (nitroContext: NitroContext) { consola.start('Generating public...') + await nitroContext._internal.hooks.callHook('nitro:generate', nitroContext) + const publicDir = nitroContext._nuxt.publicDir - let publicFiles: string[] = [] if (await isDirectory(publicDir)) { - publicFiles = readDirRecursively(publicDir).map(r => r.replace(publicDir, '')) await fse.copy(publicDir, nitroContext.output.publicDir) } const clientDist = resolve(nitroContext._nuxt.buildDir, 'dist/client') if (await isDirectory(clientDist)) { - await fse.copy(clientDist, join(nitroContext.output.publicDir, nitroContext._nuxt.publicPath), { - // TODO: Workaround vite's issue that duplicates public files - // https://github.com/nuxt/framework/issues/1192 - filter: src => !publicFiles.includes(src.replace(clientDist, '')) - }) + const buildAssetsDir = join(nitroContext.output.publicDir, nitroContext._nuxt.buildAssetsDir) + await fse.copy(clientDist, buildAssetsDir) } consola.success('Generated public ' + prettyPath(nitroContext.output.publicDir)) diff --git a/packages/nitro/src/context.ts b/packages/nitro/src/context.ts index ff062057d4..9f2cbefedd 100644 --- a/packages/nitro/src/context.ts +++ b/packages/nitro/src/context.ts @@ -19,6 +19,7 @@ export interface NitroHooks { 'nitro:document': (htmlTemplate: { src: string, contents: string, dst: string }) => void 'nitro:rollup:before': (context: NitroContext) => void | Promise 'nitro:compiled': (context: NitroContext) => void + 'nitro:generate': (context: NitroContext) => void | Promise 'close': () => void } @@ -71,8 +72,8 @@ export interface NitroContext { generateDir: string publicDir: string serverDir: string - routerBase: string - publicPath: string + baseURL: string + buildAssetsDir: string isStatic: boolean fullStatic: boolean staticAssets: any @@ -139,8 +140,8 @@ export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): N generateDir: nuxtOptions.generate.dir, publicDir: resolve(nuxtOptions.srcDir, nuxtOptions.dir.public || nuxtOptions.dir.static), serverDir: resolve(nuxtOptions.srcDir, (nuxtOptions.dir as any).server || 'server'), - routerBase: nuxtOptions.router.base, - publicPath: nuxtOptions.app.assetsPath, + baseURL: nuxtOptions.app.baseURL, + buildAssetsDir: nuxtOptions.app.buildAssetsDir, isStatic: nuxtOptions.target === 'static' && !nuxtOptions.dev, fullStatic: nuxtOptions.target === 'static' && !nuxtOptions._legacyGenerate, staticAssets: nuxtOptions.generate.staticAssets, diff --git a/packages/nitro/src/presets/browser.ts b/packages/nitro/src/presets/browser.ts index 5c15093c82..5cc7a5d840 100644 --- a/packages/nitro/src/presets/browser.ts +++ b/packages/nitro/src/presets/browser.ts @@ -1,17 +1,19 @@ import { existsSync, promises as fsp } from 'fs' import { resolve } from 'pathe' import consola from 'consola' +import { joinURL } from 'ufo' import { extendPreset, prettyPath } from '../utils' import { NitroPreset, NitroContext, NitroInput } from '../context' import { worker } from './worker' export const browser: NitroPreset = extendPreset(worker, (input: NitroInput) => { - const routerBase = input._nuxt.routerBase + // TODO: Join base at runtime + const baseURL = input._nuxt.baseURL const script = `` @@ -21,11 +23,11 @@ if ('serviceWorker' in navigator) { - - + + ` + return `` }) .join('') } diff --git a/packages/nitro/src/runtime/entries/cloudflare.ts b/packages/nitro/src/runtime/entries/cloudflare.ts index ecf4cb331e..c71ef0f933 100644 --- a/packages/nitro/src/runtime/entries/cloudflare.ts +++ b/packages/nitro/src/runtime/entries/cloudflare.ts @@ -1,9 +1,9 @@ import '#polyfill' -import { getAssetFromKV } from '@cloudflare/kv-asset-handler' +import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler' +import { withoutBase } from 'ufo' import { localCall } from '../server' import { requestHasBody, useRequestBody } from '../server/utils' - -const PUBLIC_PATH = process.env.PUBLIC_PATH // Default: /_nuxt/ +import { buildAssetsURL, baseURL } from '#paths' addEventListener('fetch', (event: any) => { event.respondWith(handleEvent(event)) @@ -11,7 +11,7 @@ addEventListener('fetch', (event: any) => { async function handleEvent (event) { try { - return await getAssetFromKV(event, { cacheControl: assetsCacheControl }) + return await getAssetFromKV(event, { cacheControl: assetsCacheControl, mapRequestToAsset: baseURLModifier }) } catch (_err) { // Ignore } @@ -42,7 +42,7 @@ async function handleEvent (event) { } function assetsCacheControl (request) { - if (request.url.includes(PUBLIC_PATH) /* TODO: Check with routerBase */) { + if (request.url.startsWith(buildAssetsURL())) { return { browserTTL: 31536000, edgeTTL: 31536000 @@ -50,3 +50,8 @@ function assetsCacheControl (request) { } return {} } + +const baseURLModifier = (request: Request) => { + const url = withoutBase(request.url, baseURL()) + return mapRequestToAsset(new Request(url, request)) +} diff --git a/packages/nitro/src/runtime/entries/server.ts b/packages/nitro/src/runtime/entries/server.ts index 93709ca07e..23edfa195f 100644 --- a/packages/nitro/src/runtime/entries/server.ts +++ b/packages/nitro/src/runtime/entries/server.ts @@ -3,6 +3,7 @@ import { Server as HttpServer } from 'http' import { Server as HttpsServer } from 'https' import destr from 'destr' import { handle } from '../server' +import { baseURL } from '#paths' const cert = process.env.NITRO_SSL_CERT const key = process.env.NITRO_SSL_KEY @@ -19,7 +20,7 @@ server.listen(port, hostname, (err) => { process.exit(1) } const protocol = cert && key ? 'https' : 'http' - console.log(`Listening on ${protocol}://${hostname}:${port}`) + console.log(`Listening on ${protocol}://${hostname}:${port}${baseURL()}`) }) export default {} diff --git a/packages/nitro/src/runtime/server/index.ts b/packages/nitro/src/runtime/server/index.ts index b300ab3fc8..ce38f7c0b2 100644 --- a/packages/nitro/src/runtime/server/index.ts +++ b/packages/nitro/src/runtime/server/index.ts @@ -1,8 +1,8 @@ -import '../app/config' import { createApp, useBase } from 'h3' import { createFetch, Headers } from 'ohmyfetch' import destr from 'destr' import { createCall, createFetch as createLocalFetch } from 'unenv/runtime/fetch/index' +import { baseURL } from '../app/paths' import { timingMiddleware } from './timing' import { handleError } from './error' // @ts-ignore @@ -18,7 +18,7 @@ app.use(serverMiddleware) app.use(() => import('../app/render').then(e => e.renderMiddleware), { lazy: true }) export const stack = app.stack -export const handle = useBase(process.env.ROUTER_BASE, app) +export const handle = useBase(baseURL(), app) export const localCall = createCall(handle) export const localFetch = createLocalFetch(localCall, globalThis.fetch) diff --git a/packages/nitro/src/runtime/server/static.ts b/packages/nitro/src/runtime/server/static.ts index ea084d484f..f29cfa4bd8 100644 --- a/packages/nitro/src/runtime/server/static.ts +++ b/packages/nitro/src/runtime/server/static.ts @@ -2,9 +2,10 @@ import { createError } from 'h3' import { withoutTrailingSlash, withLeadingSlash, parseURL } from 'ufo' // @ts-ignore import { getAsset, readAsset } from '#static' +import { buildAssetsDir } from '#paths' const METHODS = ['HEAD', 'GET'] -const PUBLIC_PATH = process.env.PUBLIC_PATH // Default: /_nuxt/ + const TWO_DAYS = 2 * 60 * 60 * 24 const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION @@ -26,8 +27,10 @@ export default async function serveStatic (req, res) { } } + const isBuildAsset = id.startsWith(buildAssetsDir()) + if (!asset) { - if (id.startsWith(PUBLIC_PATH) && !id.startsWith(STATIC_ASSETS_BASE)) { + if (isBuildAsset && !id.startsWith(STATIC_ASSETS_BASE)) { throw createError({ statusMessage: 'Cannot find static asset ' + id, statusCode: 404 @@ -62,7 +65,7 @@ export default async function serveStatic (req, res) { res.setHeader('Last-Modified', asset.mtime) } - if (id.startsWith(PUBLIC_PATH)) { + if (isBuildAsset) { res.setHeader('Cache-Control', `max-age=${TWO_DAYS}, immutable`) } diff --git a/packages/nitro/src/server/dev.ts b/packages/nitro/src/server/dev.ts index d6195e43b9..b0e10ae6ee 100644 --- a/packages/nitro/src/server/dev.ts +++ b/packages/nitro/src/server/dev.ts @@ -12,6 +12,7 @@ import servePlaceholder from 'serve-placeholder' import serveStatic from 'serve-static' import { resolve } from 'pathe' import connect from 'connect' +import { joinURL } from 'ufo' import type { NitroContext } from '../context' import { handleVfs } from './vfs' @@ -76,8 +77,9 @@ export function createDevServer (nitroContext: NitroContext) { const app = createApp() // _nuxt and static - app.use(nitroContext._nuxt.publicPath, serveStatic(resolve(nitroContext._nuxt.buildDir, 'dist/client'))) - app.use(nitroContext._nuxt.routerBase, serveStatic(resolve(nitroContext._nuxt.publicDir))) + const buildAssetsURL = joinURL(nitroContext._nuxt.baseURL, nitroContext._nuxt.buildAssetsDir) + app.use(buildAssetsURL, serveStatic(resolve(nitroContext._nuxt.buildDir, 'dist/client'))) + app.use(nitroContext._nuxt.baseURL, serveStatic(resolve(nitroContext._nuxt.publicDir))) // debugging endpoint to view vfs app.use('/_vfs', useBase('/_vfs', handleVfs(nitroContext))) @@ -89,7 +91,7 @@ export function createDevServer (nitroContext: NitroContext) { app.use(devMiddleware.middleware) // serve placeholder 404 assets instead of hitting SSR - app.use(nitroContext._nuxt.publicPath, servePlaceholder()) + app.use(buildAssetsURL, servePlaceholder()) // SSR Proxy const proxy = httpProxy.createProxy() diff --git a/packages/nitro/src/server/middleware.ts b/packages/nitro/src/server/middleware.ts index 15472f7a71..6f4e3a8a79 100644 --- a/packages/nitro/src/server/middleware.ts +++ b/packages/nitro/src/server/middleware.ts @@ -23,10 +23,10 @@ export interface ServerMiddleware { promisify?: boolean // Default is true } -function filesToMiddleware (files: string[], baseDir: string, basePath: string, overrides?: Partial): ServerMiddleware[] { +function filesToMiddleware (files: string[], baseDir: string, baseURL: string, overrides?: Partial): ServerMiddleware[] { return files.map((file) => { const route = joinURL( - basePath, + baseURL, file .slice(0, file.length - extname(file).length) .replace(/\/index$/, '') diff --git a/packages/nitro/src/utils/index.ts b/packages/nitro/src/utils/index.ts index 420f0e3f97..dae925667c 100644 --- a/packages/nitro/src/utils/index.ts +++ b/packages/nitro/src/utils/index.ts @@ -1,5 +1,5 @@ import { createRequire } from 'module' -import { relative, dirname, join, resolve } from 'pathe' +import { relative, dirname, resolve } from 'pathe' import fse from 'fs-extra' import jiti from 'jiti' import defu from 'defu' @@ -152,11 +152,3 @@ export function readPackageJson ( throw error } } - -export function readDirRecursively (dir: string) { - return fse.readdirSync(dir).reduce((files, file) => { - const name = join(dir, file) - const isDirectory = fse.statSync(name).isDirectory() - return isDirectory ? [...files, ...readDirRecursively(name)] : [...files, name] - }, []) -} diff --git a/packages/nitro/types/shims.d.ts b/packages/nitro/types/shims.d.ts index 68664a2884..634a78fa34 100644 --- a/packages/nitro/types/shims.d.ts +++ b/packages/nitro/types/shims.d.ts @@ -21,3 +21,11 @@ declare module '#config' { const runtimeConfig: PrivateRuntimeConfig & PublicRuntimeConfig export default runtimeConfig } + +declare module '#paths' { + export const baseURL: () => string + export const buildAssetsDir: () => string + + export const buildAssetsURL: (...path: string[]) => string + export const publicAssetsURL: (...path: string[]) => string +} diff --git a/packages/nuxt3/src/core/nitro.ts b/packages/nuxt3/src/core/nitro.ts index e9cee50924..f398745ecf 100644 --- a/packages/nuxt3/src/core/nitro.ts +++ b/packages/nuxt3/src/core/nitro.ts @@ -20,6 +20,7 @@ export function initNitro (nuxt: Nuxt) { nuxt.hooks.addHooks(nitroContext.nuxtHooks) nuxt.hook('close', () => nitroContext._internal.hooks.callHook('close')) nitroContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template)) + nitroContext._internal.hooks.hook('nitro:generate', ctx => nuxt.callHook('nitro:generate', ctx)) // @ts-ignore nuxt.hooks.addHooks(nitroDevContext.nuxtHooks) diff --git a/packages/nuxt3/src/pages/runtime/router.ts b/packages/nuxt3/src/pages/runtime/router.ts index 533be3afc1..5f1ddc2ad2 100644 --- a/packages/nuxt3/src/pages/runtime/router.ts +++ b/packages/nuxt3/src/pages/runtime/router.ts @@ -8,7 +8,7 @@ import { import NuxtNestedPage from './nested-page.vue' import NuxtPage from './page.vue' import NuxtLayout from './layout' -import { defineNuxtPlugin } from '#app' +import { defineNuxtPlugin, useRuntimeConfig } from '#app' // @ts-ignore import routes from '#build/routes' @@ -29,9 +29,10 @@ export default defineNuxtPlugin((nuxtApp) => { // TODO: remove before release - present for backwards compatibility & intentionally undocumented nuxtApp.vueApp.component('NuxtChild', NuxtNestedPage) + const { baseURL } = useRuntimeConfig().app const routerHistory = process.client - ? createWebHistory() - : createMemoryHistory() + ? createWebHistory(baseURL) + : createMemoryHistory(baseURL) const router = createRouter({ history: routerHistory, diff --git a/packages/schema/src/config/_app.ts b/packages/schema/src/config/_app.ts index 2b66c53a77..e16e389145 100644 --- a/packages/schema/src/config/_app.ts +++ b/packages/schema/src/config/_app.ts @@ -1,7 +1,6 @@ import { resolve, join } from 'pathe' import { existsSync, readdirSync } from 'fs' import defu from 'defu' -import { isRelative, joinURL, hasProtocol } from 'ufo' export default { /** Vue.js config */ @@ -29,16 +28,40 @@ export default { /** * Nuxt App configuration. * @version 2 + * @version 3 */ app: { - $resolve: (val, get) => { - const useCDN = hasProtocol(get('build.publicPath'), true) && !get('dev') - const isRelativePublicPath = isRelative(get('build.publicPath')) - return defu(val, { - basePath: get('router.base'), - assetsPath: isRelativePublicPath ? get('build.publicPath') : useCDN ? '/' : joinURL(get('router.base'), get('build.publicPath')), - cdnURL: useCDN ? get('build.publicPath') : null - }) + /** + * The base path of your Nuxt application. + * + * This can be set at runtime by setting the BASE_PATH environment variable. + * @example + * ```bash + * BASE_PATH=/prefix/ node .output/server/index.mjs + * ``` + */ + baseURL: '/', + /** The folder name for the built site assets, relative to `baseURL` (or `cdnURL` if set). This is set at build time and should not be customized at runtime. */ + buildAssetsDir: '/_nuxt/', + /** + * The folder name for the built site assets, relative to `baseURL` (or `cdnURL` if set). + * @deprecated - use `buildAssetsDir` instead + * @version 2 + */ + assetsPath: { + $resolve: (val, get) => val ?? get('buildAssetsDir') + }, + /** + * An absolute URL to serve the public folder from (production-only). + * + * This can be set to a different value at runtime by setting the CDN_URL environment variable. + * @example + * ```bash + * CDN_URL=https://mycdn.org/ node .output/server/index.mjs + * ``` + */ + cdnURL: { + $resolve: (val, get) => get('dev') ? null : val || null } }, @@ -302,7 +325,7 @@ export default { * @see [vue@3 documentation](https://v3.vuejs.org/guide/transitions-enterleave.html) * @version 2 */ - pageTransition: { + pageTransition: { $resolve: (val, get) => { val = typeof val === 'string' ? { name: val } : val return defu(val, { diff --git a/packages/schema/src/config/build.ts b/packages/schema/src/config/build.ts index f215f02b7f..9c701aff1a 100644 --- a/packages/schema/src/config/build.ts +++ b/packages/schema/src/config/build.ts @@ -1,5 +1,5 @@ import { isCI, isTest } from 'std-env' -import { hasProtocol } from 'ufo' +import { normalizeURL, withTrailingSlash } from 'ufo' export default { /** @@ -153,13 +153,9 @@ export default { * } * ``` * @version 2 - * @version 3 */ publicPath: { - $resolve: (val, get) => { - if (hasProtocol(val, true) && get('dev')) { val = null } - return (val || '/_nuxt/').replace(/([^/])$/, '$1/') - } + $resolve: (val, get) => val ? withTrailingSlash(normalizeURL(val)) : get('app.buildAssetsDir') }, /** diff --git a/packages/schema/src/config/generate.ts b/packages/schema/src/config/generate.ts index 6f7a92b601..07422951fd 100644 --- a/packages/schema/src/config/generate.ts +++ b/packages/schema/src/config/generate.ts @@ -160,7 +160,7 @@ export default { * The full path to the directory underneath `/_nuxt/` where static assets * (payload, state and manifest files) will live. */ - base: { $resolve: (val, get) => val || joinURL(get('app.assetsPath'), get('generate.dir')) }, + base: { $resolve: (val, get) => val || joinURL(get('app.buildAssetsDir'), get('generate.dir')) }, /** The full path to the versioned directory where static assets for the current buidl are located. */ versionBase: { $resolve: (val, get) => val || joinURL(get('generate.base'), get('generate.version')) }, /** A unique string to uniquely identify payload versions (defaults to the current timestamp). */ diff --git a/packages/schema/src/config/router.ts b/packages/schema/src/config/router.ts index 7e7c8c1bb3..2cb4baa91e 100644 --- a/packages/schema/src/config/router.ts +++ b/packages/schema/src/config/router.ts @@ -16,10 +16,9 @@ export default { * This can be useful if you need to serve Nuxt as a different context root, from * within a bigger web site. * @version 2 - * @version 3 */ base: { - $resolve: (val = '/') => withTrailingSlash(normalizeURL(val)) + $resolve: (val, get) => val ? withTrailingSlash(normalizeURL(val)) : get('app.baseURL') }, /** @private */ diff --git a/packages/schema/src/types/hooks.ts b/packages/schema/src/types/hooks.ts index d9c9cf5bd2..2320c245e9 100644 --- a/packages/schema/src/types/hooks.ts +++ b/packages/schema/src/types/hooks.ts @@ -80,6 +80,7 @@ export interface NuxtHooks { // @nuxt/nitro 'nitro:document': (template: { src: string, contents: string }) => HookResult 'nitro:context': (context: any) => HookResult + 'nitro:generate': (context: any) => HookResult // @nuxt/cli 'generate:cache:ignore': (ignore: string[]) => HookResult diff --git a/packages/vite/src/client.ts b/packages/vite/src/client.ts index 3e3e6081b3..6375097aab 100644 --- a/packages/vite/src/client.ts +++ b/packages/vite/src/client.ts @@ -5,12 +5,14 @@ import vuePlugin from '@vitejs/plugin-vue' import viteJsxPlugin from '@vitejs/plugin-vue-jsx' import type { Connect } from 'vite' +import { joinURL, withoutLeadingSlash } from 'ufo' import { cacheDirPlugin } from './plugins/cache-dir' import { analyzePlugin } from './plugins/analyze' import { wpfs } from './utils/wpfs' import type { ViteBuildContext, ViteOptions } from './vite' import { writeManifest } from './manifest' import { devStyleSSRPlugin } from './plugins/dev-ssr-css' +import { DynamicBasePlugin } from './plugins/dynamic-base' export async function buildClient (ctx: ViteBuildContext) { const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { @@ -25,6 +27,7 @@ export async function buildClient (ctx: ViteBuildContext) { } }, build: { + assetsDir: ctx.nuxt.options.dev ? withoutLeadingSlash(ctx.nuxt.options.app.buildAssetsDir) : '.', rollupOptions: { output: { chunkFileNames: ctx.nuxt.options.dev ? undefined : '[name]-[hash].mjs', @@ -38,7 +41,11 @@ export async function buildClient (ctx: ViteBuildContext) { cacheDirPlugin(ctx.nuxt.options.rootDir, 'client'), vuePlugin(ctx.config.vue), viteJsxPlugin(), - devStyleSSRPlugin(ctx.nuxt.options.rootDir) + DynamicBasePlugin.vite({ env: 'client', devAppConfig: ctx.nuxt.options.app }), + devStyleSSRPlugin({ + rootDir: ctx.nuxt.options.rootDir, + buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir) + }) ], server: { middlewareMode: true diff --git a/packages/vite/src/manifest.ts b/packages/vite/src/manifest.ts index c1c84eef54..123922032c 100644 --- a/packages/vite/src/manifest.ts +++ b/packages/vite/src/manifest.ts @@ -1,5 +1,6 @@ import fse from 'fs-extra' import { resolve } from 'pathe' +import { joinURL } from 'ufo' import type { ViteBuildContext } from './vite' export async function writeManifest (ctx: ViteBuildContext, extraEntries: string[] = []) { @@ -15,7 +16,7 @@ export async function writeManifest (ctx: ViteBuildContext, extraEntries: string // Legacy dev manifest const devClientManifest = { - publicPath: ctx.nuxt.options.build.publicPath, + publicPath: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir), all: entries, initial: entries, async: [], diff --git a/packages/vite/src/plugins/dev-ssr-css.ts b/packages/vite/src/plugins/dev-ssr-css.ts index 51f3699c12..22248433fa 100644 --- a/packages/vite/src/plugins/dev-ssr-css.ts +++ b/packages/vite/src/plugins/dev-ssr-css.ts @@ -1,7 +1,13 @@ +import { joinURL } from 'ufo' import { Plugin } from 'vite' import { isCSS } from '../utils' -export function devStyleSSRPlugin (rootDir: string): Plugin { +export interface DevStyleSSRPluginOptions { + rootDir: string + buildAssetsURL: string +} + +export function devStyleSSRPlugin (options: DevStyleSSRPluginOptions): Plugin { return { name: 'nuxt:dev-style-ssr', apply: 'serve', @@ -12,13 +18,13 @@ export function devStyleSSRPlugin (rootDir: string): Plugin { } let moduleId = id - if (moduleId.startsWith(rootDir)) { - moduleId = moduleId.slice(rootDir.length) + if (moduleId.startsWith(options.rootDir)) { + moduleId = moduleId.slice(options.rootDir.length) } // When dev `