|
| 1 | +--- |
| 2 | + title: Vue源码——模版编译(三) |
| 3 | + date: 2023-12-23T12:32:15Z |
| 4 | + summary: |
| 5 | + tags: [] |
| 6 | +--- |
| 7 | + |
| 8 | + ## 前言 |
| 9 | +在上篇文章中,我们介绍了`$mount`中通过一系列方式获取到`template`,调用 compileToFunctions 函数通过传入`template`和好多个参数,获取到了`rander`,接下来我们来分析一下内部的原理 |
| 10 | +``` |
| 11 | +const { render, staticRenderFns } = compileToFunctions( |
| 12 | + template, |
| 13 | + { |
| 14 | + outputSourceRange: process.env.NODE_ENV !== "production", |
| 15 | + shouldDecodeNewlines, |
| 16 | + shouldDecodeNewlinesForHref, |
| 17 | + delimiters: options.delimiters, |
| 18 | + comments: options.comments, |
| 19 | + }, |
| 20 | + this); |
| 21 | +``` |
| 22 | + |
| 23 | +## 模板编译器 |
| 24 | +上文使用的 compileToFunctions 函数是 createCompiler 的返回值,调用这个函数时传入了我们的配置 baseOptions, |
| 25 | +``` |
| 26 | +@ src/platforms/web/compiler/index.js |
| 27 | +const { compile, compileToFunctions } = createCompiler(baseOptions); |
| 28 | +export { compile, compileToFunctions }; |
| 29 | +``` |
| 30 | +而 createCompiler 其实是 createCompilerCreator 的返回值 |
| 31 | +``` |
| 32 | +export const createCompiler = createCompilerCreator(function baseCompile( |
| 33 | + template: string, |
| 34 | + options: CompilerOptions |
| 35 | +): CompiledResult { |
| 36 | + const ast = parse(template.trim(), options); |
| 37 | + if (options.optimize !== false) { |
| 38 | + optimize(ast, options); |
| 39 | + } |
| 40 | + const code = generate(ast, options); |
| 41 | + return { |
| 42 | + ast, |
| 43 | + render: code.render, |
| 44 | + staticRenderFns: code.staticRenderFns, |
| 45 | + }; |
| 46 | +}); |
| 47 | +``` |
| 48 | +调用 createCompilerCreator 传入了 baseCompile 函数,这个函数内部声明了一个`ast`常量接收`parse`函数返回值,多数同学应该听说过这个词,ast抽象树,巴拉巴拉。。。。。。。。。。。。。。。。。。。。 |
| 49 | + |
| 50 | +我们传入的 baseCompile 函数在 createCompilerCreator 函数中其实就是担任了一个模板编译器的角色,我们看看这个函数时如何使用我们传入的编译器函数的: |
| 51 | +``` |
| 52 | +export function createCompilerCreator(baseCompile: Function): Function { |
| 53 | + return function createCompiler(baseOptions: CompilerOptions) { ... } |
| 54 | +``` |
| 55 | +很明显的,甚至从名字都可以看出,createCompilerCreator 的作用就是创建一个 createCompiler,那么有人就会问了:不能直接创建 createCompiler 函数吗,为什么非要搞这么复杂。 |
| 56 | +### 高阶函数 |
| 57 | +这里的代码可以引出一个词叫做<u>高阶函数</u> |
| 58 | +- 什么是高阶函数? |
| 59 | + 接收一个或多个函数输入,并返回另一个函数的函数,即高阶函数。 |
| 60 | +- 高阶函数有什么用? |
| 61 | + 1、通俗的讲:高阶函数可以让我们通过封装一个稍微有点复杂的函数来生成很多简单的函数,提高代码灵活度,降低耦合。 |
| 62 | + 2、闭包:闭包也是高阶函数中的一个特征,使用闭包可以防止变量污染全局变量。 |
| 63 | +此处通过`baseCompile`——编译函数,`baseOptions`——编译配置,组装成一个新增编译函数返回,如下: |
| 64 | + |
| 65 | +``` |
| 66 | +function compile( |
| 67 | + template: string, |
| 68 | + options?: CompilerOptions |
| 69 | + ): CompiledResult { |
| 70 | + // 将 finalOptions 的 __proto__ 指向 baseOptions 的 prototype |
| 71 | + const finalOptions = Object.create(baseOptions); |
| 72 | +
|
| 73 | + const errors = []; |
| 74 | + const tips = []; |
| 75 | +
|
| 76 | + let warn = (msg, range, tip) => { |
| 77 | + (tip ? tips : errors).push(msg); |
| 78 | + }; |
| 79 | +
|
| 80 | + if (options) { |
| 81 | + if ( |
| 82 | + process.env.NODE_ENV !== "production" && |
| 83 | + options.outputSourceRange |
| 84 | + ) { |
| 85 | + // 匹配模板开头的空格记录长度 |
| 86 | + const leadingSpaceLength = template.match(/^\s*/)[0].length; |
| 87 | +
|
| 88 | + warn = (msg, range, tip) => { |
| 89 | + const data: WarningMessage = { msg }; |
| 90 | + if (range) { |
| 91 | + if (range.start != null) { |
| 92 | + data.start = range.start + leadingSpaceLength; |
| 93 | + } |
| 94 | + if (range.end != null) { |
| 95 | + data.end = range.end + leadingSpaceLength; |
| 96 | + } |
| 97 | + } |
| 98 | + (tip ? tips : errors).push(data); |
| 99 | + }; |
| 100 | + } |
| 101 | + // merge custom modules |
| 102 | + if (options.modules) { |
| 103 | + finalOptions.modules = (baseOptions.modules || []).concat( |
| 104 | + options.modules |
| 105 | + ); |
| 106 | + } |
| 107 | + // merge custom directives |
| 108 | + if (options.directives) { |
| 109 | + finalOptions.directives = extend( |
| 110 | + Object.create(baseOptions.directives || null), |
| 111 | + options.directives |
| 112 | + ); |
| 113 | + } |
| 114 | +
|
| 115 | + // 拷贝其他的options |
| 116 | + for (const key in options) { |
| 117 | + if (key !== "modules" && key !== "directives") { |
| 118 | + finalOptions[key] = options[key]; |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | +
|
| 123 | + finalOptions.warn = warn; |
| 124 | + // 调用传入的baseCompile对模板进行解析 |
| 125 | + const compiled = baseCompile(template.trim(), finalOptions); |
| 126 | + if (process.env.NODE_ENV !== "production") { |
| 127 | + detectErrors(compiled.ast, warn); |
| 128 | + } |
| 129 | + compiled.errors = errors; |
| 130 | + compiled.tips = tips; |
| 131 | + return compiled; |
| 132 | + } |
| 133 | +
|
| 134 | + return { |
| 135 | + compile, |
| 136 | + compileToFunctions: createCompileToFunctionFn(compile), |
| 137 | + }; |
| 138 | + }; |
| 139 | +``` |
| 140 | + |
| 141 | +随后,返回`compile`和我们之前使用的`compileToFunctions`,值得一提我们在返回的时候又调用了一个高阶函数`createCompileToFunctionFn`对此处的`compile`做包装,如下: |
| 142 | +``` |
| 143 | +export function createCompileToFunctionFn(compile: Function): Function { |
| 144 | + // 缓存对象 |
| 145 | + const cache = Object.create(null); |
| 146 | + // 将compile处理成函数的函数 |
| 147 | + return function compileToFunctions( |
| 148 | + template: string, |
| 149 | + options?: CompilerOptions, |
| 150 | + vm?: Component |
| 151 | + ): CompiledFunctionResult { |
| 152 | + // 首先将 options 拷贝一份到新的对象上 |
| 153 | + options = extend({}, options); |
| 154 | + // 定义警告函数 |
| 155 | + const warn = options.warn || baseWarn; |
| 156 | + delete options.warn; |
| 157 | +
|
| 158 | + // csp警告 |
| 159 | + if (process.env.NODE_ENV !== "production") { |
| 160 | + // detect possible CSP restriction |
| 161 | + try { |
| 162 | + new Function("return 1"); |
| 163 | + } catch (e) { |
| 164 | + if (e.toString().match(/unsafe-eval|CSP/)) { |
| 165 | + warn( |
| 166 | + "It seems you are using the standalone build of Vue.js in an " + |
| 167 | + "environment with Content Security Policy that prohibits unsafe-eval. " + |
| 168 | + "The template compiler cannot work in this environment. Consider " + |
| 169 | + "relaxing the policy to allow unsafe-eval or pre-compiling your " + |
| 170 | + "templates into render functions." |
| 171 | + ); |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | +
|
| 176 | + // 如果有自定义的delimiters(插值符号,默认为"{{}}") |
| 177 | + // 将符号拼接到前面,没有就不拼接 |
| 178 | + const key = options.delimiters |
| 179 | + ? String(options.delimiters) + template |
| 180 | + : template; |
| 181 | + // 进行缓存 |
| 182 | + if (cache[key]) { |
| 183 | + return cache[key]; |
| 184 | + } |
| 185 | +
|
| 186 | + // 编译结果 |
| 187 | + const compiled = compile(template, options); |
| 188 | +
|
| 189 | + // 错误和提示处理 |
| 190 | + if (process.env.NODE_ENV !== "production") { |
| 191 | + if (compiled.errors && compiled.errors.length) { |
| 192 | + if (options.outputSourceRange) { |
| 193 | + compiled.errors.forEach((e) => { |
| 194 | + warn( |
| 195 | + `Error compiling template:\n\n${e.msg}\n\n` + |
| 196 | + generateCodeFrame(template, e.start, e.end), |
| 197 | + vm |
| 198 | + ); |
| 199 | + }); |
| 200 | + } else { |
| 201 | + warn( |
| 202 | + `Error compiling template:\n\n${template}\n\n` + |
| 203 | + compiled.errors.map((e) => `- ${e}`).join("\n") + |
| 204 | + "\n", |
| 205 | + vm |
| 206 | + ); |
| 207 | + } |
| 208 | + } |
| 209 | + if (compiled.tips && compiled.tips.length) { |
| 210 | + if (options.outputSourceRange) { |
| 211 | + compiled.tips.forEach((e) => tip(e.msg, vm)); |
| 212 | + } else { |
| 213 | + compiled.tips.forEach((msg) => tip(msg, vm)); |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | +``` |
| 218 | +在上面我们已经获取到了`compile`函数的返回值,也就是编译结果:ast,render,staticRenderFns 在这里,我们进一步的对这些值做处理: |
| 219 | +``` |
| 220 | + // 转为函数 |
| 221 | + const res = {}; |
| 222 | + const fnGenErrors = []; |
| 223 | + res.render = createFunction(compiled.render, fnGenErrors); |
| 224 | + res.staticRenderFns = compiled.staticRenderFns.map((code) => { |
| 225 | + return createFunction(code, fnGenErrors); |
| 226 | + }); |
| 227 | +
|
| 228 | + // check function generation errors. |
| 229 | + // this should only happen if there is a bug in the compiler itself. |
| 230 | + // mostly for codegen development use |
| 231 | + /* istanbul ignore if */ |
| 232 | + if (process.env.NODE_ENV !== "production") { |
| 233 | + if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { |
| 234 | + warn( |
| 235 | + `Failed to generate render function:\n\n` + |
| 236 | + fnGenErrors |
| 237 | + .map(({ err, code }) => `${err.toString()} in\n\n${code}\n`) |
| 238 | + .join("\n"), |
| 239 | + vm |
| 240 | + ); |
| 241 | + } |
| 242 | + } |
| 243 | +
|
| 244 | + return (cache[key] = res); |
| 245 | + }; |
| 246 | +} |
| 247 | +``` |
| 248 | +rander表达式 |
| 249 | + |
| 250 | + |
0 commit comments