UNPKG

vite

Version:

Native-ESM powered web dev build tool

187 lines (164 loc) 4.92 kB
import fs from 'fs' import path from 'path' import { ViteDevServer } from '..' import { cleanUrl, resolveFrom, unwrapId } from '../utils' import { ssrRewriteStacktrace } from './ssrStacktrace' import { ssrExportAllKey, ssrModuleExportsKey, ssrImportKey, ssrImportMetaKey, ssrDynamicImportKey } from './ssrTransform' import { transformRequest } from '../server/transformRequest' interface SSRContext { global: NodeJS.Global } type SSRModule = Record<string, any> const pendingModules = new Map<string, Promise<SSRModule>>() export async function ssrLoadModule( url: string, server: ViteDevServer, context: SSRContext = { global }, urlStack: string[] = [] ): Promise<SSRModule> { url = unwrapId(url) if (urlStack.includes(url)) { server.config.logger.warn( `Circular dependency: ${urlStack.join(' -> ')} -> ${url}` ) return {} } // when we instantiate multiple dependency modules in parallel, they may // point to shared modules. We need to avoid duplicate instantiation attempts // by register every module as pending synchronously so that all subsequent // request to that module are simply waiting on the same promise. const pending = pendingModules.get(url) if (pending) { return pending } const modulePromise = instantiateModule(url, server, context, urlStack) pendingModules.set(url, modulePromise) modulePromise.catch(() => {}).then(() => pendingModules.delete(url)) return modulePromise } async function instantiateModule( url: string, server: ViteDevServer, context: SSRContext = { global }, urlStack: string[] = [] ): Promise<SSRModule> { const { moduleGraph } = server const mod = await moduleGraph.ensureEntryFromUrl(url) if (mod.ssrModule) { return mod.ssrModule } const result = mod.ssrTransformResult || (await transformRequest(url, server, { ssr: true })) if (!result) { // TODO more info? is this even necessary? throw new Error(`failed to load module for ssr: ${url}`) } const ssrModule = { [Symbol.toStringTag]: 'Module' } Object.defineProperty(ssrModule, '__esModule', { value: true }) const isExternal = (dep: string) => dep[0] !== '.' && dep[0] !== '/' await Promise.all( result.deps!.map((dep) => { if (!isExternal(dep)) { return ssrLoadModule(dep, server, context, urlStack.concat(url)) } }) ) const ssrImportMeta = { url } const ssrImport = (dep: string) => { if (isExternal(dep)) { return nodeRequire(dep, mod.file, server.config.root) } else { return moduleGraph.urlToModuleMap.get(unwrapId(dep))?.ssrModule } } const ssrDynamicImport = (dep: string) => { if (isExternal(dep)) { return Promise.resolve(nodeRequire(dep, mod.file, server.config.root)) } else { // #3087 dynamic import vars is ignored at rewrite import path, // so here need process relative path if (dep.startsWith('.')) { dep = path.posix.resolve(path.dirname(url), dep) } return ssrLoadModule(dep, server, context, urlStack.concat(url)) } } function ssrExportAll(sourceModule: any) { for (const key in sourceModule) { if (key !== 'default') { Object.defineProperty(ssrModule, key, { enumerable: true, configurable: true, get() { return sourceModule[key] } }) } } } try { new Function( `global`, ssrModuleExportsKey, ssrImportMetaKey, ssrImportKey, ssrDynamicImportKey, ssrExportAllKey, result.code + `\n//# sourceURL=${mod.url}` )( context.global, ssrModule, ssrImportMeta, ssrImport, ssrDynamicImport, ssrExportAll ) } catch (e) { e.stack = ssrRewriteStacktrace(e.stack, moduleGraph) server.config.logger.error( `Error when evaluating SSR module ${url}:\n${e.stack}`, { timestamp: true, clear: server.config.clearScreen } ) throw e } mod.ssrModule = Object.freeze(ssrModule) return ssrModule } function nodeRequire(id: string, importer: string | null, root: string) { const mod = require(resolve(id, importer, root)) const defaultExport = mod.__esModule ? mod.default : mod // rollup-style default import interop for cjs return new Proxy(mod, { get(mod, prop) { if (prop === 'default') return defaultExport return mod[prop] } }) } const resolveCache = new Map<string, string>() function resolve(id: string, importer: string | null, root: string) { const key = id + importer + root const cached = resolveCache.get(key) if (cached) { return cached } const resolveDir = importer && fs.existsSync(cleanUrl(importer)) ? path.dirname(importer) : root const resolved = resolveFrom(id, resolveDir, true) resolveCache.set(key, resolved) return resolved }