From a56de03b8fa3c86d2a36b2cf13a7ad5026b22bf6 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Fri, 12 Aug 2022 23:14:12 +0100 Subject: [PATCH 01/11] feat: migrate to vite 3 --- package.json | 4 +- src/module.ts | 5 +- src/nitro.ts | 2 +- src/runtime/composables.ts | 3 +- src/runtime/nitro/paths.ts | 3 + src/vite/client.ts | 96 ++++++++++++++++++++-------- src/vite/dev-bundler.ts | 8 +-- src/vite/manifest.ts | 39 ++++++++---- src/vite/plugins/dev-ssr-css.ts | 6 +- src/vite/plugins/dynamic-base.ts | 103 ------------------------------- src/vite/server.ts | 50 +++++++++++---- src/vite/types.ts | 30 ++++----- src/vite/vite.ts | 40 +++++------- test/bridge.test.ts | 37 +++++++++-- yarn.lock | 80 ++++++++++++------------ 15 files changed, 257 insertions(+), 249 deletions(-) delete mode 100644 src/vite/plugins/dynamic-base.ts diff --git a/package.json b/package.json index 613f3ea8..3aa08106 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@nuxt/postcss8": "^1.1.3", "@nuxt/schema": "3.0.0-rc.11", "@nuxt/ui-templates": "^0.4.0", - "@vitejs/plugin-legacy": "^1.8.2", + "@vitejs/plugin-legacy": "^2.0.0", "@vitejs/plugin-vue2": "^1.1.2", "acorn": "^8.8.0", "cookie-es": "^0.5.0", @@ -74,7 +74,7 @@ "unimport": "^0.6.7", "unplugin": "^0.9.6", "untyped": "^0.5.0", - "vite": "^2.9.14", + "vite": "~3.0.4", "vue-bundle-renderer": "^0.4.3" }, "devDependencies": { diff --git a/src/module.ts b/src/module.ts index 242d4178..4fb5e99f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,7 +1,8 @@ import { createRequire } from 'module' import { defineNuxtModule, installModule, checkNuxtCompatibility } from '@nuxt/kit' -import type { NuxtModule } from '@nuxt/schema' -import { NuxtCompatibility } from '@nuxt/schema' +import type { NuxtModule, NuxtCompatibility } from '@nuxt/schema' +import type { Configuration } from 'webpack' +import { join } from 'pathe' import type { BridgeConfig } from '../types' import { setupNitroBridge } from './nitro' import { setupAppBridge } from './app' diff --git a/src/nitro.ts b/src/nitro.ts index aee44c14..baab9b3c 100644 --- a/src/nitro.ts +++ b/src/nitro.ts @@ -87,7 +87,7 @@ export async function setupNitroBridge () { }, publicAssets: [ { - baseURL: nuxt.options.app.buildAssetsDir, + baseURL: (nuxt.options as any).bridge.vite ? '/' : nuxt.options.app.buildAssetsDir, dir: resolve(nuxt.options.buildDir, 'dist/client') }, ...nuxt.options._layers diff --git a/src/runtime/composables.ts b/src/runtime/composables.ts index a089993e..9cf470ea 100644 --- a/src/runtime/composables.ts +++ b/src/runtime/composables.ts @@ -6,6 +6,7 @@ import type { RuntimeConfig } from '@nuxt/schema' import { sendRedirect } from 'h3' import { defu } from 'defu' import { useRouter } from 'vue-router/composables' +import { joinURL } from 'ufo' import { useNuxtApp } from './app' export { useLazyAsyncData, refreshNuxtData } from './asyncData' @@ -182,7 +183,7 @@ export const navigateTo = (to: RawLocation, options: NavigateToOptions = {}): Pr if (process.server && useNuxtApp().ssrContext) { // Server-side redirection using h3 res from ssrContext const res = useNuxtApp().ssrContext?.res - const redirectLocation = router.resolve(to).route.fullPath + const redirectLocation = joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/') return sendRedirect(res, redirectLocation) } // Client-side redirection using vue-router diff --git a/src/runtime/nitro/paths.ts b/src/runtime/nitro/paths.ts index 72d63f91..9c2b1782 100644 --- a/src/runtime/nitro/paths.ts +++ b/src/runtime/nitro/paths.ts @@ -18,3 +18,6 @@ export function publicAssetsURL (...path: string[]): string { const publicBase = useRuntimeConfig().app.cdnURL || useRuntimeConfig().app.baseURL return path.length ? joinURL(publicBase, ...path) : publicBase } + +globalThis.__publicAssetsURL = publicAssetsURL +globalThis.__buildAssetsURL = buildAssetsURL diff --git a/src/vite/client.ts b/src/vite/client.ts index 10083b91..f87c687c 100644 --- a/src/vite/client.ts +++ b/src/vite/client.ts @@ -1,13 +1,16 @@ -import { resolve } from 'pathe' +import { join, resolve } from 'pathe' import * as vite from 'vite' import createVuePlugin from '@vitejs/plugin-vue2' import PluginLegacy from '@vitejs/plugin-legacy' import { logger } from '@nuxt/kit' -import { joinURL } from 'ufo' +import { joinURL, withLeadingSlash, withoutLeadingSlash, withTrailingSlash } from 'ufo' +import escapeRE from 'escape-string-regexp' +import { getPort } from 'get-port-please' +import defu from 'defu' import { devStyleSSRPlugin } from './plugins/dev-ssr-css' -import { RelativeAssetPlugin } from './plugins/dynamic-base' import { jsxPlugin } from './plugins/jsx' import { ViteBuildContext, ViteOptions } from './types' +import { prepareManifests } from './manifest' export async function buildClient (ctx: ViteBuildContext) { const alias = { @@ -20,6 +23,15 @@ export async function buildClient (ctx: ViteBuildContext) { } const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { + experimental: { + renderBuiltUrl: (filename, { type, hostType }) => { + if (hostType !== 'js' || type === 'asset') { + // In CSS we only use relative paths until we craft a clever runtime CSS hack + return { relative: true } + } + return { runtime: `globalThis.__publicAssetsURL(${JSON.stringify(filename)})` } + } + }, define: { 'process.client': true, 'process.server': false, @@ -41,43 +53,75 @@ export async function buildClient (ctx: ViteBuildContext) { jsxPlugin(), createVuePlugin(ctx.config.vue), PluginLegacy(), - RelativeAssetPlugin(), devStyleSSRPlugin({ - rootDir: ctx.nuxt.options.rootDir, + srcDir: ctx.nuxt.options.srcDir, buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir) }) ], + appType: 'custom', server: { middlewareMode: true } } as ViteOptions) - await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false }) - - // Production build + // In build mode we explicitly override any vite options that vite is relying on + // to detect whether to inject production or development code (such as HMR code) if (!ctx.nuxt.options.dev) { - const start = Date.now() - logger.info('Building client...') - await vite.build(clientConfig) - logger.success(`Client built in ${Date.now() - start}ms`) - return + clientConfig.server.hmr = false } - // Create development server - const viteServer = await vite.createServer(clientConfig) - await ctx.nuxt.callHook('vite:serverCreated', viteServer) - - const viteMiddleware = (req, res, next) => { - // Workaround: vite devmiddleware modifies req.url - const originalURL = req.url - viteServer.middlewares.handle(req, res, (err) => { - req.url = originalURL - next(err) + if (clientConfig.server.hmr !== false) { + const hmrPortDefault = 24678 // Vite's default HMR port + const hmrPort = await getPort({ + port: hmrPortDefault, + ports: Array.from({ length: 20 }, (_, i) => hmrPortDefault + 1 + i) + }) + clientConfig.server.hmr = defu(clientConfig.server.hmr as vite.HmrOptions, { + // https://github.com/nuxt/framework/issues/4191 + protocol: 'ws', + port: hmrPort }) } - await ctx.nuxt.callHook('server:devMiddleware', viteMiddleware) - ctx.nuxt.hook('close', async () => { - await viteServer.close() + // We want to respect users' own rollup output options + ctx.config.build.rollupOptions = defu(ctx.config.build.rollupOptions, { + output: { + // https://github.com/vitejs/vite/tree/main/packages/vite/src/node/build.ts#L464-L478 + assetFileNames: ctx.nuxt.options.dev ? undefined : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[name].[hash].[ext]')), + chunkFileNames: ctx.nuxt.options.dev ? undefined : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[name].[hash].js')), + entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[name].[hash].js')) + } }) + + await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false }) + + if (ctx.nuxt.options.dev) { + // Dev + const viteServer = await vite.createServer(clientConfig) + ctx.clientServer = viteServer + await ctx.nuxt.callHook('vite:serverCreated', viteServer, { isClient: true, isServer: false }) + const baseURL = joinURL(ctx.nuxt.options.app.baseURL.replace(/^\./, '') || '/', ctx.nuxt.options.app.buildAssetsDir) + const BASE_RE = new RegExp(`^${escapeRE(withTrailingSlash(withLeadingSlash(baseURL)))}`) + const viteMiddleware: vite.Connect.NextHandleFunction = (req, res, next) => { + // Workaround: vite devmiddleware modifies req.url + const originalURL = req.url + req.url = req.url.replace(BASE_RE, '/') + viteServer.middlewares.handle(req, res, (err) => { + req.url = originalURL + next(err) + }) + } + await ctx.nuxt.callHook('server:devMiddleware', viteMiddleware) + + ctx.nuxt.hook('close', async () => { + await viteServer.close() + }) + } else { + // Build + const start = Date.now() + await vite.build(clientConfig) + logger.info(`Client built in ${Date.now() - start}ms`) + } + + await prepareManifests(ctx) } diff --git a/src/vite/dev-bundler.ts b/src/vite/dev-bundler.ts index 2b93fba8..91af950d 100644 --- a/src/vite/dev-bundler.ts +++ b/src/vite/dev-bundler.ts @@ -1,7 +1,7 @@ import { pathToFileURL } from 'url' import { existsSync } from 'fs' import { builtinModules } from 'module' -import { resolve } from 'pathe' +import { isAbsolute, resolve } from 'pathe' import * as vite from 'vite' import { ExternalsOptions, isExternal as _isExternal, ExternalsDefaults } from 'externality' import { genDynamicImport, genObjectFromRawEntries } from 'knitwork' @@ -75,7 +75,7 @@ async function transformRequest (opts: TransformOptions, id: string) { // Remove for externals const withoutVersionQuery = id.replace(/\?v=\w+$/, '') if (await isExternal(opts, withoutVersionQuery)) { - const path = builtinModules.includes(withoutVersionQuery.split('node:').pop()) + const path = builtinModules.includes(withoutVersionQuery.split('node:').pop()) || !isAbsolute(withoutVersionQuery) ? withoutVersionQuery : pathToFileURL(withoutVersionQuery).href return { @@ -129,11 +129,11 @@ export async function bundleRequest (opts: TransformOptions, entryURL: string) { // Parents: \n${listIds(chunk.parents)} // Dependencies: \n${listIds(chunk.deps)} // -------------------- -const ${hashId(chunk.id)} = ${chunk.code} +const ${hashId(chunk.id + '-' + chunk.code)} = ${chunk.code} `).join('\n') const manifestCode = `const __modules__ = ${ - genObjectFromRawEntries(chunks.map(chunk => [chunk.id, hashId(chunk.id)])) + genObjectFromRawEntries(chunks.map(chunk => [chunk.id, hashId(chunk.id + '-' + chunk.code)])) }` // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/ssr/ssrModuleLoader.ts diff --git a/src/vite/manifest.ts b/src/vite/manifest.ts index 944893f7..f833b14c 100644 --- a/src/vite/manifest.ts +++ b/src/vite/manifest.ts @@ -1,7 +1,9 @@ -import { resolve } from 'pathe' +import { join, resolve } from 'pathe' import fse from 'fs-extra' import { Manifest as ViteManifest } from 'vite' import { normalizeViteManifest, Manifest, ResourceMeta } from 'vue-bundle-renderer' +import { withoutLeadingSlash, withTrailingSlash } from 'ufo' +import escapeRE from 'escape-string-regexp' import { hash } from './utils' import { ViteBuildContext } from './types' @@ -47,11 +49,26 @@ 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 viteClientManifest: ViteManifest = await fse.readJSON(rDist('client/manifest.json')) + const clientManifest: ViteManifest = await fse.readJSON(rDist('client/manifest.json')) + + // Remove build assets directory from manifest + const buildAssetsDir = withTrailingSlash(withoutLeadingSlash(ctx.nuxt.options.app.buildAssetsDir)) + const BASE_RE = new RegExp(`^${escapeRE(buildAssetsDir)}`) + + for (const key in clientManifest) { + if (clientManifest[key].file) { + clientManifest[key].file = clientManifest[key].file.replace(BASE_RE, '') + } + for (const item of ['css', 'assets']) { + if (clientManifest[key][item]) { + clientManifest[key][item] = clientManifest[key][item].map(i => i.replace(BASE_RE, '')) + } + } + } // Search for polyfill file, we don't include it in the client entry - const polyfillName = Object.values(viteClientManifest).find(entry => entry.file.startsWith('polyfills-legacy.'))?.file - const polyfill = await fse.readFile(rDist('client/' + polyfillName), 'utf-8') + const polyfillName = Object.values(clientManifest).find(entry => entry.file.startsWith('polyfills-legacy.'))?.file + const polyfill = await fse.readFile(rDist('client', buildAssetsDir, polyfillName), 'utf-8') const clientImports = new Set() const clientEntry: Partial>> = { @@ -60,16 +77,16 @@ export async function generateBuildManifest (ctx: ViteBuildContext) { dynamicImports: new Set() } - for (const entry in viteClientManifest) { - if (!viteClientManifest[entry].file.startsWith('polyfills-legacy')) { - clientImports.add(viteClientManifest[entry].file) + for (const entry in clientManifest) { + if (!clientManifest[entry].file.startsWith('polyfills-legacy')) { + clientImports.add(clientManifest[entry].file) for (const key of ['css', 'assets', 'dynamicImports']) { - for (const file of viteClientManifest[entry][key] || []) { + for (const file of clientManifest[entry][key] || []) { clientEntry[key].add(file) } } } - delete viteClientManifest[entry].isEntry + delete clientManifest[entry].isEntry } // @vitejs/plugin-legacy uses SystemJS which need to call `System.import` to load modules @@ -82,7 +99,7 @@ export async function generateBuildManifest (ctx: ViteBuildContext) { ].join('\n') const clientEntryName = 'entry-legacy.' + hash(clientEntryCode) + '.js' - await fse.writeFile(rDist('client', clientEntryName), clientEntryCode, 'utf-8') + await fse.writeFile(rDist('client', buildAssetsDir, clientEntryName), clientEntryCode, 'utf-8') const manifest = normalizeViteManifest({ [clientEntryName]: { @@ -93,7 +110,7 @@ export async function generateBuildManifest (ctx: ViteBuildContext) { assets: [...clientEntry.assets], dynamicImports: [...clientEntry.dynamicImports] }, - ...viteClientManifest + ...clientManifest }) await writeClientManifest(manifest, ctx.nuxt.options.buildDir) diff --git a/src/vite/plugins/dev-ssr-css.ts b/src/vite/plugins/dev-ssr-css.ts index 22248433..930cf97f 100644 --- a/src/vite/plugins/dev-ssr-css.ts +++ b/src/vite/plugins/dev-ssr-css.ts @@ -3,7 +3,7 @@ import { Plugin } from 'vite' import { isCSS } from '../utils' export interface DevStyleSSRPluginOptions { - rootDir: string + srcDir: string buildAssetsURL: string } @@ -18,8 +18,8 @@ export function devStyleSSRPlugin (options: DevStyleSSRPluginOptions): Plugin { } let moduleId = id - if (moduleId.startsWith(options.rootDir)) { - moduleId = moduleId.slice(options.rootDir.length) + if (moduleId.startsWith(options.srcDir)) { + moduleId = moduleId.slice(options.srcDir.length) } // When dev `