feat(vite-node): on-demand manifest generation (#3968)

Co-authored-by: pooya parsa <pyapar@gmail.com>
This commit is contained in:
Anthony Fu 2022-04-12 18:04:55 +08:00 committed by GitHub
parent d2b4e60963
commit e31c604ac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 110 deletions

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
const count = ref(0)
function inc () {
count.value++
}
function dec () {
count.value--
}
</script>
<template>
<NuxtExampleLayout example="experimental/vite-node">
<div>
{{ count }}
<div class="flex gap-1 justify-center">
<NButton @click="inc()">
Inc
</NButton>
<NButton @click="dec()">
Dec
</NButton>
</div>
</div>
</NuxtExampleLayout>
</template>

View File

@ -0,0 +1,10 @@
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
modules: [
'@nuxt/ui'
],
experimental: {
viteNode: true
}
})

View File

@ -0,0 +1,13 @@
{
"name": "example-vite-node",
"private": true,
"scripts": {
"build": "nuxi build",
"dev": "nuxi dev",
"start": "nuxi preview"
},
"devDependencies": {
"@nuxt/ui": "npm:@nuxt/ui-edge@latest",
"nuxt3": "latest"
}
}

View File

@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

View File

@ -0,0 +1,8 @@
import { $fetch } from 'ohmyfetch'
import { getViteNodeOptions } from './vite-node-shared.mjs'
const viteNodeOptions = getViteNodeOptions()
const manifest = await $fetch('/manifest', { baseURL: viteNodeOptions.baseURL })
export default manifest

View File

