Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: migrate to vite 3 #480

Merged
merged 12 commits into from Sep 26, 2022
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Expand Up @@ -84,10 +84,10 @@ jobs:
- name: Test (fixtures)
run: yarn test:fixtures

# - name: Test (fixtures with dev)
# run: yarn test:fixtures:dev
# env:
# NODE_OPTIONS: --max-old-space-size=8192
- name: Test (fixtures with dev)
run: yarn test:fixtures:dev
env:
NODE_OPTIONS: --max-old-space-size=8192

test-fixtures-webpack:
runs-on: ${{ matrix.os }}
Expand Down
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -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.2.0",
"@vitejs/plugin-vue2": "^1.1.2",
"acorn": "^8.8.0",
"cookie-es": "^0.5.0",
Expand Down Expand Up @@ -69,12 +69,13 @@
"scule": "^0.3.2",
"semver": "^7.3.7",
"std-env": "^3.2.1",
"terser": "^5.15.0",
"ufo": "^0.8.5",
"unctx": "^2.0.2",
"unimport": "^0.6.7",
"unplugin": "^0.9.6",
"untyped": "^0.5.0",
"vite": "^2.9.14",
"vite": "~3.1.3",
"vue-bundle-renderer": "^0.4.3"
},
"devDependencies": {
Expand Down
4 changes: 0 additions & 4 deletions renovate.json
Expand Up @@ -3,10 +3,6 @@
"extends": [
"@nuxtjs"
],
"ignoreDeps": [
"vitest",
"vite"
],
"packageRules": [
{
"matchPackageNames": ["vue"],
Expand Down
3 changes: 1 addition & 2 deletions src/module.ts
@@ -1,7 +1,6 @@
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 { BridgeConfig } from '../types'
import { setupNitroBridge } from './nitro'
import { setupAppBridge } from './app'
Expand Down
2 changes: 1 addition & 1 deletion src/nitro.ts
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/composables.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -166,7 +167,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
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/nitro/paths.ts
Expand Up @@ -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
108 changes: 78 additions & 30 deletions src/vite/client.ts
@@ -1,13 +1,17 @@
import { resolve } from 'pathe'
import * as vite from 'vite'
import { join, resolve } from 'pathe'
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 type { ServerOptions, Connect, InlineConfig } from 'vite'
import defu from 'defu'
import PluginLegacy from './stub-legacy.cjs'
import vite from './stub-vite.cjs'
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 = {
Expand All @@ -19,7 +23,16 @@ export async function buildClient (ctx: ViteBuildContext) {
: `defaultexport:${p.src}`
}

const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, {
const clientConfig: 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,
Expand All @@ -28,7 +41,8 @@ export async function buildClient (ctx: ViteBuildContext) {
},
cacheDir: resolve(ctx.nuxt.options.rootDir, 'node_modules/.cache/vite/client'),
resolve: {
alias
alias,
dedupe: ['vue']
},
build: {
rollupOptions: {
Expand All @@ -41,43 +55,77 @@ 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 && 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 = defu(clientConfig.server, <ServerOptions> {
https: ctx.nuxt.options.server.https,
hmr: {
protocol: ctx.nuxt.options.server.https ? 'wss' : '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: 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: unknown) => {
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)
}
13 changes: 6 additions & 7 deletions src/vite/dev-bundler.ts
@@ -1,10 +1,10 @@
import { pathToFileURL } from 'url'
import { existsSync } from 'fs'
import { builtinModules } from 'module'
import { resolve } from 'pathe'
import * as vite from 'vite'
import { isAbsolute, resolve } from 'pathe'
import { ExternalsOptions, isExternal as _isExternal, ExternalsDefaults } from 'externality'
import { genDynamicImport, genObjectFromRawEntries } from 'knitwork'
import vite from './stub-vite.cjs'
import { hashId, uniq } from './utils'

export interface TransformChunk {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -129,12 +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)]))
}`
const manifestCode = `const __modules__ = ${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
const ssrModuleLoader = `
Expand Down
39 changes: 28 additions & 11 deletions src/vite/manifest.ts
@@ -1,7 +1,9 @@
import { resolve } from 'pathe'
import fse from 'fs-extra'
import { Manifest as ViteManifest } from 'vite'
import type { 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'

Expand Down Expand Up @@ -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<string>()
const clientEntry: Partial<Record<keyof ResourceMeta, Set<string>>> = {
Expand All @@ -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
Expand All @@ -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]: {
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/vite/plugins/dev-ssr-css.ts
@@ -1,9 +1,9 @@
import { joinURL } from 'ufo'
import { Plugin } from 'vite'
import type { Plugin } from 'vite'
import { isCSS } from '../utils'

export interface DevStyleSSRPluginOptions {
rootDir: string
srcDir: string
buildAssetsURL: string
}

Expand All @@ -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 `<style>` is injected, remove the `<link>` styles from manifest
Expand Down