闽公网安备 35020302035485号
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>
会被处理为: