You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
550 lines
17 KiB
550 lines
17 KiB
import { performance } from 'node:perf_hooks'; |
|
import { existsSync, promises } from 'node:fs'; |
|
import assert from 'node:assert'; |
|
import { pathToFileURL } from 'node:url'; |
|
import { join, extname, dirname, resolve, relative, normalize } from 'pathe'; |
|
import createDebug from 'debug'; |
|
import { isNodeBuiltin, slash, findNearestPackageData, toArray, withTrailingSlash, normalizeModuleId, toFilePath } from './utils.mjs'; |
|
import { KNOWN_ASSET_RE } from './constants.mjs'; |
|
import { f } from './chunk-browser.mjs'; |
|
import { withInlineSourcemap } from './source-map.mjs'; |
|
import 'node:module'; |
|
import 'node:path'; |
|
|
|
const BUILTIN_EXTENSIONS = /* @__PURE__ */ new Set([".mjs", ".cjs", ".node", ".wasm"]); |
|
const ESM_SYNTAX_RE = /(?:[\s;]|^)(?:import[\s\w*,{}]*from|import\s*["'*{]|export\b\s*(?:[*{]|default|class|type|function|const|var|let|async function)|import\.meta\b)/m; |
|
const ESM_EXT_RE = /\.(es|esm|esm-browser|esm-bundler|es6|module)\.js$/; |
|
const ESM_FOLDER_RE = /\/(es|esm)\/(.*\.js)$/; |
|
const COMMENT_RE = /\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm; |
|
const defaultInline = [ |
|
/virtual:/, |
|
/\.[mc]?ts$/, |
|
// special Vite query strings |
|
/[?&](init|raw|url|inline)\b/, |
|
// Vite returns a string for assets imports, even if it's inside "node_modules" |
|
KNOWN_ASSET_RE |
|
]; |
|
const depsExternal = [ |
|
/\/node_modules\/.*\.cjs\.js$/, |
|
/\/node_modules\/.*\.mjs$/ |
|
]; |
|
function guessCJSversion(id) { |
|
if (id.match(ESM_EXT_RE)) { |
|
for (const i of [ |
|
id.replace(ESM_EXT_RE, ".mjs"), |
|
id.replace(ESM_EXT_RE, ".umd.js"), |
|
id.replace(ESM_EXT_RE, ".cjs.js"), |
|
id.replace(ESM_EXT_RE, ".js") |
|
]) { |
|
if (existsSync(i)) { |
|
return i; |
|
} |
|
} |
|
} |
|
if (id.match(ESM_FOLDER_RE)) { |
|
for (const i of [ |
|
id.replace(ESM_FOLDER_RE, "/umd/$1"), |
|
id.replace(ESM_FOLDER_RE, "/cjs/$1"), |
|
id.replace(ESM_FOLDER_RE, "/lib/$1"), |
|
id.replace(ESM_FOLDER_RE, "/$1") |
|
]) { |
|
if (existsSync(i)) { |
|
return i; |
|
} |
|
} |
|
} |
|
} |
|
async function isValidNodeImport(id) { |
|
const extension = extname(id); |
|
if (BUILTIN_EXTENSIONS.has(extension)) { |
|
return true; |
|
} |
|
if (extension !== ".js") { |
|
return false; |
|
} |
|
id = id.replace("file:///", ""); |
|
const package_ = await findNearestPackageData(dirname(id)); |
|
if (package_.type === "module") { |
|
return true; |
|
} |
|
if (/\.(?:\w+-)?esm?(?:-\w+)?\.js$|\/esm?\//.test(id)) { |
|
return false; |
|
} |
|
const code = await promises.readFile(id, "utf8").catch(() => ""); |
|
return !ESM_SYNTAX_RE.test(code.replace(COMMENT_RE, "")); |
|
} |
|
const _defaultExternalizeCache = /* @__PURE__ */ new Map(); |
|
async function shouldExternalize(id, options, cache = _defaultExternalizeCache) { |
|
if (!cache.has(id)) { |
|
cache.set(id, _shouldExternalize(id, options)); |
|
} |
|
return cache.get(id); |
|
} |
|
async function _shouldExternalize(id, options) { |
|
if (isNodeBuiltin(id)) { |
|
return id; |
|
} |
|
if (id.startsWith("data:") || /^(?:https?:)?\/\//.test(id)) { |
|
return id; |
|
} |
|
id = patchWindowsImportPath(id); |
|
const moduleDirectories = (options == null ? void 0 : options.moduleDirectories) || ["/node_modules/"]; |
|
if (matchExternalizePattern(id, moduleDirectories, options == null ? void 0 : options.inline)) { |
|
return false; |
|
} |
|
if ((options == null ? void 0 : options.inlineFiles) && (options == null ? void 0 : options.inlineFiles.includes(id))) { |
|
return false; |
|
} |
|
if (matchExternalizePattern(id, moduleDirectories, options == null ? void 0 : options.external)) { |
|
return id; |
|
} |
|
if ((options == null ? void 0 : options.cacheDir) && id.includes(options.cacheDir)) { |
|
return id; |
|
} |
|
const isLibraryModule = moduleDirectories.some((dir) => id.includes(dir)); |
|
const guessCJS = isLibraryModule && (options == null ? void 0 : options.fallbackCJS); |
|
id = guessCJS ? guessCJSversion(id) || id : id; |
|
if (matchExternalizePattern(id, moduleDirectories, defaultInline)) { |
|
return false; |
|
} |
|
if (matchExternalizePattern(id, moduleDirectories, depsExternal)) { |
|
return id; |
|
} |
|
if (isLibraryModule && await isValidNodeImport(id)) { |
|
return id; |
|
} |
|
return false; |
|
} |
|
function matchExternalizePattern(id, moduleDirectories, patterns) { |
|
if (patterns == null) { |
|
return false; |
|
} |
|
if (patterns === true) { |
|
return true; |
|
} |
|
for (const ex of patterns) { |
|
if (typeof ex === "string") { |
|
if (moduleDirectories.some((dir) => id.includes(join(dir, ex)))) { |
|
return true; |
|
} |
|
} else { |
|
if (ex.test(id)) { |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
function patchWindowsImportPath(path) { |
|
if (path.match(/^\w:\\/)) { |
|
return `file:///${slash(path)}`; |
|
} else if (path.match(/^\w:\//)) { |
|
return `file:///${path}`; |
|
} else { |
|
return path; |
|
} |
|
} |
|
|
|
function hashCode(s) { |
|
return s.split("").reduce((a, b) => { |
|
a = (a << 5) - a + b.charCodeAt(0); |
|
return a & a; |
|
}, 0); |
|
} |
|
class Debugger { |
|
constructor(root, options) { |
|
this.options = options; |
|
if (options.dumpModules) { |
|
this.dumpDir = resolve( |
|
root, |
|
options.dumpModules === true ? ".vite-node/dump" : options.dumpModules |
|
); |
|
} |
|
if (this.dumpDir) { |
|
if (options.loadDumppedModules) { |
|
console.info( |
|
f.gray(`[vite-node] [debug] load modules from ${this.dumpDir}`) |
|
); |
|
} else { |
|
console.info( |
|
f.gray(`[vite-node] [debug] dump modules to ${this.dumpDir}`) |
|
); |
|
} |
|
} |
|
this.initPromise = this.clearDump(); |
|
} |
|
dumpDir; |
|
initPromise; |
|
externalizeMap = /* @__PURE__ */ new Map(); |
|
async clearDump() { |
|
if (!this.dumpDir) { |
|
return; |
|
} |
|
if (!this.options.loadDumppedModules && existsSync(this.dumpDir)) { |
|
await promises.rm(this.dumpDir, { recursive: true, force: true }); |
|
} |
|
await promises.mkdir(this.dumpDir, { recursive: true }); |
|
} |
|
encodeId(id) { |
|
return `${id.replace(/[^\w@\-]/g, "_").replace(/_+/g, "_")}-${hashCode( |
|
id |
|
)}.js`; |
|
} |
|
async recordExternalize(id, path) { |
|
if (!this.dumpDir) { |
|
return; |
|
} |
|
this.externalizeMap.set(id, path); |
|
await this.writeInfo(); |
|
} |
|
async dumpFile(id, result) { |
|
if (!result || !this.dumpDir) { |
|
return; |
|
} |
|
await this.initPromise; |
|
const name = this.encodeId(id); |
|
return await promises.writeFile( |
|
join(this.dumpDir, name), |
|
`// ${id.replace(/\0/g, "\\0")} |
|
${result.code}`, |
|
"utf-8" |
|
); |
|
} |
|
async loadDump(id) { |
|
if (!this.dumpDir) { |
|
return null; |
|
} |
|
await this.initPromise; |
|
const name = this.encodeId(id); |
|
const path = join(this.dumpDir, name); |
|
if (!existsSync(path)) { |
|
return null; |
|
} |
|
const code = await promises.readFile(path, "utf-8"); |
|
return { |
|
code: code.replace(/^\/\/.*\n/, ""), |
|
map: void 0 |
|
}; |
|
} |
|
async writeInfo() { |
|
if (!this.dumpDir) { |
|
return; |
|
} |
|
const info = JSON.stringify( |
|
{ |
|
time: (/* @__PURE__ */ new Date()).toLocaleString(), |
|
externalize: Object.fromEntries(this.externalizeMap.entries()) |
|
}, |
|
null, |
|
2 |
|
); |
|
return promises.writeFile(join(this.dumpDir, "info.json"), info, "utf-8"); |
|
} |
|
} |
|
|
|
const debugRequest = createDebug("vite-node:server:request"); |
|
class ViteNodeServer { |
|
constructor(server, options = {}) { |
|
this.server = server; |
|
this.options = options; |
|
var _a, _b, _c; |
|
const ssrOptions = server.config.ssr; |
|
options.deps ?? (options.deps = {}); |
|
options.deps.cacheDir = relative( |
|
server.config.root, |
|
options.deps.cacheDir || server.config.cacheDir |
|
); |
|
if (ssrOptions) { |
|
if (ssrOptions.noExternal === true) { |
|
(_a = options.deps).inline ?? (_a.inline = true); |
|
} else if (options.deps.inline !== true) { |
|
(_b = options.deps).inline ?? (_b.inline = []); |
|
const inline = options.deps.inline; |
|
options.deps.inline.push( |
|
...toArray(ssrOptions.noExternal).filter( |
|
(dep) => !inline.includes(dep) |
|
) |
|
); |
|
} |
|
} |
|
if (process.env.VITE_NODE_DEBUG_DUMP) { |
|
options.debug = Object.assign( |
|
{ |
|
dumpModules: !!process.env.VITE_NODE_DEBUG_DUMP, |
|
loadDumppedModules: process.env.VITE_NODE_DEBUG_DUMP === "load" |
|
}, |
|
options.debug ?? {} |
|
); |
|
} |
|
if (options.debug) { |
|
this.debugger = new Debugger(server.config.root, options.debug); |
|
} |
|
if (options.deps.inlineFiles) { |
|
options.deps.inlineFiles = options.deps.inlineFiles.flatMap((file) => { |
|
if (file.startsWith("file://")) { |
|
return file; |
|
} |
|
const resolvedId = resolve(file); |
|
return [resolvedId, pathToFileURL(resolvedId).href]; |
|
}); |
|
} |
|
(_c = options.deps).moduleDirectories ?? (_c.moduleDirectories = []); |
|
const envValue = process.env.VITE_NODE_DEPS_MODULE_DIRECTORIES || process.env.npm_config_VITE_NODE_DEPS_MODULE_DIRECTORIES; |
|
const customModuleDirectories = envValue == null ? void 0 : envValue.split(","); |
|
if (customModuleDirectories) { |
|
options.deps.moduleDirectories.push(...customModuleDirectories); |
|
} |
|
options.deps.moduleDirectories = options.deps.moduleDirectories.map( |
|
(dir) => { |
|
if (!dir.startsWith("/")) { |
|
dir = `/${dir}`; |
|
} |
|
if (!dir.endsWith("/")) { |
|
dir += "/"; |
|
} |
|
return normalize(dir); |
|
} |
|
); |
|
if (!options.deps.moduleDirectories.includes("/node_modules/")) { |
|
options.deps.moduleDirectories.push("/node_modules/"); |
|
} |
|
} |
|
fetchPromiseMap = { |
|
ssr: /* @__PURE__ */ new Map(), |
|
web: /* @__PURE__ */ new Map() |
|
}; |
|
transformPromiseMap = { |
|
ssr: /* @__PURE__ */ new Map(), |
|
web: /* @__PURE__ */ new Map() |
|
}; |
|
durations = { |
|
ssr: /* @__PURE__ */ new Map(), |
|
web: /* @__PURE__ */ new Map() |
|
}; |
|
existingOptimizedDeps = /* @__PURE__ */ new Set(); |
|
fetchCaches = { |
|
ssr: /* @__PURE__ */ new Map(), |
|
web: /* @__PURE__ */ new Map() |
|
}; |
|
fetchCache = /* @__PURE__ */ new Map(); |
|
externalizeCache = /* @__PURE__ */ new Map(); |
|
debugger; |
|
shouldExternalize(id) { |
|
return shouldExternalize(id, this.options.deps, this.externalizeCache); |
|
} |
|
getTotalDuration() { |
|
const ssrDurations = [...this.durations.ssr.values()].flat(); |
|
const webDurations = [...this.durations.web.values()].flat(); |
|
return [...ssrDurations, ...webDurations].reduce((a, b) => a + b, 0); |
|
} |
|
async ensureExists(id) { |
|
if (this.existingOptimizedDeps.has(id)) { |
|
return true; |
|
} |
|
if (existsSync(id)) { |
|
this.existingOptimizedDeps.add(id); |
|
return true; |
|
} |
|
return new Promise((resolve2) => { |
|
setTimeout(() => { |
|
this.ensureExists(id).then(() => { |
|
resolve2(true); |
|
}); |
|
}); |
|
}); |
|
} |
|
async resolveId(id, importer, transformMode) { |
|
if (importer && !importer.startsWith(withTrailingSlash(this.server.config.root))) { |
|
importer = resolve(this.server.config.root, importer); |
|
} |
|
const mode = transformMode ?? (importer && this.getTransformMode(importer) || "ssr"); |
|
return this.server.pluginContainer.resolveId(id, importer, { |
|
ssr: mode === "ssr" |
|
}); |
|
} |
|
getSourceMap(source) { |
|
var _a, _b; |
|
const fetchResult = (_a = this.fetchCache.get(source)) == null ? void 0 : _a.result; |
|
if (fetchResult == null ? void 0 : fetchResult.map) { |
|
return fetchResult.map; |
|
} |
|
const ssrTransformResult = (_b = this.server.moduleGraph.getModuleById(source)) == null ? void 0 : _b.ssrTransformResult; |
|
return (ssrTransformResult == null ? void 0 : ssrTransformResult.map) || null; |
|
} |
|
assertMode(mode) { |
|
assert( |
|
mode === "web" || mode === "ssr", |
|
`"transformMode" can only be "web" or "ssr", received "${mode}".` |
|
); |
|
} |
|
async fetchModule(id, transformMode) { |
|
const mode = transformMode || this.getTransformMode(id); |
|
return this.fetchResult(id, mode).then((r) => { |
|
return this.options.sourcemap !== true ? { ...r, map: void 0 } : r; |
|
}); |
|
} |
|
async fetchResult(id, mode) { |
|
const moduleId = normalizeModuleId(id); |
|
this.assertMode(mode); |
|
const promiseMap = this.fetchPromiseMap[mode]; |
|
if (!promiseMap.has(moduleId)) { |
|
promiseMap.set( |
|
moduleId, |
|
this._fetchModule(moduleId, mode).finally(() => { |
|
promiseMap.delete(moduleId); |
|
}) |
|
); |
|
} |
|
return promiseMap.get(moduleId); |
|
} |
|
async transformRequest(id, filepath = id, transformMode) { |
|
const mode = transformMode || this.getTransformMode(id); |
|
this.assertMode(mode); |
|
const promiseMap = this.transformPromiseMap[mode]; |
|
if (!promiseMap.has(id)) { |
|
promiseMap.set( |
|
id, |
|
this._transformRequest(id, filepath, mode).finally(() => { |
|
promiseMap.delete(id); |
|
}) |
|
); |
|
} |
|
return promiseMap.get(id); |
|
} |
|
async transformModule(id, transformMode) { |
|
if (transformMode !== "web") { |
|
throw new Error( |
|
'`transformModule` only supports `transformMode: "web"`.' |
|
); |
|
} |
|
const normalizedId = normalizeModuleId(id); |
|
const mod = this.server.moduleGraph.getModuleById(normalizedId); |
|
const result = (mod == null ? void 0 : mod.transformResult) || await this.server.transformRequest(normalizedId); |
|
return { |
|
code: result == null ? void 0 : result.code |
|
}; |
|
} |
|
getTransformMode(id) { |
|
var _a, _b, _c, _d; |
|
const withoutQuery = id.split("?")[0]; |
|
if ((_b = (_a = this.options.transformMode) == null ? void 0 : _a.web) == null ? void 0 : _b.some((r) => withoutQuery.match(r))) { |
|
return "web"; |
|
} |
|
if ((_d = (_c = this.options.transformMode) == null ? void 0 : _c.ssr) == null ? void 0 : _d.some((r) => withoutQuery.match(r))) { |
|
return "ssr"; |
|
} |
|
if (withoutQuery.match(/\.([cm]?[jt]sx?|json)$/)) { |
|
return "ssr"; |
|
} |
|
return "web"; |
|
} |
|
getChangedModule(id, file) { |
|
const module = this.server.moduleGraph.getModuleById(id) || this.server.moduleGraph.getModuleById(file); |
|
if (module) { |
|
return module; |
|
} |
|
const _modules = this.server.moduleGraph.getModulesByFile(file); |
|
if (!_modules || !_modules.size) { |
|
return null; |
|
} |
|
const modules = [..._modules]; |
|
let mod = modules[0]; |
|
let latestMax = -1; |
|
for (const m of _modules) { |
|
const timestamp = Math.max( |
|
m.lastHMRTimestamp, |
|
m.lastInvalidationTimestamp |
|
); |
|
if (timestamp > latestMax) { |
|
latestMax = timestamp; |
|
mod = m; |
|
} |
|
} |
|
return mod; |
|
} |
|
async _fetchModule(id, transformMode) { |
|
var _a, _b; |
|
let result; |
|
const cacheDir = (_a = this.options.deps) == null ? void 0 : _a.cacheDir; |
|
if (cacheDir && id.includes(cacheDir)) { |
|
if (!id.startsWith(withTrailingSlash(this.server.config.root))) { |
|
id = join(this.server.config.root, id); |
|
} |
|
const timeout = setTimeout(() => { |
|
throw new Error( |
|
`ViteNodeServer: ${id} not found. This is a bug, please report it.` |
|
); |
|
}, 5e3); |
|
await this.ensureExists(id); |
|
clearTimeout(timeout); |
|
} |
|
const { path: filePath } = toFilePath(id, this.server.config.root); |
|
const moduleNode = this.getChangedModule(id, filePath); |
|
const cache = this.fetchCaches[transformMode].get(filePath); |
|
const timestamp = moduleNode ? Math.max( |
|
moduleNode.lastHMRTimestamp, |
|
moduleNode.lastInvalidationTimestamp |
|
) : 0; |
|
if (cache && (timestamp === 0 || cache.timestamp >= timestamp)) { |
|
return cache.result; |
|
} |
|
const time = Date.now(); |
|
const externalize = await this.shouldExternalize(filePath); |
|
let duration; |
|
if (externalize) { |
|
result = { externalize }; |
|
(_b = this.debugger) == null ? void 0 : _b.recordExternalize(id, externalize); |
|
} else { |
|
const start = performance.now(); |
|
const r = await this._transformRequest(id, filePath, transformMode); |
|
duration = performance.now() - start; |
|
result = { code: r == null ? void 0 : r.code, map: r == null ? void 0 : r.map }; |
|
} |
|
const cacheEntry = { |
|
duration, |
|
timestamp: time, |
|
result |
|
}; |
|
const durations = this.durations[transformMode].get(filePath) || []; |
|
this.durations[transformMode].set(filePath, [...durations, duration ?? 0]); |
|
this.fetchCaches[transformMode].set(filePath, cacheEntry); |
|
this.fetchCache.set(filePath, cacheEntry); |
|
return result; |
|
} |
|
async processTransformResult(filepath, result) { |
|
const mod = this.server.moduleGraph.getModuleById(filepath); |
|
return withInlineSourcemap(result, { |
|
filepath: (mod == null ? void 0 : mod.file) || filepath, |
|
root: this.server.config.root |
|
}); |
|
} |
|
async _transformRequest(id, filepath, transformMode) { |
|
var _a, _b, _c, _d; |
|
debugRequest(id); |
|
let result = null; |
|
if ((_a = this.options.debug) == null ? void 0 : _a.loadDumppedModules) { |
|
result = await ((_b = this.debugger) == null ? void 0 : _b.loadDump(id)) ?? null; |
|
if (result) { |
|
return result; |
|
} |
|
} |
|
if (transformMode === "web") { |
|
result = await this.server.transformRequest(id); |
|
if (result) { |
|
result = await this.server.ssrTransform(result.code, result.map, id); |
|
} |
|
} else { |
|
result = await this.server.transformRequest(id, { ssr: true }); |
|
} |
|
const sourcemap = this.options.sourcemap ?? "inline"; |
|
if (sourcemap === "inline" && result && !id.includes("node_modules")) { |
|
result = await this.processTransformResult(filepath, result); |
|
} |
|
if ((_c = this.options.debug) == null ? void 0 : _c.dumpModules) { |
|
await ((_d = this.debugger) == null ? void 0 : _d.dumpFile(id, result)); |
|
} |
|
return result; |
|
} |
|
} |
|
|
|
export { ViteNodeServer, guessCJSversion, shouldExternalize };
|
|
|