我们平时写的 .vue 文件称为 SFC(Single File Components)。vue 会先对 .vue 文件进行解析,分成 template、script、styles、customBlocks 四个部分,称为 descriptor。之后,再对这四个部分分别进行编译最终得到可以在浏览器中执行的 .js 文件。本文介绍将 SFC 解析为 descriptor 这一过程。
SFCDescriptor,是表示 .vue 各个代码块的对象,为以下数据格式:
// an object format describing a single-file component.declare type SFCDescriptor = {template: ?SFCBlock;script: ?SFCBlock;styles: Array<SFCBlock>;customBlocks: Array<SFCBlock>;};
vue 提供了一个 compiler.parseComponent(file, [options])方法,来将 .vue 文件解析成一个 SFCDescriptor。
解析 sfc 文件的源码入口在 src/sfc/parser.js 中,编译后的产出在 /packages/vue-template-compiler 和 /packages/vue-server-renderer 下的 build.js 中。
build.js 文件中直接 export 出了parseComponent方法。
首先我们来看看parseComponent方法都做了哪些事情。
parseComponent方法
/*** Parse a single-file component (*.vue) file into an SFC Descriptor Object.*/export function parseComponent (content: string,options?: Object = {}): SFCDescriptor {const sfc: SFCDescriptor = {template: null,script: null,styles: [],customBlocks: []}let depth = 0let currentBlock: ?SFCBlock = nullfunction start (tag: string, attrs: Array<Attribute>, unary: boolean, start: number, end: number) {}// ...function end (tag: string, start: number, end: number) {}// ...parseHTML(content, {start,end})return sfc}
parseComponent方法中主要定义了start和end两个函数,之后调用了parseHTML方法来对 .vue 文件内容践行编译。start和end两个函数作为参数传给了parseHTML,我们等下再看。
先看下这个parseHTML方法是做啥的呢?
parseHTML方法该方法看名字可以猜到是一个 html-parser。
parseHTML的代码细节较多,我们可以简单理解为:遍历解析查找文件中的各个标签,解析到每个起始标签时,调用 option 中的 start 方法进行处理;解析到每个结束标签时,调用 option 中的 end 方法进行处理。
对应到这里,就是分别调用parseComponent方法中定义的 start 和 end 函数进行处理。
由于我们这里只是想要找到第一层标签,也就是 template、script这些。因此可以在parseComponent中维护一个 depth 变量,在start中将depth++,在end中depth--。那么,每个depth === 1的标签就是我们需要获取的信息,包含 template、script、style 以及一些自定义标签。
接下来我们来看start和end中进行了哪些处理。
start每当遇到一个起始标签时,执行start函数。
function start (tag: string,attrs: Array<Attribute>,unary: boolean,start: number,end: number) {if (depth === 0) {currentBlock = {type: tag,content: '',start: end,attrs: attrs.reduce((cumulated, { name, value }) => {cumulated[name] = value || truereturn cumulated}, {})}if (isSpecialTag(tag)) {checkAttrs(currentBlock, attrs)if (tag === 'style') {sfc.styles.push(currentBlock)} else {sfc[tag] = currentBlock}} else { // custom blockssfc.customBlocks.push(currentBlock)}}if (!unary) {depth++}}
1、记录下 currentBlock。每个 currentBlock 包含以下内容:
declare type SFCBlock = {type: string;content: string;start?: number;end?: number;lang?: string;src?: string;scoped?: boolean;module?: string | boolean;};
2、根据 tag 名称,将 currentBlock 对象保存在在返回结果对象中。
返回结果对象定义为 sfc,如果tag不是 script,style,template 中的任一个,就放在 sfc.customBlocks 中。如果是style,就放在 sfc.styles 中。script 和 template 则直接放在 sfc 下。
if (isSpecialTag(tag)) {checkAttrs(currentBlock, attrs)if (tag === 'style') {sfc.styles.push(currentBlock)} else {sfc[tag] = currentBlock}} else { // custom blockssfc.customBlocks.push(currentBlock)}
end每当遇到一个结束标签时,执行end函数。
function end (tag: string, start: number, end: number) {if (depth === 1 && currentBlock) {currentBlock.end = startlet text = deindent(content.slice(currentBlock.start, currentBlock.end))// pad content so that linters and pre-processors can output correct// line numbers in errors and warningsif (currentBlock.type !== 'template' && options.pad) {text = padContent(currentBlock, options.pad) + text}currentBlock.content = textcurrentBlock = null}depth--}
如果当前是第一层标签(depth === 1),并且 currentBlock 变量存在,那么取出这部分text,放在 currentBlock.content 中。
if (depth === 1 && currentBlock) {currentBlock.end = startlet text = deindent(content.slice(currentBlock.start, currentBlock.end))// pad content so that linters and pre-processors can output correct// line numbers in errors and warningsif (currentBlock.type !== 'template' && options.pad) {text = padContent(currentBlock, options.pad) + text}currentBlock.content = textcurrentBlock = null}
depth--。
在将 .vue 整个遍历一遍后,得到的 sfc 对象即为我们需要的 SFCDescriptor。
compiler.parseComponent(file, [options])得到的只是一个组件的 SFCDescriptor,最终编译成.js 文件是交给 vue-loader 等库来做的。
表格如果字段多,有横向滚动条是不是不方便?可拖拽的滚动是不是就方便了?
使用create-vue创建vue3项目,vite,vue3
相对VUE2 ,VUE3做了哪些优化和改进呢?
vue项目,不使用window.location.reload(),如何刷新当前页面?
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
vue2.0 v-html指令有潜在的xss风险,如何解决?
vue项目通过axios怎么下载二进制文件或图片?
ant.design UI框架 同时使用v-model或value属性时, placeholder不显示
如何在VUE项目中添加stylelint,检查css,scss,less的语法问题,保证团队代码的规范统一
VUE全局函数
定义了服务端渲染的属性名称常量SSR_ATTR,定义了一些资产类型常量ASSET_TYPES,定义了生命周期相关钩子函数的函数名称
使用iviewui-admin框架构建管理系统时,遇到的各类问题
vue项目中动态图片路径拼接
vue下filter方法的调用
21个vue顶级UI框架: Vuetify,Quasar,Vue Material,Keen-UI,Buefy,Bootstrap Vue,Muse-UI,AT-UI,Vux,iView,Uiv,Vuikit,Onsen UI+Vue,Semantic UI+Vue,Fish-UI,Mint UI,Framework7 Vue,Cube UI,Vueblu,Ant Design Vue
&amp;amp;quot;.vue&amp;amp;quot;文件是如何解析成js对象的~
立个flag,看过年这段时间能不能把vue的源码读完?多年前看过jQuery的部分源码,如果想要更深入的了解vue,还是得从源码开始
vue源码阅读,慢慢学习
vue的生命周期函数
vue组件的数据data为什么一定要是函数 ?
Mint-ui this.$messagebox点取消时,报错:Uncaught (in promise) cancel
vue-validate使用
这就是个vue的坑。。。
vue如何监听enter事件?
vue使用axios进行form表单提交
vue中如何改变title标题
如果在vue2.0中使用NProgress进度条
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。也是vue2.0官方推荐的,同时不再对vue-resource(vue1.0使用)进行更新和维护
分析Vue.js源码
npm run dev模式下,webpack4+vue2项目下,类似'/user/login'二级路由,刷新时静态资源路径不对,静态资源返回404
[Vue warn]: Error in render: "TypeError: Cannot read property 'matched' of undefined"
did you register the component correctly? For recursive components, make sure to provide the "name" option
vue.js如何查看注册了哪些组件,获取组件名称
vue底层的Virtual DOM就是基于snabbdom修改的