mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(vite): vite dev server bundler (#604)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
21a3e44fdb
commit
bb8c4ff019
@ -54,6 +54,7 @@
|
|||||||
"node-fetch": "^3.0.0",
|
"node-fetch": "^3.0.0",
|
||||||
"ohmyfetch": "^0.3.1",
|
"ohmyfetch": "^0.3.1",
|
||||||
"ora": "^6.0.1",
|
"ora": "^6.0.1",
|
||||||
|
"p-debounce": "^4.0.0",
|
||||||
"pathe": "^0.2.0",
|
"pathe": "^0.2.0",
|
||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^5.6.0",
|
||||||
"rollup": "^2.58.0",
|
"rollup": "^2.58.0",
|
||||||
|
@ -49,7 +49,7 @@ function watch (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bundle (nuxt: Nuxt) {
|
async function bundle (nuxt: Nuxt) {
|
||||||
const useVite = !!nuxt.options.vite
|
const useVite = nuxt.options.vite !== false
|
||||||
const { bundle } = await (useVite ? import('@nuxt/vite-builder') : import('@nuxt/webpack-builder'))
|
const { bundle } = await (useVite ? import('@nuxt/vite-builder') : import('@nuxt/webpack-builder'))
|
||||||
return bundle(nuxt)
|
return bundle(nuxt)
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,7 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
manifest: true,
|
manifest: true,
|
||||||
outDir: resolve(ctx.nuxt.options.buildDir, 'dist/client'),
|
outDir: resolve(ctx.nuxt.options.buildDir, 'dist/client')
|
||||||
assetsDir: '.'
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
replace({ 'process.env': 'import.meta.env' }),
|
replace({ 'process.env': 'import.meta.env' }),
|
||||||
|
184
packages/vite/src/dev-bundler.ts
Normal file
184
packages/vite/src/dev-bundler.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import { builtinModules } from 'module'
|
||||||
|
import { createHash } from 'crypto'
|
||||||
|
import * as vite from 'vite'
|
||||||
|
|
||||||
|
interface TransformChunk {
|
||||||
|
id: string,
|
||||||
|
code: string,
|
||||||
|
deps: string[],
|
||||||
|
parents: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SSRTransformResult {
|
||||||
|
code: string,
|
||||||
|
map: object,
|
||||||
|
deps: string[]
|
||||||
|
dynamicDeps: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transformRequest (viteServer: vite.ViteDevServer, id) {
|
||||||
|
// Virtual modules start with `\0`
|
||||||
|
if (id && id.startsWith('/@id/__x00__')) {
|
||||||
|
id = '\0' + id.slice('/@id/__x00__'.length)
|
||||||
|
}
|
||||||
|
if (id && id.startsWith('/@id/')) {
|
||||||
|
id = id.slice('/@id/'.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Externals
|
||||||
|
if (builtinModules.includes(id) || id.includes('node_modules')) {
|
||||||
|
return {
|
||||||
|
code: `(global, exports, importMeta, ssrImport, ssrDynamicImport, ssrExportAll) => import('${id.replace(/^\/@fs/, '')}').then(r => { ssrExportAll(r) })`,
|
||||||
|
deps: [],
|
||||||
|
dynamicDeps: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform
|
||||||
|
const res: SSRTransformResult = await viteServer.transformRequest(id, { ssr: true }).catch((err) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(`[SSR] Error transforming ${id}: ${err}`)
|
||||||
|
// console.error(err)
|
||||||
|
}) as SSRTransformResult || { code: '', map: {}, deps: [], dynamicDeps: [] }
|
||||||
|
|
||||||
|
// Wrap into a vite module
|
||||||
|
const code = `async function (global, __vite_ssr_exports__, __vite_ssr_import_meta__, __vite_ssr_import__, __vite_ssr_dynamic_import__, __vite_ssr_exportAll__) {
|
||||||
|
${res.code || '/* empty */'};
|
||||||
|
}`
|
||||||
|
return { code, deps: res.deps || [], dynamicDeps: res.dynamicDeps || [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transformRequestRecursive (viteServer: vite.ViteDevServer, id, parent = '<entry>', chunks: Record<string, TransformChunk> = {}) {
|
||||||
|
if (chunks[id]) {
|
||||||
|
chunks[id].parents.push(parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await transformRequest(viteServer, id)
|
||||||
|
const deps = uniq([...res.deps, ...res.dynamicDeps])
|
||||||
|
|
||||||
|
chunks[id] = {
|
||||||
|
id,
|
||||||
|
code: res.code,
|
||||||
|
deps,
|
||||||
|
parents: [parent]
|
||||||
|
} as TransformChunk
|
||||||
|
for (const dep of deps) {
|
||||||
|
await transformRequestRecursive(viteServer, dep, id, chunks)
|
||||||
|
}
|
||||||
|
return Object.values(chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function bundleRequest (viteServer: vite.ViteDevServer, entryURL) {
|
||||||
|
const chunks = await transformRequestRecursive(viteServer, entryURL)
|
||||||
|
|
||||||
|
const listIds = ids => ids.map(id => `// - ${id} (${hashId(id)})`).join('\n')
|
||||||
|
const chunksCode = chunks.map(chunk => `
|
||||||
|
// --------------------
|
||||||
|
// Request: ${chunk.id}
|
||||||
|
// Parents: \n${listIds(chunk.parents)}
|
||||||
|
// Dependencies: \n${listIds(chunk.deps)}
|
||||||
|
// --------------------
|
||||||
|
const ${hashId(chunk.id)} = ${chunk.code}
|
||||||
|
`).join('\n')
|
||||||
|
|
||||||
|
const manifestCode = 'const __modules__ = {\n' +
|
||||||
|
chunks.map(chunk => ` '${chunk.id}': ${hashId(chunk.id)}`).join(',\n') + '\n}'
|
||||||
|
|
||||||
|
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/ssr/ssrModuleLoader.ts
|
||||||
|
const ssrModuleLoader = `
|
||||||
|
const __pendingModules__ = new Map()
|
||||||
|
const __pendingImports__ = new Map()
|
||||||
|
const __ssrContext__ = { global: {} }
|
||||||
|
|
||||||
|
function __ssrLoadModule__(url, urlStack = []) {
|
||||||
|
const pendingModule = __pendingModules__.get(url)
|
||||||
|
if (pendingModule) { return pendingModule }
|
||||||
|
const modulePromise = __instantiateModule__(url, urlStack)
|
||||||
|
__pendingModules__.set(url, modulePromise)
|
||||||
|
modulePromise.catch(() => { __pendingModules__.delete(url) })
|
||||||
|
.finally(() => { __pendingModules__.delete(url) })
|
||||||
|
return modulePromise
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __instantiateModule__(url, urlStack) {
|
||||||
|
const mod = __modules__[url]
|
||||||
|
if (mod.stubModule) { return mod.stubModule }
|
||||||
|
const stubModule = { [Symbol.toStringTag]: 'Module' }
|
||||||
|
Object.defineProperty(stubModule, '__esModule', { value: true })
|
||||||
|
mod.stubModule = stubModule
|
||||||
|
const importMeta = { url, hot: { accept() {} } }
|
||||||
|
urlStack = urlStack.concat(url)
|
||||||
|
const isCircular = url => urlStack.includes(url)
|
||||||
|
const pendingDeps = []
|
||||||
|
const ssrImport = async (dep) => {
|
||||||
|
// TODO: Handle externals if dep[0] !== '.' | '/'
|
||||||
|
if (!isCircular(dep) && !__pendingImports__.get(dep)?.some(isCircular)) {
|
||||||
|
pendingDeps.push(dep)
|
||||||
|
if (pendingDeps.length === 1) {
|
||||||
|
__pendingImports__.set(url, pendingDeps)
|
||||||
|
}
|
||||||
|
await __ssrLoadModule__(dep, urlStack)
|
||||||
|
if (pendingDeps.length === 1) {
|
||||||
|
__pendingImports__.delete(url)
|
||||||
|
} else {
|
||||||
|
pendingDeps.splice(pendingDeps.indexOf(dep), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return __modules__[dep].stubModule
|
||||||
|
}
|
||||||
|
function ssrDynamicImport (dep) {
|
||||||
|
// TODO: Handle dynamic import starting with . relative to url
|
||||||
|
return ssrImport(dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ssrExportAll(sourceModule) {
|
||||||
|
for (const key in sourceModule) {
|
||||||
|
if (key !== 'default') {
|
||||||
|
try {
|
||||||
|
Object.defineProperty(stubModule, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get() { return sourceModule[key] }
|
||||||
|
})
|
||||||
|
} catch (_err) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mod(
|
||||||
|
__ssrContext__.global,
|
||||||
|
stubModule,
|
||||||
|
importMeta,
|
||||||
|
ssrImport,
|
||||||
|
ssrDynamicImport,
|
||||||
|
ssrExportAll
|
||||||
|
)
|
||||||
|
|
||||||
|
return stubModule
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const code = [
|
||||||
|
chunksCode,
|
||||||
|
manifestCode,
|
||||||
|
ssrModuleLoader,
|
||||||
|
`export default await __ssrLoadModule__('${entryURL}')`
|
||||||
|
].join('\n\n')
|
||||||
|
|
||||||
|
return { code }
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashId (id: string) {
|
||||||
|
return '$id_' + hash(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash (input: string, length = 8) {
|
||||||
|
return createHash('sha256')
|
||||||
|
.update(input)
|
||||||
|
.digest('hex')
|
||||||
|
.substr(0, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uniq<T> (arr: T[]): T[] {
|
||||||
|
return Array.from(new Set(arr))
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import * as vite from 'vite'
|
import * as vite from 'vite'
|
||||||
import vuePlugin from '@vitejs/plugin-vue'
|
import vuePlugin from '@vitejs/plugin-vue'
|
||||||
|
import fse from 'fs-extra'
|
||||||
|
import pDebounce from 'p-debounce'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import { ViteBuildContext, ViteOptions } from './vite'
|
import { ViteBuildContext, ViteOptions } from './vite'
|
||||||
import { wpfs } from './utils/wpfs'
|
import { wpfs } from './utils/wpfs'
|
||||||
import { cacheDirPlugin } from './plugins/cache-dir'
|
import { cacheDirPlugin } from './plugins/cache-dir'
|
||||||
|
import { bundleRequest } from './dev-bundler'
|
||||||
|
|
||||||
export async function buildServer (ctx: ViteBuildContext) {
|
export async function buildServer (ctx: ViteBuildContext) {
|
||||||
const serverConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, {
|
const serverConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, {
|
||||||
@ -19,7 +22,14 @@ export async function buildServer (ctx: ViteBuildContext) {
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/server')
|
'#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/server'),
|
||||||
|
// Alias vue
|
||||||
|
'vue/server-renderer': 'vue/server-renderer',
|
||||||
|
'vue/compiler-sfc': 'vue/compiler-sfc',
|
||||||
|
'@vue/reactivity': `@vue/reactivity/dist/reactivity.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`,
|
||||||
|
'@vue/shared': `@vue/shared/dist/shared.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`,
|
||||||
|
'vue-router': `vue-router/dist/vue-router.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`,
|
||||||
|
vue: `vue/dist/vue.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ssr: {
|
ssr: {
|
||||||
@ -57,36 +67,49 @@ export async function buildServer (ctx: ViteBuildContext) {
|
|||||||
|
|
||||||
const onBuild = () => ctx.nuxt.callHook('build:resources', wpfs)
|
const onBuild = () => ctx.nuxt.callHook('build:resources', wpfs)
|
||||||
|
|
||||||
|
// Production build
|
||||||
|
if (!ctx.nuxt.options.dev) {
|
||||||
|
const start = Date.now()
|
||||||
|
consola.info('Building server...')
|
||||||
|
await vite.build(serverConfig)
|
||||||
|
await onBuild()
|
||||||
|
consola.success(`Server built in ${Date.now() - start}ms`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctx.nuxt.options.ssr) {
|
if (!ctx.nuxt.options.ssr) {
|
||||||
await onBuild()
|
await onBuild()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastBuild = 0
|
// Start development server
|
||||||
const build = async () => {
|
const viteServer = await vite.createServer(serverConfig)
|
||||||
let start = Date.now()
|
|
||||||
// debounce
|
// Close server on exit
|
||||||
if (start - lastBuild < 300) {
|
ctx.nuxt.hook('close', () => viteServer.close())
|
||||||
await sleep(300 - (start - lastBuild) + 1)
|
|
||||||
start = Date.now()
|
// Initialize plugins
|
||||||
if (start - lastBuild < 300) {
|
await viteServer.pluginContainer.buildStart({})
|
||||||
return
|
|
||||||
}
|
// Build and watch
|
||||||
}
|
const _doBuild = async () => {
|
||||||
lastBuild = start
|
const start = Date.now()
|
||||||
await vite.build(serverConfig)
|
const { code } = await bundleRequest(viteServer, resolve(ctx.nuxt.options.appDir, 'entry'))
|
||||||
|
await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8')
|
||||||
|
const time = (Date.now() - start)
|
||||||
|
consola.success(`Vite server built in ${time}ms`)
|
||||||
await onBuild()
|
await onBuild()
|
||||||
consola.info(`Server built in ${Date.now() - start}ms`)
|
|
||||||
}
|
}
|
||||||
|
const doBuild = pDebounce(_doBuild, 100)
|
||||||
|
|
||||||
await build()
|
// Initial build
|
||||||
|
await _doBuild()
|
||||||
|
|
||||||
ctx.nuxt.hook('builder:watch', () => build())
|
// Watch
|
||||||
ctx.nuxt.hook('app:templatesGenerated', () => build())
|
viteServer.watcher.on('all', (_event, file) => {
|
||||||
}
|
if (file.indexOf(ctx.nuxt.options.buildDir) === 0) { return }
|
||||||
|
doBuild()
|
||||||
function sleep (ms:number) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, ms)
|
|
||||||
})
|
})
|
||||||
|
// ctx.nuxt.hook('builder:watch', () => doBuild())
|
||||||
|
ctx.nuxt.hook('app:templatesGenerated', () => doBuild())
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { defineNuxtConfig } from '@nuxt/kit'
|
import { defineNuxtConfig } from '@nuxt/kit'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
// vite: true
|
|
||||||
})
|
})
|
||||||
|
@ -1566,6 +1566,7 @@ __metadata:
|
|||||||
node-fetch: ^3.0.0
|
node-fetch: ^3.0.0
|
||||||
ohmyfetch: ^0.3.1
|
ohmyfetch: ^0.3.1
|
||||||
ora: ^6.0.1
|
ora: ^6.0.1
|
||||||
|
p-debounce: ^4.0.0
|
||||||
pathe: ^0.2.0
|
pathe: ^0.2.0
|
||||||
pretty-bytes: ^5.6.0
|
pretty-bytes: ^5.6.0
|
||||||
rollup: ^2.58.0
|
rollup: ^2.58.0
|
||||||
@ -10395,6 +10396,13 @@ fsevents@~2.3.2:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"p-debounce@npm:^4.0.0":
|
||||||
|
version: 4.0.0
|
||||||
|
resolution: "p-debounce@npm:4.0.0"
|
||||||
|
checksum: 7f796f6ed264cb964b83601e70c4c0d94dd52d54e1361400ee80df7527217a59074aa14ab746d4d3089b352181c4ffb5329158fd679cb4b4b52c208574e9e56d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"p-finally@npm:^1.0.0":
|
"p-finally@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "p-finally@npm:1.0.0"
|
resolution: "p-finally@npm:1.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user