在 vue 工程中,安装依赖时,需要 vue 和 vue-template-compiler 版本必须保持一致,否则会报错。
Module build failed: Error: Cannot find module 'vue-template-compiler'
为什么二者版本必须一致呢?vue-template-compiler 承担哪些作用?其和 vue-loader 又有何关联?
vue-template-compiler
作用: 该模块可用于将 Vue 2.0 模板预编译为渲染函数(template => ast => render),以避免运行时编译开销和 CSP 限制。大都数场景下,与 vue-loader
一起使用,只有在编写具有非常特定需求的构建工具时,才需要单独使用它
内容安全策略 (CSP) 是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括跨站脚本 (XSS) 和数据注入等攻击。
需要注意的是,This package is auto-generated. For pull requests please see src/platforms/web/entry-compiler.js. -- 这个包是自动生成的,请查看 entry-compiler.js
文件。
其文件路径,vue/src/platforms/web/entry-compiler.js
export { parseComponent } from 'sfc/parser'
export { compile, compileToFunctions } from './compiler/index'
export { ssrCompile, ssrCompileToFunctions } from './server/compiler'
export { generateCodeFrame } from 'compiler/codeframe'
可得知,vue-template-compiler
的代码是从 vue 源码中抽离的!接着,我们对比一下 vue-template-compiler
和 vue 关于编译的 API。发现对于 compile 等函数是一致,只是 vue-template-compiler
开放的参数和方法更多。因此,vue
和 vue-template-compiler
的版本必须一致(同一份源码)!
const compiler = require('vue-template-compiler')
const result = compiler.compile(`
<div id="test">
<div>
<p>This is my vue render test</p>
</div>
<p>my name is {{myName}}</p>
</div>`
)
console.log(result)
compiler.compile(template, [options])
{
ast: ASTElement, // 解析模板生成的ast
render: string, // 渲染函数
staticRenderFns: Array<string>, // 静态子树
errors: Array<string>,
tips: Array<string>
}
{
ast: {
type: 1,
tag: 'div',
attrsList: [ [Object] ],
attrsMap: { id: 'test' },
rawAttrsMap: {},
parent: undefined,
children: [ [Object], [Object], [Object] ],
plain: false,
attrs: [ [Object] ],
static: false,
staticRoot: false
},
render: `with(this){return _c('div',{attrs:{"id":"test"}},[
_m(0), // 上述提到的静态子树,索引为0 <div><p>This is my vue render test</p></div>
_v(" "), // 空白节点 </div> <p> 之间的换行内容
_c('p',[_v("my name is "+_s(myName))]) // <p>my name is {{myName}}</p>
])}`,
staticRenderFns: [
`with(this){return _c('div',[_c('p',[_v("This is my vue render test")])])}`
],
errors: [],
tips: []
}
需要注意的是:children
存在3个节点,其中第二个为空节点 { type: 3, text: ' ', static: true }
即代码中的</div><p>
之间的内容。
compiler.compileToFunctions(template)
简化版的 compiler.compile()
,但只返回
{
render: Function,
staticRenderFns: Array<Function>
}
其通过 new Function()
方式
compiler.parseComponent(file, [options])
将 SFC (单文件组件或* .vue文件)解析为描述符「以下述提供SFC为例」
{
template: {
type: 'template',
content: 'n<div class="example">{{ msg }}</div>n',
start: 10,
attrs: {},
end: 54
},
script: {
type: 'script',
content: 'n' +
'export default {n' +
' data () {n' +
' return {n' +
" msg: 'Hello world!'n" +
' }n' +
' }n' +
'}n',
start: 77,
attrs: {},
end: 174
},
styles: [
{
type: 'style',
content: 'n.example {n color: red;n}n',
start: 194,
attrs: {},
end: 236
}
],
customBlocks: [
{
type: 'custom1',
content: '自定义块',
start: 257,
attrs: {},
end: 261
}
],
errors: []
}
通常用于 SFC 构建工具,如 vue-loader、vueify等
compiler.generateCodeFrame(template, start, end)
高亮展示 start,end 代码段。
compiler.generateCodeFrame(`上述代码`, 15, 20)
1 | <template>
2 | <div class="example">{{ msg }}</div>
| ^^^^^
3 | </template>
简介解析过程
入口文件 src/compiller/index.js
// 生成ast语法树
const ast = parse(template.trim(), options)
// 标记静态内容(以免diff的时候需要重复比较)
optimize(ast, options)
// 生成 render function code
const code = generate(ast, options)
vue-loader
webpack loader for Vue Single-File Components. 用于 Vue 单文件组件的 webpack 加载器。
Vue Single-File Components
*.vue
文件是一种自定义文件格式,使用类似于 HTML 的语法来描述 Vue 组件。每个 *.vue
文件都包含三种类型的顶级语言块:<template>
,<script>
和 <style>
,以及其他可选的自定义块:
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
}
}
}
</script>
<style>
.example {
color: red;
}
</style>
<custom1>自定义块</custom1>
vue-loader 将解析文件,提取每个语言块,如有必要,将它们通过其他加载器进行管道传输,最后将它们组装回ES 模块,其默认导出为 Vue.js 组件选项对象。
- Template:每个
*.vue.
文件一次最多可以包含一个<template>
块;内容将被提取并传递给vue-template-compiler
并预编译为 JavaScript 渲染函数,最后注入<script>
部分的导出组件中 - Script: 每个
*.vue.
文件一次最多可以包含一个<script>
块;任何针对.js
文件的 webpack rules 都将应用于<script>
块中的内容 - Style: 默认匹配
/.css$/
;可以包含多个<style>
块;可以包含Scoped
或者module
属性;任何针对.css
文件的 webpack rules 都将应用于<style>
块中的内容 - Custom Blocks: 自定义块,以满足任何项目的特定需求
如果你希望将 *.vue
. 组件拆分为多个文件,则可以使用 src 属性为语言块导入外部文件:
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
- 相对路径必须以
./
开头 - 可以从 npm 依赖项导入资源
如何工作
处理 SFC 中的每个语言块,然后组装成最终模块。
- 使用
@vue/component-compiler-utils
解析 SFC 为每个语言块生成一个导入
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script'
export * from 'source.vue?vue&type=script'
// import <style> blocks
import 'source.vue?vue&type=style&index=1'
script.render = render
export default script
2. 对相应语言块,增加其他处理规则
import script from 'babel-loader!vue-loader!source.vue?vue&type=script'
import 'source.vue?vue&type=style&index=1&scoped&lang=scss'
3. 处理扩展请求时,再次调用 vue-loader,但这次会明确将其传递给匹配的目标加载器
4. 对于 scoped 等进行处理(VueLoaderPlugin 会注入一个全局的Pitching Loader(src/pitcher.ts
),它会拦截Vue的 <template>
和<style>
请求并注入必要的加载器)
参考地址
- https://astexplorer.net/
- https://github.com/vuejs/vue/tree/v2.6.10/packages/vue-template-compiler
- https://developer.mozilla.org/zh-CN/docs/Web/Security/CSP
- https://github.com/vuejs/vue-loader/blob/master/docs/spec.md
- https://github.com/vuejs/vue-loader/blob/master/docs/guide/custom-blocks.md
欢迎关注 「 Super 前端 」微信公众号
版权声明: 本文原创自我的博客:李刚的学习专栏