function rewriteImport(content) { return content.replace(/ from ['"](.*)['"]/g, (s1, s2) => { // 相对路径/绝对路径不处理 if (s2.startsWith('./') || s2.startsWith('/') || s2.startsWith('../')) { return s1; } else { // 堆代码 duidaima.com // 裸模块重写为 /@modules/xxx return ` from '/@modules/${s2}'`; } }); }当服务器收到 JS 文件请求(如/src/main.js)时,会先读取文件内容,通过这个函数将所有裸模块引用重写。例如:
else if (url.startsWith("/@modules/")) { // 提取模块名(如从 /@modules/vue 中拿到 vue) const moduleName = url.replace("/@modules/", ""); // 拼接 node_modules 中该模块的路径 const prefix = path.join(__dirname, "node_modules", moduleName); // 读取依赖的 package.json,找到 module 字段(ESM入口) const modulePath = require(path.join(prefix, "package.json")).module; // 读取入口文件内容 const result = fs.readFileSync(path.join(prefix, modulePath), "utf-8"); // 再次重写文件内的裸模块引用(递归处理) ctx.body = rewriteImport(result); }这里的关键是利用了 npm 包中package.json的module字段 —— 现代 npm 包通常会通过该字段指定 ESM 格式的入口文件(而非 CommonJS 的main字段)。Vite 直接读取这个入口文件,并再次通过rewriteImport处理其中的依赖引用,形成完整的模块加载链。这也是 Vite “预构建” 的简化版逻辑:通过处理裸模块路径,让浏览器能直接加载node_modules中的依赖。
const res = compilerSFC.parse(fs.readFileSync(p, 'utf-8')); // 解析SFC为AST const scriptContent = res.descriptor.script.content; // 提取<script>内容 // 将 export default 改为常量,方便后续注入render函数 const script = scriptContent.replace("export default", "const __script = "); // 支持 scoped: 预计算 scopeId 与样式导入 const hasScoped = res.descriptor.styles && res.descriptor.styles.some(s => s.scoped); const scopeId = hasScoped ? `data-v-${res.descriptor.id}` : ''; const styleImports = (res.descriptor.styles || []) .map((s, i) => `import '${url}?type=style&index=${i}&lang=${s.lang || 'css'}'`) .join('\n'); ctx.body = ` ${rewriteImport(script)} import {render as __render} from '${url}?type=template' ${styleImports} ${hasScoped ? `__script.__scopeId = '${scopeId}'` : ''} __script.render = __render export default __script `;相比之前的版本,这里新增了样式处理的关键逻辑:
else if (query.type === 'template') { const tpl = res.descriptor.template.content; // 提取<template>内容 const hasScoped = res.descriptor.styles && res.descriptor.styles.some(s => s.scoped); const scopeId = hasScoped ? `data-v-${res.descriptor.id}` : undefined; // 编译为render函数(带 scopeId) const render = compilerDOM.compile(tpl, { mode: 'module', scopeId }).code; ctx.body = rewriteImport(render); // 重写其中的模块引用 }当模板对应的组件使用了 scoped 样式时,编译模板时会传入scopeId,让生成的render函数在创建 DOM 元素时自动添加data-v-xxx属性,确保样式只作用于当前组件。
else if (query.type === "style") { const index = parseInt(query.index); const style = res.descriptor.styles[index]; // 获取对应索引的样式块 // 使用 SFC 提供的样式编译,支持 scoped 与预处理器(scss 等) const id = `data-v-${res.descriptor.id}`; const result = await compilerSFC.compileStyleAsync({ source: style.content, // 样式源代码 filename: p, // 文件名(用于错误提示) id, // 用于 scoped 样式的标识 scoped: style.scoped, // 是否为 scoped 样式 preprocessLang: style.lang // 预处理器类型(如 'scss') }); const cssContent = result.code; // 编译后的 CSS // 将 CSS 注入到页面 ctx.body = ` const style = document.createElement('style'); style.setAttribute('type','text/css'); style.textContent = `${cssContent.replace(/`/g, '\`')}`; document.head.appendChild(style); `; }这段代码实现了三个关键功能:
「兼容性处理」:使用模板字符串转义(replace(//g, ''))避免 CSS 中的反引号破坏 JS 语法
<template> <div class="box">{{ msg }}</div> </template> <style scoped> .box { color: red; } </style>会被处理为: