Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

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

Merged
merged 20 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/experimental/vite-node/app.vue
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 10 additions & 0 deletions examples/experimental/vite-node/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
modules: [
'@nuxt/ui'
],
experimental: {
viteNode: true
}
})
13 changes: 13 additions & 0 deletions examples/experimental/vite-node/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
3 changes: 3 additions & 0 deletions examples/experimental/vite-node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}
8 changes: 8 additions & 0 deletions packages/vite/src/runtime/client.manifest.mjs
Original file line number Diff line number Diff line change
@@ -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
60 changes: 0 additions & 60 deletions packages/vite/src/runtime/server.mjs

This file was deleted.

8 changes: 8 additions & 0 deletions packages/vite/src/runtime/vite-node-shared.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface ViteNodeRuntimeOptions {
baseURL: string,
rootDir: string,
entryPath: string,
base: string
}

export function getViteNodeOptions (): ViteNodeRuntimeOptions
4 changes: 4 additions & 0 deletions packages/vite/src/runtime/vite-node-shared.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('./vite-node-shared').getViteNodeOptions} */
export function getViteNodeOptions () {
return JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS || '{}')
}
26 changes: 26 additions & 0 deletions packages/vite/src/runtime/vite-node.mjs
Original file line number Diff line number Diff line change
@@ -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
}
115 changes: 65 additions & 50 deletions packages/vite/src/vite-node.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
})
})
}
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down