Skip to content
This repository was archived by the owner on Jan 4, 2023. It is now read-only.
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
189 changes: 189 additions & 0 deletions src/dev-bundler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { builtinModules } from 'module'
import * as vite from 'vite'
import { uniq, hashId } from './utils'

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/defaultexport')) {
id = id.slice('/@id/'.length)
}

// Externals
if (builtinModules.includes(id)) { // || id.includes('node_modules')) {
return {
code: `(global, exports, importMeta, ssrImport, ssrDynamicImport, ssrExportAll) => { ssrExportAll(require('${id.replace(/^\/@fs/, '')}')) }`,
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__) {
const module = __createCJSModule__(__vite_ssr_exports__)
${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: string) {
const chunks = await transformRequestRecursive(viteServer, entryURL)

const listIds = (ids: string[]) => 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
}

function __createCJSModule__(exports) {
return {
get exports() {
if (!exports.default)
exports.default = {}
return exports.default
},
set exports(v) {
exports.default = v
}
}
}

async function __instantiateModule__(url, urlStack) {
const mod = __modules__[url]
if (mod.stubModule) { return mod.stubModule }
let 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
)

// fix for cjs/esm misalignment
if (!('default' in stubModule)) {
stubModule.default = stubModule
}

return stubModule
}
`

const code = [
chunksCode,
manifestCode,
ssrModuleLoader,
`module.exports = function (...args) { return __ssrLoadModule__('${entryURL}').then(i => i.default(...args)) }`
].join('\n\n')

return { code }
}
152 changes: 1 addition & 151 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { builtinModules } from 'module'
import { resolve } from 'pathe'
import * as vite from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
Expand All @@ -9,7 +8,7 @@ import { ViteBuildContext, ViteOptions } from './types'
import { wpfs } from './utils/wpfs'
import { jsxPlugin } from './plugins/jsx'
import { generateDevSSRManifest } from './manifest'
import { uniq, hashId } from './utils'
import { bundleRequest } from './dev-bundler'

export async function buildServer (ctx: ViteBuildContext) {
// Workaround to disable HMR
Expand Down Expand Up @@ -113,152 +112,3 @@ export async function buildServer (ctx: ViteBuildContext) {
doBuild()
})
}

// ---- Vite Dev Bundler POC ----

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/defaultexport')) {
id = id.slice('/@id/'.length)
}

// Externals
if (builtinModules.includes(id)) {
return {
code: `() => require('${id}')`,
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 () {
const exports = {}
const module = { exports }
const __vite_ssr_exports__ = exports;
const __vite_ssr_exportAll__ = __createViteSSRExportAll__(__vite_ssr_exports__)
${res.code || '/* empty */'};
return module.exports;
}`
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)
}

async function bundleRequest (viteServer: vite.ViteDevServer, id) {
const chunks = await transformRequestRecursive(viteServer, id)

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 $chunks = {\n' +
chunks.map(chunk => ` '${chunk.id}': ${hashId(chunk.id)}`).join(',\n') + '\n}'

const dynamicImportCode = `
const __vite_import_cache__ = Object.create({})
function __vite_ssr_import__(id) {
if (!__vite_import_cache__[id]) {
__vite_import_cache__[id] = Promise.resolve($chunks[id]())
.then(mod => {
if (mod && !('default' in mod)) {
mod.default = mod
}
return mod
})
}
return __vite_import_cache__[id]
}
function __vite_ssr_dynamic_import__(id) {
return __vite_ssr_import__(id)
}
`

// https://github.com/vitejs/vite/blob/fb406ce4c0fe6da3333c9d1c00477b2880d46352/packages/vite/src/node/ssr/ssrModuleLoader.ts#L121-L133
const helpers = `
function __createViteSSRExportAll__(ssrModule) {
return (sourceModule) => {
for (const key in sourceModule) {
if (key !== 'default') {
Object.defineProperty(ssrModule, key, {
enumerable: true,
configurable: true,
get() {
return sourceModule[key]
}
})
}
}
}
}
`

// TODO: implement real HMR
const metaPolyfill = `
const __vite_ssr_import_meta__ = {
hot: {
accept() {}
}
}
`

const code = [
metaPolyfill,
chunksCode,
manifestCode,
dynamicImportCode,
helpers,
`module.exports = function (...args) { return ${hashId(id)}().then(r => r.default(...args)) }`
].join('\n\n')

return { code }
}