mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(vite): experimental vite-node
support (#2795)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
5d58ef48af
commit
ac40c9746c
@ -30,6 +30,7 @@
|
||||
"jiti": "^1.13.0",
|
||||
"nitropack-dev": "link:../nitropack",
|
||||
"nuxt3": "workspace:./packages/nuxt3",
|
||||
"vite": "^2.8.4",
|
||||
"unbuild": "^0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -15,12 +15,12 @@ const getClientManifest = cachedImport(() => import('#build/dist/server/client.m
|
||||
const getSSRApp = !process.env.NUXT_NO_SSR && cachedImport(() => import('#build/dist/server/server.mjs'))
|
||||
|
||||
const getSSRRenderer = cachedResult(async () => {
|
||||
// Load client manifest
|
||||
const clientManifest = await getClientManifest()
|
||||
if (!clientManifest) { throw new Error('client.manifest is not available') }
|
||||
// Load server bundle
|
||||
const createSSRApp = await getSSRApp()
|
||||
if (!createSSRApp) { throw new Error('Server bundle is not available') }
|
||||
// Load client manifest
|
||||
const clientManifest = await getClientManifest()
|
||||
if (!clientManifest) { throw new Error('client.manifest is not available') }
|
||||
// Create renderer
|
||||
const { renderToString } = await import('#nitro-renderer')
|
||||
return createRenderer((createSSRApp), { clientManifest, renderToString, publicPath: buildAssetsURL() }).renderToString
|
||||
|
@ -3,5 +3,11 @@ export default {
|
||||
* Set to true to generate an async entrypoint for the Vue bundle (for module federation support).
|
||||
* @version 3
|
||||
*/
|
||||
asyncEntry: false
|
||||
asyncEntry: false,
|
||||
|
||||
/**
|
||||
* Use vite-node for on-demand server chunk loading
|
||||
* @version 3
|
||||
*/
|
||||
viteNode: process.env.EXPERIMENTAL_VITE_NODE ? true : false
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import { defineBuildConfig } from 'unbuild'
|
||||
export default defineBuildConfig({
|
||||
declaration: true,
|
||||
entries: [
|
||||
'src/index'
|
||||
'src/index',
|
||||
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' }
|
||||
],
|
||||
dependencies: [
|
||||
'vue'
|
||||
|
@ -43,7 +43,8 @@
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"ufo": "^0.7.11",
|
||||
"unplugin": "^0.4.0",
|
||||
"vite": "^2.8.6"
|
||||
"vite": "^2.8.6",
|
||||
"vite-node": "^0.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.2.31"
|
||||
|
@ -12,6 +12,7 @@ import type { ViteBuildContext, ViteOptions } from './vite'
|
||||
import { writeManifest } from './manifest'
|
||||
import { devStyleSSRPlugin } from './plugins/dev-ssr-css'
|
||||
import { DynamicBasePlugin, RelativeAssetPlugin } from './plugins/dynamic-base'
|
||||
import { viteNodePlugin } from './vite-node'
|
||||
|
||||
export async function buildClient (ctx: ViteBuildContext) {
|
||||
const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, {
|
||||
@ -44,7 +45,8 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
devStyleSSRPlugin({
|
||||
rootDir: ctx.nuxt.options.rootDir,
|
||||
buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir)
|
||||
})
|
||||
}),
|
||||
viteNodePlugin(ctx)
|
||||
],
|
||||
server: {
|
||||
middlewareMode: true
|
||||
@ -59,6 +61,7 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false })
|
||||
|
||||
const viteServer = await vite.createServer(clientConfig)
|
||||
ctx.clientServer = viteServer
|
||||
await ctx.nuxt.callHook('vite:serverCreated', viteServer)
|
||||
|
||||
const viteMiddleware: Connect.NextHandleFunction = (req, res, next) => {
|
||||
|
54
packages/vite/src/runtime/server.mjs
Normal file
54
packages/vite/src/runtime/server.mjs
Normal file
@ -0,0 +1,54 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { ViteNodeRunner } from 'vite-node/client'
|
||||
import { dirname, join } from 'pathe'
|
||||
|
||||
const entry = '__NUXT_SERVER_ENTRY__'
|
||||
const url = '__NUXT_SERVER_FETCH_URL__'
|
||||
const base = '__NUXT_SERVER_BASE__'
|
||||
|
||||
const runner = new ViteNodeRunner({
|
||||
root: process.cwd(),
|
||||
base,
|
||||
async fetchModule (id) {
|
||||
return await $fetch(url, {
|
||||
method: 'POST',
|
||||
body: { id }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const IS_CSS_RE = /\.(css|postcss|sass|scss|less|stylus|styl)(\?[^.]+)?$/
|
||||
function isCSS (file) {
|
||||
return IS_CSS_RE.test(file)
|
||||
}
|
||||
|
||||
async function writeManifest (extraEntries) {
|
||||
const dir = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const entries = [
|
||||
'@vite/client',
|
||||
'entry.mjs',
|
||||
...extraEntries
|
||||
]
|
||||
|
||||
const clientManifest = {
|
||||
publicPath: '',
|
||||
all: entries,
|
||||
initial: entries,
|
||||
async: [],
|
||||
modules: {}
|
||||
}
|
||||
|
||||
await fs.writeFile(join(dir, 'client.manifest.json'), JSON.stringify(clientManifest, null, 2), 'utf8')
|
||||
await fs.writeFile(join(dir, 'client.manifest.mjs'), 'export default ' + JSON.stringify(clientManifest, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
export default (async () => {
|
||||
const { default: render } = await runner.executeFile(entry)
|
||||
const result = await render()
|
||||
const modules = Array.from(runner.moduleCache.keys())
|
||||
// Write CSS modules intro manifest to prevent FOUC
|
||||
await writeManifest(modules.filter(i => isCSS(i)).map(i => i.slice(1)))
|
||||
return result
|
||||
})()
|
@ -1,4 +1,4 @@
|
||||
import { join, resolve, normalize } from 'pathe'
|
||||
import { resolve, join, normalize } from 'pathe'
|
||||
import * as vite from 'vite'
|
||||
import vuePlugin from '@vitejs/plugin-vue'
|
||||
import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
|
||||
@ -9,10 +9,11 @@ import { withoutTrailingSlash } from 'ufo'
|
||||
import { ViteBuildContext, ViteOptions } from './vite'
|
||||
import { wpfs } from './utils/wpfs'
|
||||
import { cacheDirPlugin } from './plugins/cache-dir'
|
||||
import { prepareDevServerEntry } from './vite-node'
|
||||
import { DynamicBasePlugin } from './plugins/dynamic-base'
|
||||
import { isCSS, isDirectory, readDirRecursively } from './utils'
|
||||
import { bundleRequest } from './dev-bundler'
|
||||
import { writeManifest } from './manifest'
|
||||
import { isCSS, isDirectory, readDirRecursively } from './utils'
|
||||
|
||||
export async function buildServer (ctx: ViteBuildContext) {
|
||||
const _resolve = id => resolveModule(id, { paths: ctx.nuxt.options.modulesDir })
|
||||
@ -85,7 +86,7 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
const clientDist = resolve(ctx.nuxt.options.buildDir, 'dist/client')
|
||||
|
||||
// Remove public files that have been duplicated into buildAssetsDir
|
||||
// TODO: Add option to configure this behaviour in vite
|
||||
// TODO: Add option to configure this behavior in vite
|
||||
const publicDir = join(ctx.nuxt.options.srcDir, ctx.nuxt.options.dir.public)
|
||||
let publicFiles: string[] = []
|
||||
if (await isDirectory(publicDir)) {
|
||||
@ -128,6 +129,8 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
|
||||
// Start development server
|
||||
const viteServer = await vite.createServer(serverConfig)
|
||||
ctx.ssrServer = viteServer
|
||||
|
||||
await ctx.nuxt.callHook('vite:serverCreated', viteServer)
|
||||
|
||||
// Close server on exit
|
||||
@ -136,6 +139,10 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
// Initialize plugins
|
||||
await viteServer.pluginContainer.buildStart({})
|
||||
|
||||
if (ctx.nuxt.options.experimental.viteNode) {
|
||||
logger.info('Using experimental vite-node server...')
|
||||
await prepareDevServerEntry(ctx)
|
||||
} else {
|
||||
// Build and watch
|
||||
const _doBuild = async () => {
|
||||
const start = Date.now()
|
||||
@ -160,4 +167,5 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
})
|
||||
// ctx.nuxt.hook('builder:watch', () => doBuild())
|
||||
ctx.nuxt.hook('app:templatesGenerated', () => doBuild())
|
||||
}
|
||||
}
|
||||
|
87
packages/vite/src/vite-node.ts
Normal file
87
packages/vite/src/vite-node.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { IncomingMessage } from 'http'
|
||||
import { ViteNodeServer } from 'vite-node/server'
|
||||
import fse from 'fs-extra'
|
||||
import { resolve } from 'pathe'
|
||||
import { addServerMiddleware } from '@nuxt/kit'
|
||||
import type { Connect, Plugin as VitePlugin } from 'vite'
|
||||
import { distDir } from './dirs'
|
||||
import type { ViteBuildContext } from './vite'
|
||||
|
||||
// TODO: Remove this in favor of registerViteNodeMiddleware
|
||||
// after Nitropack or h3 fixed for adding middlewares after setup
|
||||
export function viteNodePlugin (ctx: ViteBuildContext): VitePlugin {
|
||||
return {
|
||||
name: 'nuxt:vite-node-server',
|
||||
enforce: 'pre',
|
||||
configureServer (server) {
|
||||
server.middlewares.use('/__nuxt_vite_node__', createViteNodeMiddleware(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function registerViteNodeMiddleware (ctx: ViteBuildContext) {
|
||||
addServerMiddleware({
|
||||
route: '/__nuxt_vite_node__/',
|
||||
handle: createViteNodeMiddleware(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
function createViteNodeMiddleware (ctx: ViteBuildContext): Connect.NextHandleFunction {
|
||||
let node: ViteNodeServer | undefined
|
||||
return async (req, res, next) => {
|
||||
if (!node && ctx.ssrServer) {
|
||||
node = new ViteNodeServer(ctx.ssrServer, {
|
||||
deps: {
|
||||
inline: [
|
||||
...ctx.nuxt.options.build.transpile as string[]
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!node) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const body = await getBodyJson(req) || {}
|
||||
const { id } = body
|
||||
if (!id) {
|
||||
res.statusCode = 400
|
||||
res.end()
|
||||
} else {
|
||||
res.write(JSON.stringify(await node.fetchModule(id)))
|
||||
res.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareDevServerEntry (ctx: ViteBuildContext) {
|
||||
const entryPath = resolve(ctx.nuxt.options.appDir, 'entry.async')
|
||||
const raw = await fse.readFile(resolve(distDir, 'runtime/server.mjs'), 'utf-8')
|
||||
const host = ctx.nuxt.options.server.host || 'localhost'
|
||||
const port = ctx.nuxt.options.server.port || '3000'
|
||||
const protocol = ctx.nuxt.options.server.https ? 'https' : 'http'
|
||||
const code = raw
|
||||
.replace('__NUXT_SERVER_FETCH_URL__', `${protocol}://${host}:${port}/__nuxt_vite_node__/`)
|
||||
.replace('__NUXT_SERVER_ENTRY__', entryPath)
|
||||
.replace('__NUXT_SERVER_BASE__', ctx.ssrServer.config.base || '/_nuxt/')
|
||||
await fse.writeFile(
|
||||
resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'),
|
||||
code,
|
||||
'utf-8'
|
||||
)
|
||||
}
|
||||
|
||||
function getBodyJson (req: IncomingMessage) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
let body = ''
|
||||
req.on('data', (chunk) => { body += chunk })
|
||||
req.on('error', reject)
|
||||
req.on('end', () => {
|
||||
try {
|
||||
resolve(JSON.parse(body) || {})
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
@ -20,6 +20,8 @@ export interface ViteOptions extends InlineConfig {
|
||||
export interface ViteBuildContext {
|
||||
nuxt: Nuxt
|
||||
config: ViteOptions
|
||||
clientServer?: vite.ViteDevServer
|
||||
ssrServer?: vite.ViteDevServer
|
||||
}
|
||||
|
||||
export async function bundle (nuxt: Nuxt) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<img src="~/assets/logo.svg" class="h-20 mb-4">
|
||||
<img src="./assets/logo.svg" class="h-20 mb-4">
|
||||
<h1 class="greeting">
|
||||
{{ hello }}, <br>Nuxt 3!
|
||||
</h1>
|
||||
|
26
yarn.lock
26
yarn.lock
@ -3387,6 +3387,7 @@ __metadata:
|
||||
unbuild: latest
|
||||
unplugin: ^0.4.0
|
||||
vite: ^2.8.6
|
||||
vite-node: ^0.6.0
|
||||
vue: 3.2.31
|
||||
peerDependencies:
|
||||
vue: 3.2.31
|
||||
@ -13386,7 +13387,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"kolorist@npm:^1.5.0":
|
||||
"kolorist@npm:^1.5.0, kolorist@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "kolorist@npm:1.5.1"
|
||||
checksum: c113be08834fc03a24699612141c79879fceba9ff9765ad500507fb594ee4fa3465a3453ea90bbc9b0dd82f7ba5dbd79814da28e9ebaf8da27266a0088ba2714
|
||||
@ -21677,6 +21678,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-node@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "vite-node@npm:0.6.0"
|
||||
dependencies:
|
||||
kolorist: ^1.5.1
|
||||
minimist: ^1.2.5
|
||||
mlly: ^0.4.3
|
||||
pathe: ^0.2.0
|
||||
vite: ^2.8.4
|
||||
bin:
|
||||
vite-node: vite-node.mjs
|
||||
checksum: 0440416b788c083af43a128242a1e5bb28ffde00867707a4251902aae223cb730c8ec6d4fc1901a00aef551d3c8f5676f4705a361584c0f5876eae952389b4c2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-plugin-vue2@npm:^1.9.3":
|
||||
version: 1.9.3
|
||||
resolution: "vite-plugin-vue2@npm:1.9.3"
|
||||
@ -21708,9 +21724,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:^2.7.10, vite@npm:^2.8.2, vite@npm:^2.8.6":
|
||||
version: 2.8.6
|
||||
resolution: "vite@npm:2.8.6"
|
||||
"vite@npm:^2.8.4":
|
||||
version: 2.8.4
|
||||
resolution: "vite@npm:2.8.4"
|
||||
dependencies:
|
||||
esbuild: ^0.14.14
|
||||
fsevents: ~2.3.2
|
||||
@ -21733,7 +21749,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 4b02d133892c98362c10214b7ad518d74b59745889197a2ba0b63260ed083fcef75a447e8fb58dbd2af8747386274b36017983d93031254df6ead38701950dcc
|
||||
checksum: 0531ea17d354c35026c87e732d28c777492cc5165c4abdaa507c4894535ecbbfcf447fa3f270bbb160cd7cba8ad319cc86a221be18b2ccd40d8be139f9d7381d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user