一、概念:processNormalScript 的作用
在 Vue 单文件组件(SFC, Single File Component)的编译阶段中,processNormalScript 用于处理普通的 <script> 块。它主要负责:
- 提取脚本内容与 AST(抽象语法树) ;
- 分析脚本中绑定的变量与导出;
- 在需要时重写默认导出(
export default)结构; - 注入 CSS 变量绑定代码;
- 返回处理后的脚本块结果。
这是 Vue 编译管线中 script transform 阶段的核心函数之一,直接影响到模板与脚本间的响应式联动。
二、原理:执行流程拆解
我们来看完整源码(已格式化与注释):
typescript
import { analyzeScriptBindings } from './analyzeScriptBindings'
import type { ScriptCompileContext } from './context'
import MagicString from 'magic-string'
import { rewriteDefaultAST } from '../rewriteDefault'
import { genNormalScriptCssVarsCode } from '../style/cssVars'
import type { SFCScriptBlock } from '../parse'
export const normalScriptDefaultVar = `__default__`
export function processNormalScript(
ctx: ScriptCompileContext,
scopeId: string,
): SFCScriptBlock {
const script = ctx.descriptor.script!
try {
let content = script.content
let map = script.map
const scriptAst = ctx.scriptAst!
const bindings = analyzeScriptBindings(scriptAst.body)
const { cssVars } = ctx.descriptor
const { genDefaultAs, isProd } = ctx.options
if (cssVars.length || genDefaultAs) {
const defaultVar = genDefaultAs || normalScriptDefaultVar
const s = new MagicString(content)
rewriteDefaultAST(scriptAst.body, s, defaultVar)
content = s.toString()
if (cssVars.length && !ctx.options.templateOptions?.ssr) {
content += genNormalScriptCssVarsCode(
cssVars,
bindings,
scopeId,
!!isProd,
defaultVar,
)
}
if (!genDefaultAs) {
content += `\nexport default ${defaultVar}`
}
}
return {
...script,
content,
map,
bindings,
scriptAst: scriptAst.body,
}
} catch (e: any) {
// silently fallback if parse fails since user may be using custom babel syntax
return script
}
}
🧩 执行步骤逐行拆解:
-
提取脚本与上下文:
iniconst script = ctx.descriptor.script! let content = script.content let map = script.map const scriptAst = ctx.scriptAst!- 从编译上下文
ctx中获取<script>内容与其 AST。 - AST 在之前的编译阶段由
@babel/parser生成。
- 从编译上下文
-
分析脚本绑定变量:
iniconst bindings = analyzeScriptBindings(scriptAst.body)- 调用
analyzeScriptBindings分析顶层作用域中的变量与导出。 - 用于判断哪些变量可能在模板中被引用(如
setup()、data、props等)。
- 调用
-
检查是否需要生成附加代码:
csharpconst { cssVars } = ctx.descriptor const { genDefaultAs, isProd } = ctx.options if (cssVars.length || genDefaultAs) { ... }- 若组件中存在 CSS 变量(
v-bind(...))或要求生成特定默认导出(用于 TS 转译),则进入重写逻辑。
- 若组件中存在 CSS 变量(
-
重写默认导出:
scssconst s = new MagicString(content) rewriteDefaultAST(scriptAst.body, s, defaultVar) content = s.toString()-
使用
MagicString操作源码字符串; -
通过 AST 找到
export default {...}; -
替换为形如:
iniconst __default__ = { ... }以便后续动态注入或修改。
-
-
生成 CSS 变量绑定代码:
scssif (cssVars.length && !ctx.options.templateOptions?.ssr) { content += genNormalScriptCssVarsCode(cssVars, bindings, scopeId, !!isProd, defaultVar) }-
调用
genNormalScriptCssVarsCode动态生成绑定逻辑,如:ini__default__.__cssVars = { '--color': color } -
仅在非 SSR 模式下执行。
-
-
恢复默认导出:
scssif (!genDefaultAs) { content += `\nexport default ${defaultVar}` }- 若没有显式要求更改导出名,则重新导出
__default__。
- 若没有显式要求更改导出名,则重新导出
-
返回处理结果:
arduinoreturn { ...script, content, map, bindings, scriptAst: scriptAst.body, } -
错误处理:
kotlincatch (e: any) { return script }- 当 AST 解析失败(例如使用了 Babel 自定义语法),直接返回原脚本,不中断编译。
三、对比:processNormalScript vs. processScriptSetup
| 项目 | processNormalScript |
processScriptSetup |
|---|---|---|
| 处理目标 | 普通 <script> |
<script setup> |
| AST 解析来源 | ctx.scriptAst |
ctx.scriptSetupAst |
| 是否生成绑定 | 是(用于模板绑定) | 是(但更复杂) |
| 默认导出重写 | 可能(需要 __default__) |
不直接导出,改为编译后的 setup 函数 |
| CSS 变量支持 | 有 | 有 |
| SSR 兼容性 | 受限制(需判断) | 更完善 |
简而言之,processNormalScript 是传统 SFC 的编译逻辑,而 processScriptSetup 是 Vue 3 的现代化编译入口。
四、实践:在自定义编译流程中使用
假设我们要在构建工具中自定义一个简单的 SFC 编译插件,可以在处理阶段调用:
css
import { processNormalScript } from 'vue/compiler-sfc'
const ctx = {
descriptor: {
script: { content: 'export default { data() { return { msg: "hi" } } }' },
cssVars: []
},
scriptAst: parseToAST('export default { data() { return { msg: "hi" } } }'),
options: {}
}
const result = processNormalScript(ctx, 'data-v-123abc')
console.log(result.content)
输出结果:
javascript
const __default__ = { data() { return { msg: "hi" } } }
export default __default__
五、拓展:MagicString 的妙用
MagicString 是一个轻量的源码变换工具,它允许:
- 精确映射(保留 SourceMap);
- 基于偏移操作字符串;
- 避免直接操作 AST 再生成代码的高开销。
Vue 编译器选择它,原因是它在性能与精确度之间达到了理想平衡。
六、潜在问题与注意事项
- 自定义语法不兼容:
若脚本包含 Babel 自定义语法(如 pipeline operator),AST 解析会失败,触发catch分支。 - SourceMap 可能不完全准确:
当MagicString操作过多时,需要重新生成映射,否则调试体验受影响。 - CSS 变量作用域冲突:
若多个组件共享相同scopeId,可能引发样式隔离失效。 - SSR 模式需特殊处理:
由于genNormalScriptCssVarsCode默认跳过 SSR,若需在服务端渲染中使用动态 CSS,需自定义逻辑。
结语
processNormalScript 是 Vue SFC 编译器中的一个基础模块,尽管代码量不大,但它承担了 AST 操作、导出重写、CSS 注入 等核心功能,是 Vue 组件编译链路中最典型的源码变换范例之一。
本文部分内容借助 AI 辅助生成,并由作者整理审核。