@ -1,60 +0,0 @@
import { promises as fs } from 'fs'
import { fileURLToPath } from 'url'
import { ViteNodeRunner } from 'vite-node/client'
import { dirname, join } from 'pathe'
const url = process.env.NUXT_VITE_SERVER_FETCH
const entry = process.env.NUXT_VITE_SERVER_ENTRY
const base = process.env.NUXT_VITE_SERVER_BASE
const root = process.env.NUXT_VITE_SERVER_ROOT
const runner = new ViteNodeRunner({
root,
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 () {
const dir = dirname(fileURLToPath(import.meta.url))
const entries = [
'@vite/client',
'entry.mjs',
...Array.from(runner.moduleCache.keys())
.filter(i => runner.moduleCache.get(i).exports && isCSS(i))
.map(i => i.slice(1))
]
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')
}
let render
export default async (ssrContext) => {
// Workaround for stub mode
// https://github.com/nuxt/framework/pull/3983
process.server = true
render = render || (await runner.executeFile(entry)).default
const result = await render(ssrContext)
await writeManifest()
return result
}

View File

@ -0,0 +1,8 @@
export interface ViteNodeRuntimeOptions {
baseURL: string,
rootDir: string,
entryPath: string,
base: string
}
export function getViteNodeOptions (): ViteNodeRuntimeOptions

View File

@ -0,0 +1,4 @@
/** @type {import('./vite-node-shared').getViteNodeOptions} */
export function getViteNodeOptions () {
return JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS || '{}')
}

View File

@ -0,0 +1,26 @@
import { ViteNodeRunner } from 'vite-node/client'
import { $fetch } from 'ohmyfetch'
import { getViteNodeOptions } from './vite-node-shared.mjs'
const viteNodeOptions = getViteNodeOptions()
const runner = new ViteNodeRunner({
root: viteNodeOptions.rootDir,
base: viteNodeOptions.base,
async fetchModule (id) {
return await $fetch('/module/' + encodeURI(id), {
baseURL: viteNodeOptions.baseURL
})
}
})
let render
export default async (ssrContext) => {
// Workaround for stub mode
// https://github.com/nuxt/framework/pull/3983
process.server = true
render = render || (await runner.executeFile(viteNodeOptions.entryPath)).default
const result = await render(ssrContext)
return result
}

View File

@ -1,11 +1,12 @@
import { IncomingMessage } from 'http'
import { createApp, createError, defineEventHandler, defineLazyEventHandler } from 'h3'
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 type { Plugin as VitePlugin, ViteDevServer } from 'vite'
import { distDir } from './dirs'
import type { ViteBuildContext } from './vite'
import { isCSS } from './utils'
// TODO: Remove this in favor of registerViteNodeMiddleware
// after Nitropack or h3 fixed for adding middlewares after setup
@ -26,68 +27,82 @@ export function registerViteNodeMiddleware (ctx: ViteBuildContext) {
})
}
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: [
/\/nuxt3\//,
/^#/,
...ctx.nuxt.options.build.transpile as string[]
]
}
})
}
if (!node) {
return next()
}
function getManifest (server: ViteDevServer) {
const ids = Array.from(server.moduleGraph.urlToModuleMap.keys())
.filter(i => isCSS(i))
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()
}
const entries = [
'@vite/client',
'entry.mjs',
...ids.map(i => i.slice(1))
]
return {
publicPath: '',
all: entries,
initial: entries,
async: [],
modules: {}
}
}
function createViteNodeMiddleware (ctx: ViteBuildContext) {
const app = createApp()
app.use('/manifest', defineEventHandler(async () => {
const manifest = await getManifest(ctx.ssrServer)
return manifest
}))
app.use('/module', defineLazyEventHandler(() => {
const node: ViteNodeServer = new ViteNodeServer(ctx.ssrServer, {
deps: {
inline: [
/\/nuxt3\//,
/^#/,
...ctx.nuxt.options.build.transpile as string[]
]
}
})
return async (event) => {
const moduleId = decodeURI(event.req.url).substring(1)
if (moduleId === '/') {
throw createError({ statusCode: 400 })
}
const module = await node.fetchModule(moduleId) as any
return module
}
}))
return app.nodeHandler
}
export async function prepareDevServerEntry (ctx: ViteBuildContext) {
let entryPath = resolve(ctx.nuxt.options.appDir, 'entry.async.mjs')
if (!fse.existsSync(entryPath)) {
entryPath = resolve(ctx.nuxt.options.appDir, 'entry.async')
}
// TODO: Update me
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'
process.env.NUXT_VITE_SERVER_FETCH = `${protocol}://${host}:${port}/__nuxt_vite_node__/`
process.env.NUXT_VITE_SERVER_ENTRY = entryPath
process.env.NUXT_VITE_SERVER_BASE = ctx.ssrServer.config.base || '/_nuxt/'
process.env.NUXT_VITE_SERVER_ROOT = ctx.nuxt.options.rootDir
// Serialize and pass vite-node runtime options
const viteNodeServerOptions = {
baseURL: `${protocol}://${host}:${port}/__nuxt_vite_node__`,
rootDir: ctx.nuxt.options.rootDir,
entryPath,
base: ctx.ssrServer.config.base || '/_nuxt/'
}
process.env.NUXT_VITE_NODE_OPTIONS = JSON.stringify(viteNodeServerOptions)
await fse.copyFile(
resolve(distDir, 'runtime/server.mjs'),
resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs')
await fse.writeFile(
resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'),
`export { default } from ${JSON.stringify(resolve(distDir, 'runtime/vite-node.mjs'))}`
)
await fse.writeFile(
resolve(ctx.nuxt.options.buildDir, 'dist/server/client.manifest.mjs'),
`export { default } from ${JSON.stringify(resolve(distDir, 'runtime/client.manifest.mjs'))}`
)
}
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)
}
})
})
}

View File

@ -10755,6 +10755,15 @@ __metadata:
languageName: unknown
linkType: soft
"example-vite-node@workspace:examples/experimental/vite-node":
version: 0.0.0-use.local
resolution: "example-vite-node@workspace:examples/experimental/vite-node"
dependencies:
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
nuxt3: latest
languageName: unknown
linkType: soft
"example-wasm@workspace:examples/experimental/wasm":
version: 0.0.0-use.local
resolution: "example-wasm@workspace:examples/experimental/wasm"