看个demo
看个简单的demo,代码如下:
{{ format(msg) }}
{{ title }}
在上面的demo中定义了四个顶层绑定:Child子组件、从util.js文件中导入的format方法、使用ref定义的msg只读常量、使用let定义的title变量。并且在template中直接使用了这四个顶层绑定。
由于innerContent是在if语句里面的变量,不是
将断点走到第二部分,代码如下:
for (const node of scriptSetupAst.body) {
if (node.type === "ImportDeclaration") {
// ...省略
}
}
for (const node of scriptSetupAst.body) {
if (
(node.type === "VariableDeclaration" ||
node.type === "FunctionDeclaration" ||
node.type === "ClassDeclaration" ||
node.type === "TSEnumDeclaration") &&
!node.declare
) {
// 顶层声明的变量、函数、类、枚举声明组成的setupBindings对象
// 给setupBindings对象赋值,{msg: 'setup-ref'}
// 顶层声明的变量组成的setupBindings对象
walkDeclaration(
"scriptSetup",
node,
setupBindings,
vueImportAliases,
hoistStatic
);
}
}
在这一部分的代码中使用for循环遍历了两次scriptSetupAst.body,scriptSetupAst.body为script中的代码对应的AST抽象语法树中body的内容,如下图:
图片
从上图中可以看到scriptSetupAst.body数组有6项,分别对应的是script模块中的6块代码。
第一个for循环中使用if判断node.type === "ImportDeclaration",也就是判断是不是import语句。如果是import语句,那么import的内容肯定是顶层绑定,需要将import导入的内容存储到ctx.userImports对象中。注:后面会专门写一篇文章来讲如何收集所有的import导入。
通过这个for循环已经将所有的import导入收集到了ctx.userImports对象中了,在debug终端看看此时的ctx.userImports,如下图:
图片
从上图中可以看到在ctx.userImports中收集了三个import导入,分别是Child组件、format函数、ref函数。
在里面有几个字段需要注意,isUsedInTemplate表示当前import导入的东西是不是在template中使用,如果为true那么就需要将这个import导入塞到return对象中。
isType表示当前import导入的是不是type类型,因为在ts中是可以使用import导入type类型,很明显type类型也不需要塞到return对象中。
我们再来看第二个for循环,同样也是遍历scriptSetupAst.body。如果当前是变量定义、函数定义、类定义、ts枚举定义,这四种类型都属于顶层绑定(除了import导入以外就只有这四种顶层绑定了)。需要调用walkDeclaration函数将这四种顶层绑定收集到setupBindings对象中。
从前面的scriptSetupAst.body图中可以看到if模块的type为IfStatement,明显不属于上面的这四种类型,所以不会执行walkDeclaration函数将里面的innerContent变量收集起来后面再塞到return对象中。这也就解释了为什么非顶层绑定不能在template中直接使用。
我们在debug终端来看看执行完第二个for循环后setupBindings对象是什么样的,如下图:
从上图中可以看到在setupBindings对象中收集msg和title这两个顶层变量。其中的setup-ref表示当前变量是一个ref定义的变量,setup-let表示当前变量是一个let定义的变量。
移除template模块和style模块
接着将断点走到第三部分,代码如下:
ctx.s.remove(0, startOffset);
ctx.s.remove(endOffset, source.length);
这块代码很简单,startOffset为