精华内容
下载资源
问答
  • neither <code>hot</code> nor <code>hmr</code> seems to be compiling the <code>.vue</code> files, as those changes are not shown; not even initially... <p>I can see webpack correctly recompiling once ...
  • t compile .vue files yet. I get the following error. <img width="492" alt="capture d ecran 2018-11-07 a 23 06 14" src="https://img-blog.csdnimg.cn/img_convert/3cb61154f969356b0b2d7090c41c2137.png" />...
  • 编译vue单文件组件背景Vue官方定义了一种名叫单文件组件(SFC)规范的*.vue文件,用类 HTML 语法描述一个 Vue 组件。每个 .vue 文件包含三种类型的顶级语言块 、 和 ,还允许添加可选的自定义块:{{ msg }}export ...

    编译vue单文件组件

    背景

    Vue官方定义了一种名叫单文件组件(SFC)规范的*.vue文件,用类 HTML 语法描述一个 Vue 组件。每个 .vue 文件包含三种类型的顶级语言块 、

    {{ msg }}

    export default {

    data () {

    return {

    msg: 'Hello world!'

    }

    }

    }

    .example {

    color: red;

    }

    This could be e.g. documentation for the component.

    为此Vue的官方提供了vue-loader,它会解析文件,提取每个语言块,如有必要会通过其它 loader 处理,最后将他们组装成一个 ES Module,它的默认导出是一个 Vue.js 组件选项的对象。

    vue-loader 支持使用非默认语言,比如 CSS 预处理器,预编译的 HTML 模版语言,通过设置语言块的 lang 属性。例如,你可以像下面这样使用 Sass 语法编写样式:

    /* write Sass! */

    开始

    大部分情况下我们开发Vue项目是通过vue-cli这个脚手架快速生成一个项目骨架而开始的

    vue init webpack my-vue-project

    然后我们在这个项目中编写.vue单文件组件,通过:

    npm rub build

    构建整个项目,将产出的文件发布上线,事实上在整个项目构建过程中我们并不知道.vue文件到底发生了什么。当然如果只是单纯通过这种方式来开发一个Vue的单页项目,其实也并不需要了解太多细节,我们只需要按照给定的项目规范编写业务代码即可,但这种傻瓜式的开发配置,有时候并不能满足多变的需求。

    想法

    既然Vue官方已经定义了一种单文件组件(SFC)规范的东东,我们何不借来使用:实现组件的平台化。我们提供一个.vue组件编辑器供用户使用,编辑完成后单击保存,即可看到该组件渲染之后的页面。

    在这个过程中会涉及到如何编译.vue单文件组件,以及动态/异步渲染vue单文件组件,本文主要记录编译vue单文件组件的几种方法:

    在开始之前我们提前创建了一个项目:compile-vue-demo,方便您的调试和查看,该项目集成了下面几种编译方式。

    rollup

    Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解决方案,如 CommonJS 和 AMD。ES6 模块可以使你自由、无缝地使用你最喜爱的 library 中那些最有用独立函数,而你的项目不必携带其他未使用的代码。ES6 模块最终还是要由浏览器原生实现,但当前 Rollup 可以使你提前体验。

    配置文件

    // rollup.config.js

    import VuePlugin from 'rollup-plugin-vue'

    export default {

    entry: 'src/main.vue',

    format: 'iife',

    dest: 'rel/bundle.js',

    output: {

    name: 'main',

    },

    plugins: [VuePlugin(/* VuePluginOptions */)],

    }

    项目里的entry.rollup.html文件为rollup编译的入口文件,通过运行下面的命令编译:

    rollup -c --watch

    webpack

    项目里的entry.webpack.html文件为webpack编译方式的入口文件,通过运行下面的命令来编译:

    webpack

    // webpack.config.js

    const path = require('path');

    const VueLoaderPlugin = require('vue-loader/lib/plugin');

    module.exports = {

    entry: './src/hello.vue',

    output: {

    filename: 'bundle.js',

    path: path.resolve(__dirname, './dist'),

    library: 'hello',

    //libraryTarget: 'umd'

    },

    mode: 'development',

    devtool: 'none',

    module: {

    rules: [

    // 解析.vue文件

    {

    test: /\.vue$/,

    loader: 'vue-loader'

    },

    // 它会应用到普通的 `.css` 文件

    // 以及 `.vue` 文件中的 `

    {

    test: /\.css$/,

    loader: 'css-loader'

    },

    // 它会应用到普通的 `.js` 文件

    // 以及 `.vue` 文件中的 `

    {

    test: /\.js$/,

    loader: 'babel-loader'

    }

    ]

    },

    plugins: [

    new VueLoaderPlugin()

    ]

    }

    上面的配置就是将./src/hello.vue文件编译打包成 ./dist/bundle.js文件,为了能够在打包之后拿到hello.vue组件对象,上面配置了output.library = 'hello'

    在entry.webpack.html文件中则通过下面方式实例化了一个Vue App

    var vm = new Vue({

    el:'#app',

    data: {

    hello: 'hello'

    },

    created() {},

    components:{

    hello: hello.default

    }

    });

    FIS3

    项目里的entry.fis.html文件为fis编译方式的入口文件,通过运行下面命令来查看效果:

    fis3 server start

    fis3 release -wcL

    在fis中已经有大神帮我们实现了.vue文件的编译插件:fis3-parser-vue-component 具体配置如下

    // fis-conf.js

    fis.match('*.vue', {

    isMod: true,

    rExt: '.js',

    useSameNameRequire: true,

    parser: fis.plugin('vue-component', {

    runtimeOnly: true, // vue@2.x 有润timeOnly模式,为ture时,template会在构建时转为render方法, 这里开启后paths中无需指定

    // styleNameJoin

    styleNameJoin: '', // 样式文件命名连接符 `component-xx-a.css`

    extractCSS: false, // 是否将css生成新的文件, 如果为false, 则会内联到js中

    // css scoped

    cssScopedIdPrefix: '_v-', // hash前缀:_v-23j232jj

    cssScopedHashType: 'sum', // hash生成模式,num:使用`hash-sum`, md5: 使用`fis.util.md5`

    cssScopedHashLength: 8 // hash 长度,cssScopedHashType为md5时有效

    }),

    optimizer: [/*fis.plugin('uglify-js'),*/ function(content, file, settings) {

    //console.log(file);

    return content;

    }]

    }).match('{*.less, *.vue:less}', {

    parser: fis.plugin('less'),

    postprocessor: fis.plugin('autoprefixer'),

    rExt: '.css'

    }).match('{src/**.js, *.vue:js}', {

    parser: fis.plugin('babel-6.x', {

    presets: ['es2015', 'stage-3'],

    plugins: ['add-module-exports']

    }),

    rExt: '.js'

    });

    平台化?

    上面的两种编译.vue的方法,也只是我们借助工具经过简单配置来实现的,尽管实现了.vue单文件组件的编译,但还是不够友好,或者说我们还是不能方便的使用,他们仍然是要通过编译文件的形式来解析代码,是否可以以一种服务的形式存在直接解析/编译从前端编辑器POST过来的.vue单文件组件代码?我们后面文章将继续介绍~~

    参考

    展开全文
  • Vue.extend、Vue.component、new Vue以及Vue.compile1、Vue.extend(options):2、Vue.component(options):3、new Vue创建==根实例== Vue.extend(vue扩展构造器)、Vue.component(vue全局组件注册)、new Vue(创建vue...

    Vue.extend、Vue.component、new Vue以及动态编译Vue.compile

    先说说Vue.extend(vue扩展构造器)、Vue.component(vue全局组件注册)、new Vue(创建vue根实例)三者之间的关系。

    野路子理解:

    1、Vue.extend(options):

    1、根据官方文档的描述是用来创建Vue“子类”的api,也就是创建一个新的构造函数
    2、该构造函数里面包含了对Vue初始化函数的调用,另外Vue.extend里通过Object.create()以Vue的原型对象为原型创建了新的对象当做该构造函数的原型,这样做的好处是更改原型属性时,避免对Vue的原型造成污染同时又继承了Vue的原型属性。
    3、options是共有的选项数据,也就是只要由该构造函数创建的实例都会有这些选项,和mixin一毛一样。另外实例初始化时还可以传递实例单独的options选项。
    4、因为生成的构造函数在new调用时,函数里调用了Vue的初始化方法,所以生成的是一个正了八经的Vue实例对象。

    2、Vue.component(options):

    1、用来注册全局组件,官网上有三种用法:

    // 1、注册组件,传入一个扩展过的构造器
    Vue.component(‘my-component’, Vue.extend({ /* … */ }))

    // 2、注册组件,传入一个选项对象 (自动调用 Vue.extend)
    Vue.component(‘my-component’, { /* … */ })

    // 3、获取注册的组件 (始终返回构造器)
    var MyComponent = Vue.component(‘my-component’)

    1、根据第1、2用法再加上options参数和Vue.extend的options可以一毛一样,由此得出注册组件就是根据Vue.extend的选项options创建一个新的构造函数,和注册时填写的id名称一一对应。
    2、可以这么理解:每一次组件实例化,其实都是根据这个新的构造函数来创建。当然实际要考虑的更多。

    3、new Vue()

    官方文档中经常见到:

    通过 new Vue 创建的根 Vue 实例

    刚开始以为new Vue()才是根实例,后来看了源码发现,这只是代表了一个组件树结构的根组件。不信可以测一下 Vue.extend返回的构造函数创建的实例也可以是根实例。
    不过通常应用中的根实例都是通过new Vue()创建的:
    1、spa应用的根实例只有一个。
    2、稍微老点的系统可能一个页面一个根实例。

    4、Vue.compile

    这个api不常用,但是用起来也挺爽。根据一次实际应用来介绍它。
    需求:
    根据配置页面的模板字符串动态改变页面显示内容。
    比如模板字符串可能配成以下几种:
    this.templateStr=

    1、“item.name”;
    2、“getName(item.id)”;
    3、“‘我的名字是’+getName(item.id)”;

    上面三种都是vue中的模板表达式,由于都是字符串形式,所以这里就可以用Vue.compile来编译,具体如下:

    	// 组合要编译的字符串
    	const templateStr = `<span>{{this.templateStr}}</span>`;
    	// 调用Vue.compile获取render函数
    	const templateRes = Vue.compile(templateStr);
    	// render函数执行需要Vue实例,否则报错,并且,模板中的各种属性、方法都会去这个实例里面找,所以。。。实例可以随便搞了
    	// 这样
    	const node1 = templateRes.render.call(this);
    	// 这样
    	const Ctor = Vue.extend();
    	const vueInstance = new Ctor({
    		data: function() {
    			return {
    				item: {
    					name: 'zhangsan',
    					id: '1'
    				}
    			}
    		},
    		methods: {
    			getName(id){
    				return 'zhangsan' + id;
    			}
    		}
    	});
    	const node2 = templateRes.render.call(vueInstance);
    

    代码中node1、node2就是vnode实例。
    提示:render函数执行需要Vue实例,否则报错,并且,模板中的各种属性、方法都会去这个实例里面找。

    展开全文
  • <div><p>Hi, I'm trying to create a new project with the ...webpack: Failed to compile. </code></pre> <p>Any idea ? <p>thanks, BR</p><p>该提问来源于开源项目:vue-typed/vue-typed</p></div>
  • Vue Compile原理分析

    2020-09-22 19:20:45
    VueCompile是一个非常复杂的内容,Compile的主要作用是解析模板,生成渲染模板的render, 而render的作用主要是为了生成VNode, Compile主要分为3大块: parse 接受template原始模板,按着模板的节点和数据生成对应...

    Vue中Compile是一个非常复杂的内容,Compile的主要作用是解析模板,生成渲染模板的render, 而render的作用主要是为了生成VNode, Compile主要分为3大块:

    1. parse 接受template原始模板,按着模板的节点和数据生成对应的ast
    2. optimize 遍历ast的每一个节点,标记静态节点,这样就知道哪部分不会变化,于是在页面需要更新时,减少去对比这部分DOM,提升性能
    3. generate 把前两步生成完善的ast,组成render字符串,然后将render字符串通过new Function的方式转换成渲染函数

    1. Compile从新建实例到Compile结束的主要流程

    Vue编译template, 生成render发生在$mount这个函数中

    Vue.prototype.$mount = function(el) {
        var options = this.$options;
        if (!options.render) {
            // 获取template模板
            var tpl = options.template;
            if (tpl) {
                tpl = document.querySelector(tpl).innerHTML;
            }
            if (tpl) {
                var ref = compileToFunctions(tpl, {}, this);
                options.render = ref.render;
                options.staticRenderFns = ref.staticRenderFns;
            }
        }
        // 执行上面生成的render, 生成DOM,挂载DOM
        return mount.call(this, el);
    }
    

    compileToFunctions的生成流程

    // compileToFunctions是通过createCompiler执行返回的
    var ref$1 = createCompiler();
    var { compileToFunctions } = ref$1;
    

    createCompiler的生成流程

    // createCompiler 是通过 createCompilerCreator 生成的
    // createCompilerCreator 会传入一个 baseCompile 的函数
    var createCompiler = createCompilerCreator(
        function baseCompile(template, options) {
            var ast = parse(template.trim(), options);
            if (options.optimize !== false) {
                optimize(ast, options);
            }
            var code = generate(ast, options);
            return {
                ast,
                render: code.render,
                staticRenderFns: code.staticRenderFns
            }
        }
    );
    
    function createCompilerCreator (baseCompile) {
        return function () {
            // 作用是合并选项,并且调用 baseCompile
            function compile(template) {
                var compiled = baseCompile(template);
                return compiled;
            }
    
            return {
                compile,
                // compileToFunctions 用来生成 render 和 staticRenderFns
                // compileToFunctions 其内核就是 baseCompile
                compileToFunctions: createCompileToFunctionFn(compile)
            }
        }
    }
    
    function createCompileToFunctionFn (compile) {
        // 作为缓存,防止每次都重新编译
        // template 字符串 作为 key
        // 值为 render 和 staticRenderFns
        var cache = Object.create(null);
        return function compileToFunctions(template, options, vm) {
            var key = template;
    
            // 有缓存的时候直接取出缓存中的结果即可
            if (cache[key]) return cache[key];
            // compile 是 createCompilerCreator 传入的 compile
            var compiled = compile(template, options);
            var res = {
                // 将字符串render解析成函数
                render: new Function(compiled.render),
                staticRenderFns: compiled.staticRenderFns.map(function(code) {
                    return new Function(code, fnGenErrors);
                })
            };
            cache[key] = res;
            return cache[key];
        }
    }
    

    2. parse主要流程

    parse的主要作用就是将template字符串转化为ast,ast为抽象语法树,是一种以树形结果来表示模板语法结构,比如:

    {
        tag: 'div',
        type: 1, // 1->节点; 2->表达式,比如{{isShow}}; 3->纯文本
        children: [{
            type: 3,
            text: '11'
        }]
    }
    

    parse整个流程非常复杂,需要一步步深入

    // pase接收template字符串
    function parse(template) {
        // 缓存模板中解析的每个节点的ast
        // 是一个数组存放模板中按顺序从头到尾每个标签的AST
        // 主要用来理清节点父子关系
        var stack = [];
        // 根节点,是ast
        var root;
        // 当前解析标签的父节点
        // 这样才知道当前解析节点的父节点是谁,才把这个节点添加给响应节点的children
        // 根节点没有父节点,所以是undefined
        var currentParent;
    
        // parseHTML 负责 template 中的标签匹配,再传入 start, end, chars 等方法
        parseHTML(template, {
            start, // 处理头标签
            end, // 处理尾标签
            chars // 处理文本
        });
    
        return root;
    }
    
    function parseHTML(html, options) {
        while(html) {
            // 寻找 < 的起始位置
            var textEnd = html.indexOf('<'),
                text,
                rest,
                next;
            
            // 如果模板起始位置是标签开头 <
            // 如果匹配到 <, 那这个有可能是头标签上的,也有可能是尾标签上的
            if (textEnd === 0) {
    
                // 如果是尾标签得到 <
                // 比如html = '</div>', 匹配出 endTagMatch=['</div>', 'div']
                var endTagMatch = html.match(endTag);
                if (endTagMatch) {
                    // endTagMatch[0]='</a>'
                    html = html.substring(endTagMatch[0].length);
                    // 处理尾标签的方法
                    options.end();
                    continue;
                }
    
                // 如果是起始标签的 <
                // parseStartTag作用是,匹配标签存在的属性,截断template
                // 比如: html='<div></div>'
                // parseStartTag处理之后, startTagMatch={tagName: 'div', attrs: []}
                var startTagMatch = parseStartTag();
                // 匹配到起始标签之后
                if (startTagMatch) {
                    // 处理起始标签
                    options.start(...);
                    continue;
                }
            }
    
            // 模板起始位置不是 <, 而是文字
            if (textEnd >= 0) {
                text = html.substring(0, textEnd);
                html = html.substring(n);
            }
    
            // 处理文字
            if (options.chars && text) {
                options.chars(text);
            }
        }
    }
    

    处理头标签, 当parseHTML匹配到一个首标签,都会把该标签的信息传递给start

    function start(tag, attrs, unary) {
        // 创建AST节点
        var element = createASTElement(tag, attrs, currentParent);
    
        // 设置根节点,一个模板仅有一个根节点
        if (!root) {
            root = element;
        }
    
        // 处理父子关系
        if (currentParent) {
            currentParent.children.push(element);
            element.parent = currentParent;
        }
    
        // 不是单标签(input, img), 都需要保存到stack
        if (!unary) {
            currentParent = element;
            stack.push(element);
        }
    }
    
    function createASTElement(tag, attrs, parent) {
        // 创建一个AST结构,保存数据
        /*
            模板上的属性经过parseHTML解析成一个数组
            [{ name: 'hoho', value: '333'},{ name: 'href', value: '444' }]
            makeAttrMap可以将其转化成如下结构
            { hoho: '333', href: '444' }
        */
        return {
            type: 1,
            tag,
            attrsList: attrs,
            attrsMap: makeAttrsMap(attrs),
            parent,
            children: []
        }
    }
    

    处理尾标签匹配到尾标签,比如</div>的时候,就会调用传入的end方法

    function end() {
        // 标签解析结束,移除该标签
        /*
            比如有如下一段html:
            <div>
                <section></section>
                <p></p>
            </div>
            stack匹配到两个头标签之后 stack=['div', 'section']
            然后匹配到</section>, 则移除stack中的section, 并且重设 currentParent
            stack=['div']
            currentParent='div'
            再匹配到</p>, p的父节点就是div了,父子顺序就正确了
        */
        stack.length -= 1;
        currentParent = stack[stack.length - 1];
    }
    

    处理文本字符串,当parseHTML去匹配<的时候,发现template开头到<还有一段距离,那么这段距离就是文本了,这段文本会给chars方法处理

    // chars的主要作用就是为父节点添加文本子节点
    // 文本子节点有两种类型
    // 1. 普通型,直接存为文本子节点
    // 2. 表达式型,需要经过parseText处理
    /*
        比如表达式{{isShow}}会被解析成
        {
            expression: toString(isShow),
            tokens: [{@binding: "isShow"}]
        }
        其主要是为了把表达式isShow拿到,方便从实例上获取
    */
    function chars(text) {
        // 必须存在根节点,不能用文字开头
        if (!currentParent) return;
        var children = currentParent.children;
    
        // 通过parseText解析成字符串,判断是否含有双括号表达式,比如{{isShow}}
        // 如果含有表达式,会存放多一点信息
        var res = parseText(text);
        if (res) {
            children.push({
                type: 2,
                expression: res.expression,
                tokens: res.tokens,
                text,
            });
        }
        // 普通字符串,直接存在字符串节点
        else if (!children.length || children[children.length - 1].text !== ' ) {
            children.push({
                type: 3,
                text,
            });
        }
    }
    

    下面来看一个完整的Parse流程

    <div>11</div>
    
    1. 开始循环template,匹配到第一个头标签div, 传入start, 生成对应的AST,该div的ast变成根节点root,并设置其为当前父节点currentParent, 保存节点存储数组stack
    stack = [{ tag: 'div', children: [] }]
    

    第一轮处理结束,template截断到第一次匹配到的位置11</div>

    1. 开始第二次遍历,开始匹配到<, 发现<不在开头,从开头位置到<有一段普通字符串,调用chars,传入字符串,发现其没有双括号等表达式,直接给父节点添加简单子节点
    currentParent.children.push({ type: 3, text: '11' });
    

    此时

    stack = [{ tag: 'div', children: [{ type: 3, text: '11' }] }]
    

    第二轮结束,template截断到刚刚匹配完的字符串,此时template=</div>

    1. 开始第三轮遍历,继续寻找<, 发现就在开头,但是这是一个结束标签,标签名是div。这个标签匹配完毕,也会从stack中移除,第3次遍历结束,template继续阶段,此时template为空,遍历结束。
    {
        tag: 'div',
        type: 1,
        children: [{ type: 3, text: '11' }]
    }
    

    3. 标签解析

    上一小节讲到了是通过parseHTML这个方法来对template进行循环遍历的。就是不断的将模板字符串匹配然后截断,直到字符串为空,其中和阶段有关的一个重要函数就是advance

    // 截断模板字符串,并保存当前的位置
    function advance(n) {
        index += n;
        html = html.substring(n);
    }
    
    • 如果 < 在template开头
      如果是尾标签的<, 那么交给parseEndTag处理
      如果是头标签的>, 那么交给parseStartTag处理

    • 如果 < 不在template开头,那么表明开头到 < 的这段位置是字符串,也需要用到advance去截断字符串。

    template = '<div>111</div>';
    parseHTML(template);
    

    parseStartTag作用是处理头标签

    1. 把头标签的所有信息及合起来,包括属性,标签名
    2. 匹配完成之后,同样调用advance去截断template
    3. 把标签信息返回
    function parseStartTag() {
        // html ='<div name=1>111</div>'
        // start = ["<div", "div", index: 0]
        var start = html.match(startTagOpen);
        if (start) {
            // 存储本次头标签的信息
            var match = {
                tagName: start[1],
                attrs: [],
                start: index
            }
        }
    
        // start[0]是<div
        // 阶段之后template="name=1 >111</div>"
        advance(start[0].length);
    
        var end, attr;
        // 循环匹配属性内容,保存属性列表
        // 直到template遍历到头标签的 >
        while (
            // 匹配不到头标签的 > , 开始匹配 属性内容
            !(end = html.match(startTagClose))
            // 开始匹配属性内容
            // attr = ["name=1", "name", "="]
            && (attr = html.match(attribute))
        ) {
            advance(attr[0].length);
            match.attrs.push(attr);
        }
    
        // 匹配到起始标签的 > , 标签属性那些已经匹配完毕了
        // 返回收集到的标签信息
        if (end) {
            advance(end[0].length);
            // 如果是单标签,那么 unarySlash 的值是 / , 比如 <input />
            match.unarySlash = end[1];
            match.end = index;
            return match;
        }
    }
    

    经过以上的parseStartTag会处理返回如下内容

    // <div name=1></div>
    {
        tagName: 'div',
        // 头标签中的属性信息
        attrs: [
            [" name=1", "name", "=", undefined, undefined, "1"]
        ],
        unarySlash: "", // 表示这个表示是否是单标签
        start: 0, // 头标签的 < 在template中的位置
        end: 12 // 头标签的 > 在template中的位置
    }
    

    通过parseStartTag返回的头信息,最后传给了handleStartTag

    function handleStartTag(match) {
        var tagName = match.tagName;
        var unarySlash = match.unarySlash;
        // 判断是不是单标签,input, img这些
        var unary = isUnaryTag$$1(tagName) || !!unarySlash;
        var l = match.attrs.length;
        var attrs = new Array(l);
    
        // 把属性数组转化成对象
        for (let i = 0; i < l; i++) {
            var args = match.attrs[i];
            var value = args[3] || args[4] || args[5] || '';
            attrs[i] = {
                name: args[1],
                value
            }
        }
    
        // 不是单标签才存到stack
        if (!unary) {
            stack.push({
                tag: tagName,
                attrs
            });
        }
    
        if (options.start) {
            options.start(
                tagName, attrs, unary,
                match.start, match.end
            );
        }
    }
    

    当使用endTag这个正则成功匹配到尾标签时,会调用parseEndTag

    function parseEndTag(tagName, start, end) {
        var pos, lowerCasedTagName;
        if (tagName) {
            for (pos = stack.length - 1; pos >= 0; pos--) {
                if (stack[pos].tagName === tagName) break;
            }
        }
        else {
            // 如果没有提供标签签名,那么关闭所有存在stack中的起始标签
            pos = 0;
        }
    
        // 批量 stack pop 位置后的所有标签
        if (pos >= 0) {
            // 关闭 pos 位置之后所有的起始标签,避免有些标签没有尾标签
            // 比如stack.length = 7, pos = 5, 那么就关闭最后两个
            for (var i = stack.length - 1; i >= pos; i--) {
                if (options.end) {
                    options.end(stack[i].tag, start, end);
                }
            }
            // 匹配完闭合标签之后,就把匹配的标签头给移除了
            stack.length = pos;
        }
    }
    
    /*
        简要流程如下
        <div>
            <header>
                <span></span>
            </header>
        </div>
        一开始会匹配到3个头标签 stack = [div, header, span]
        然后开始匹配到span, 然后去stack末尾去找span
        确定span在stack中的位置pos后,批量闭合stack的pos之后所有的标签
    */
    

    4.属性解析

    处理class分为两种,一种静态的class, 一种动态的class

    function transformNode(el, options) {
        var staticClass = getAndRemoveAttr(el, 'class');
        if (staticClass) {
            el.staticClass = JSON.stringify(staticClass);
        }
        // 处理动态class
        var classBinding = getBindingAttr(el, 'class', false);
        if (classBinding) {
            el.classBinding = classBinding;
        }
    }
    
    /*
        <span class="a" :class="b"></span>
        转化为下面的
        {    
            classBinding: "b"
            staticClass: ""a""
            tag: "span"
            type: 1
        }
    */
    

    处理style几乎和class一模一样

    function transformNode$1(el, options) {    
        var staticStyle = getAndRemoveAttr(el, 'style');    
        if (staticStyle) {        
            // 比如绑定 style="height:0;width:0"
            // parseStyleText 解析得到对象 { height:0,width:0 }
            el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
        }  
        // :style="{height:a}" 解析得 {height:a}
        var styleBinding = getBindingAttr(el, 'style', false);    
        if (styleBinding) {
            el.styleBinding = styleBinding;
        }
    }
    
    /*
        <span style="width:0" :style="{height: a}"></span>
        转化成下面的
        {
            staticStyle: "{"width":"0"}"
            styleBinding: "{height:a}"
            tag: "span"
            type: 1
        }
    */
    

    解析v-for用的是processFor

    function processFor(el) {
        var exp = getAndRemoveAttr(el, 'v-for');
        if (exp) {
            // 比如指令是v-for="(item, index) in arr"
            // res = {for: "arr", alias: "item", iterator1: "index"}
            var res = parseFor(exp);
            if (res) {
                // 把res和el属性合并起来
                extend(el, res);
            }
        }
    }
    
    /*
        <div v-for="(item, index) in arr"></div>
        可以转化为
        {
            alias: "item",    
            for: "arr",    
            iterator1: "index",    
            tag: "div",    
            type: 1,
        }
    */
    

    解析v-if用的是processIf

    function processIf(el) {
        var exp = getAndRemoveAttr(el, 'v-if');
        if (exp) {
            el.if = exp;
            (el.ifConditions || el.ifConditions = []).push({
                exp,
                block: el
            })
        } else {
            // 对于 v-else 和 v-else-if 没有做太多的处理
            // 这二者会调用之后的 processIfConditions
            if (getAndRemoveAttr(el, 'v-else') !== null) {
                el.else = true;
            }
            var elseif = getAndRemoveAttr(el, 'v-else-if');
            if (el.elseif) {
                el.elseif = elseif;
            }
        }
    }
    
    function processIfConditions(el, parent) {
        var prev = findPrevElement(parent.children);
        if (prev && prev.if) {
            (prev.ifConditions || prev.ifConditions = []).push({
                exp: el.elseif,
                block: el
            });
        }
    }
    
    /*
        <div>
            <p></p>
            <div v-if="a"></div>
            <strong v-else-if="b"></strong>
            <span v-else></span>
        </div>
        解析之后生成如下
        {
            tag: "header",    
            type: 1,    
            children:[{        
                tag: "header",        
                type: 1,        
                if: "a",        
                ifCondition:[
                    {exp: "a", block: {header的ast 节点}}
                    {exp: "b", block: {strong的ast 节点}}
                    {exp: undefined, block: {span的ast节点}}
                ]
            },{        
                tag: "p"
                type: 1
            }]
        }
    */
    

    slot的解析是通过processSlot来解析的

    function processSlot(el) {
        if (el.tag === 'slot') {
            el.slotName = el.attrsMap.name;
        }
        else {
            var slotScope = getAndRemoveAttr(el, 'slot-scope');
            el.slotScope = slotScope;
    
            // slot的名字
            var slotTarget = el.attrsMap.slot;
            if (slotTarget) {
                el.slotTarget = slotTarget === '""'
                    ? '"default"'
                    : slotTarget
            }
        }
    }
    
    /*
        <span>
            <slot name="header" :a="num" :b="num"></slot>
        </span>
        以上模板解析成
        {
            {  
            tag: "span"
            type: 1
            children:[{  
                attrsMap: {name: " header", :a: "num", :b: "num"}
                slotName: "" header""
                tag: "slot"
                type: 1
            }]
        }
    
        父组件模板
        <div>
            <child >
                <p slot="header" slot-scope="c"> {{ c }}</p>
            </child>
        </div>
        解析成
        {    
            children: [{        
                tag: "child",        
                type: 1,        
                children: [{            
                    slotScope: "c",            
                    slotTarget: ""header "",            
                    tag: "p",            
                    type: 1
                }]
            }],    
            tag: "div",    
            type: 1
        }
    */
    

    Vue自带属性v-, :, @三种符号的属性名,会分开处理

    先来看:的情况,经过Vue的处理,:开头的属性会被放入el.props或者el.attrs中。

    当我们给指令添加了.prop的时候

    <!-- 这个属性会被存放到el.props中 -->
    <div :name.props="myName"></div>
    

    props是直接添加到DOM属性上的,attrs是直接显示在标签上的。添加props的时候,需要转化成驼峰法,因为DOM元素的props不支持-连接的。

    当匹配到@或者v-on时,属于事件添加,没有太多处理

    <div @click="aaa" @keyup="bbb"></div>
    <!-- 
        {
            events: {
                click: { value: 'aaa' },
                keyup: { value: 'bbb' }
            }
        }
     -->
    

    v-开头的会全部保存到el.directives

    <div v-a:key="bbb"></div>
    <!-- 
        {
            directives: [{
                arg: "key",
                modifiers: undefined,
                name: "a",
                rawName: "v-a:key",
                value: "bbb"
            }]
        }
     -->
    

    普通属性直接存放进el.attrs

    <div bbb="ccc"></div>
    <!-- 
        {
            attrs: [{
                name: 'bbb',
                value: 'ccc'
            }]
        }
     -->
    

    总体源码如下

    var onRE = /^@|^v-on:/;
    var dirRE = /^v-|^@|^:/;
    var bindRE = /^:|^v-bind:/;
    var modifierRE = /\.[^.]+/g;
    var argRE = /:(.*)$/;
    function processAttrs(el) {    
        var list = el.attrsList;    
        var i, l, name, rawName, value, modifiers, isProp;   
        for (i = 0, l = list.length; i < l; i++) {
            name = rawName = list[i].name;
            value = list[i].value;        
            // 判断属性是否带有 'v-' , '@' , ':'
            if (dirRE.test(name)) {            
                // mark element as dynamic
                el.hasBindings = true;       
                // 比如 v-bind.a.b.c = "xxzxxxx"
                // 那么 modifiers = {a: true, b: true, c: true}
                modifiers = parseModifiers(name);            
                // 抽取出纯名字
                if (modifiers) {    
                    // name = "v-bind.a.b.c = "xxzxxxx" "
                    // 那么 name= v-bind
                    name = name.replace(modifierRE, '');
                }        
                // 收集动态属性,v-bind,可能是绑定的属性,可能是传入子组件的props
                // bindRE = /^:|^v-bind:/
                if (bindRE.test(name)) {   
                    // 抽取出纯名字,比如 name= v-bind
                    // 替换之后,name = bind
                    name = name.replace(bindRE, '');
                    isProp = false;      
                    if (modifiers) {   
                        // 直接添加到 dom 的属性上
                        if (modifiers.prop) {
                            isProp = true;    
                            // 变成驼峰命名
                            name = camelize(name);                        
                            if (name === 'innerHtml')   
                                name = 'innerHTML'; 
                        }      
                        // 子组件同步修改
                        if (modifiers.sync) {
                            addHandler(el,      
                                // 得到驼峰命名                      
                                "update:" + camelize(name), 
                                // 得到 "value= $event"
                                genAssignmentCode(value, "$event")
                            );
                        }
                    }  
                    // el.props 的作用上面有说,这里有部分是 表单的必要属性都要保存在 el.props 中
                    if (
                         isProp ||
                         // platformMustUseProp 判断这个属性是不是要放在 el.props 中
                         // 比如表单元素 input 等,属性是value selected ,checked 等
                         // 比如 tag=input,name=value,那么value 属性要房子啊 el.props 中
                         (!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name))
                    ) {
                        (el.props || (el.props = [])).push({ 
                            name, 
                            value
                        });
                    } 
    
                    // 其他属性放在 el.attrs 中
                    else {
                        (el.attrs || (el.attrs = [])).push({ 
                            name, 
                            value
                        });
                    }
                }            
                // 收集事件,v-on , onRE = /^@|^v-on:/
                else if (onRE.test(name)) {    
                    // 把 v-on 或者 @ 去掉,拿到真正的 指令名字
                    // 比如 name ="@click" , 替换后 name = "click"
                    name = name.replace(onRE, '');
                    addHandler(el, name, value, modifiers, false);
                }            
                // 收集其他指令,比如 "v-once",
                else { 
                    // 把v- 去掉,拿到真正的 指令名字
                    name = name.replace(dirRE, '');                
                    // name = "bind:key" , argMatch = [":a", "a"]
                    var argMatch = name.match(argRE);                
                    var arg = argMatch && argMatch[1];    
                    if (arg) {                    
                        // 比如 name = "bind:key" ,去掉 :key
                        // 然后 name = "bind"
                        name = name.slice(0, -(arg.length + 1));
                    }
                    (el.directives || (el.directives = [])).push({ 
                        name, 
                        rawName, 
                        value, 
                        arg, 
                        modifiers
                    });
                }
            } else {
    
                (el.attrs || (el.attrs = [])).push({ 
                    name, 
                    value
                });
            }
        }
    }
    

    optimize是Compile的三大步骤之一,是一个性能优化的手段

    // ... parse
    var ast = parse(template.trim(), options);
    if (options.optimize !== false) {
        optimize(ast, options);
    }
    // ...generate
    

    它能遍历AST子树,检测纯静态的子树,即永不需要更改的DOM,Vue内部进行Optimize的方法就是给节点加上static属性。

    function optimize(root, options) {
        if (!root) return;
        makeStatic$1(root);
        makeStaticRoots(root);
    }
    

    先来看看Vue是如何判断static节点的

    function isStatic(node) {
        // 文字表达式
        if (node.type === 2) return false;
        // 纯文本
        if (node.type === 3) return true;
        return node.pre || ( // 如果添加了v-pre指令,表明节点不需要解析了
            !node.hasBindings && // 不能存在指令,事件等
            !node.if && // 不能存在v-if
            !node.for && // 不能存在v-for
            !['slot', 'component'].indexOf(node.tag) > -1 && // 节点名称不能是slot和component
            isPlatformReserverdTag(node.tag) && // 需要时正常的HTML标签
            !isDirectChildOfTemplateFor(node) && // 父辈节点不能是template或者带有v-for
            Object.keys(node).every(isStaticKey) // 该节点所有的属性都需要是静态节点的静态属性
        );
    }
    

    上面提到的makeStatic$1方法,主要用来标记节点是否是静态节点

    // 标记节点是否是静态节点
    
    function markStatic$1(node) {
        node.static = isStatic(node);    
        if (node.type !== 1) return
        // 不要将组件插槽内容设置为静态。
        // 这就避免了
        // 1、组件无法更改插槽节点
        // 2、静态插槽内容无法热加载
        if (        
            // 正常 thml 标签 才往下处理,组件之类的就不可以
            !isPlatformReservedTag(node.tag) &&
            // 标签名是 slot 才往下处理
            node.tag !== 'slot' &&
            // 有 inline-tempalte 才往下处理
            node.attrsMap['inline-template'] == null
        ) {
            return
        }
        // 遍历所有孩子,如果孩子 不是静态节点,那么父亲也不是静态节点
        var l = node.children.length
        for (var i = 0;i < l; i++) {        
            var child = node.children[i];  
            // 递归设置子节点,子节点再调用子节点
            markStatic$1(child);        
            if (!child.static) {
                node.static = false;
            }
        }
        if (node.ifConditions) {    
            var c = node.ifConditions.length  
            for (var j = 1; j < c; j++) {    
                // block 是 节点的 ast
                var block = node.ifConditions[j].block;
                markStatic$1(block);  
                if (!block.static) {
                    node.static = false;
                }
            }
        }
    }
    

    第二步就是标记静态根节点

    // 标记根节点是否是静态节点
    
    function markStaticRoots(node) {    
        if (node.type === 1) return
        // 要使一个节点符合静态根的条件,它应该有这样的子节点
        // 不仅仅是静态文本。否则,吊装费用将会增加
        // 好处大于坏处,最好总是保持新鲜。
        if (        
            // 静态节点
            node.static &&        
            // 有孩子
            node.children.length &&        
            // 孩子有很多,或者第一个孩子不是纯文本
            ! (node.children.length === 1 && node.children[0].type === 3)
        ) {
            node.staticRoot = true;        
            return
        }
        else {
            node.staticRoot = false;
        }    
        if (node.children) {    
            var l = node.children.length    
            for (var i = 0; i < l; i++) {
                markStaticRoots(
                    node.children[i]
                );
            }
        }
    }
    

    markStatic$1 这个函数只是为 markStaticRoots 服务的,是为了先把每个节点都处理之后,更加方便快捷静态根节点。

    被判断为静态根节点的条件

    1. 该节点所有的子孙节点都是静态节点
    2. 必须存在子节点
    3. 子节点不能是纯文本节点。Vue不会将这种节点标记为静态节点,如果将这种节点也标记为静态节点,会起到负优化的作用,下面讨论为什么给纯文本节点标记为静态节点,是一种负优化

    首先标记为静态节点需要维护静态模板存储对象,这个信息存储在_staticTrees中。随着静态根节点的增加,这个存储对象会越来越大,那么占用的内存也会越来越多,势必要增加一些不必要的存储

    其实这个问题涉及到 render 和 静态 render 的合作,

    <div>
        <span>
            <strong>我是静态文本</strong>
        </span>
        <span v-if="testStaticRender"></span>
    </div>
    

    生成的render函数是这样的

    with(this) {
        return _c('div', [
            // 这个函数就是去获取静态模板的,这样会产生很多额外的调用
            _m(0),
            (testStaticRender ? _c('span') : _e())
        ])
    }
    

    genarate

    generate的作用是根据生成的AST节点,拼接成字符串,而这个字符串可以被转化为函数,函数执行后,就会生成VNode

    // options用来传入一些判断函数或者指令
    function generate(ast, options) {
        // CodegenState 给实例初始化编译状态
        var state = new CodegenState(options);
        // genElement 将AST转化为字符串
        var code = ast ? genElement(ast, state) : '_c("div")';
        return {
            render: "with(this){ return " + code + "}",
            staticRenderFns: state.staticRenderFns
        }
    }
    
    function CodegenState(options) {
        this.options = options;      
        // class$1 用于处理AST中的class
        // style$1 用于处理AST中的style
        this.dataGenFns = [ class$1.genData, style$1.genData];      
        this.directives = { on , bind, cloak, model,text ,html]
        // 用来存放静态根节点的render函数
        this.staticRenderFns = [];
    }
    

    genElement是AST拼接成字符串的重点函数,主要是处理各种节点,并且拼接起来

    // 这个里面主要是一个个的处理函数
    function genElement(el, state) {    
        if (
            el.staticRoot && !el.staticProcessed
        ) {
            // 拼接静态节点
            return genStatic(el, state)
        }    
        else if (
            el.for && !el.forProcessed
        ) {        
            return genFor(el, state)
        }    
        else if (
            el.if && !el.ifProcessed
        ) {        
            return genIf(el, state)
        }    
        else if (el.tag === 'slot') {        
            return genSlot(el, state)
        }    
        else {    
            var code;  
            // 处理 is 绑定的组件
            if (el.component) {
                code = genComponent(el.component, el, state);
            }    
            // 上面所有的解析完之后,会走到这一步
            else {  
                // 当 el 不存在属性的时候,el.plain = true
                var data = el.plain ? undefined : genData$2(el, state);  
                // 处理完父节点,遍历处理所有子节点
                var children = genChildren(el, state);
                code = `_c(
                    '${el.tag}'
                    ${data ?  ("," + data) : ''} 
                    ${children ? ("," + children) : ''}
                )`
            }      
            return code
        }
    }
    

    拼接静态节点

    function genStatic(el, state) {
        el.staticProcessed = true;
        state.staticRenderFns.push(
            "with(this){ return " + genElement(el, state) + "}"
        );
        return `_m(${
            state.staticRenderFns.length - 1
        })`;
    }
    

    拼接v-if节点

    // el.ifCondition 是用来存放条件数组的
    function genIf(el, state) {
        el.isProcessed = true;
        return genIfConditions(
            el.ifConditions.slice(),
            state
        );
    }
    
    /*
        <div>
            <p v-if="isShow"></p>
            <span v-else-if="isShow == 2"></span>
            <section v-else></section>
        </div>
        会编译成如下
        {    
            tag:"div",    
            children:[{        
                tag:"p",        
                ifCondition:[{            
                    exp: "isShow",            
                    block: {..p 的 ast 节点}
                },{            
                    exp: "isShow==2",            
                    block: {..span 的 ast 节点}
                },{            
                    exp: undefined,            
                    block: {..section 的 ast 节点}
                }]
            }]
        }
    */
    

    7. 事件拼接

    function genData$2(el, state) {
        var data = '{';
        // 组件自定义事件,比如`<div @click="a"></div>`
        if (el.events) {
            data += genHandlers(el.events, false) + ',';
        }
        // 原生DOM事件,比如 `@click.native`
        if (el.nativeEvents) {
            data += genHandlers(el.nativeEvents, true) + ',';
        }
        data = data.replace(/,$/, '') + '}';
        return data;
    }
    

    从上面的函数可以知道,不管是组件自定义事件还是原生DOM事件,都是调用的genHandlers

    function genHandlers(events, isNative) {
        var res = isNative ? 'nativeOn:{' : 'on:{';
        var handler = events[name];
        for (var name in events) {
            res += ` ${name}:${genHandler(name, handler)}, `
        }
        return res.slice(0, -1) + '}';
    }
    

    修饰符内部配置

    var modifierCode = {
        stop: '$event.stopPropagation();',
        prevent: '$event.preventDefault();',
        ctrl: genGuard("!$event.ctrlKey"),
        shift: genGuard("!$event.shiftKey"),   
        alt: genGuard("!$event.altKey"),
        meta: genGuard("!$event.metaKey"),
        self: genGuard("$event.target !== $event.currentTarget"),
        left: genGuard("'button' in $event && $event.button !== 0"),
        middle: genGuard("'button' in $event && $event.button !== 1"),
        right: genGuard("'button' in $event && $event.button !== 2")
    };
    var genGuard = function(condition) {    
        return ` if ( ${ condition } ) return null `
    };
    
    /*
        比如添加了stop修饰符的,会这么拼接
        "function($event ){ " + 
            "$event.stopPropagation();" + 
            " return "+ aaa +"($event);" +
        "}"
    */
    

    键盘修饰符

    // keys是一个数组,保存的是添加的修饰符,可以是数字,可以是键名
    function genKeyFilter(keys) {
        var key = keys.map(genFilterCode).join('&&');
        return `if( !('button' in $event) && ${ key } )
            return null `;
    }
    
    function genFilterCode(key) {
        var keyVal = parseInt(key);
        // 如果key是数字,那直接返回字符串
        if (keyVal) {
            return "$event.keyCode!==" + keyVal
        }
        // 如果key是键名,比如`enter`
        // 这个键名可能不在keyCodes, keyNames中,可以支持自定义
        var keyCode = keyCodes[key]; // 获取键值,keyName="Enter"
        var keyName = keyNames[key]; // 获取键名,keyCode=13
    
        // $event.keyCode 是按下的键的值
        // $event.key 是按下键的名
        // 比如我们按下字母`V`, 那此时的keyCode是86, key是'v'
        return `
            _k(
                $event.keyCode , 
                ${ key } , ${ keyCode }, 
                ${ $event.key } , ${ keyName }
            )
        `
    }
    
    // 返回的这个_k本体其实就是`checkKeyCodes`函数
    function checkKeyCodes(
        eventKeyCode, key, keyCode, 
        eventKeyName, keyName
    ) {    
        // 比如 key 传入的是自定义名字  aaaa
        // keyCode 从Vue 定义的 keyNames 获取 aaaa 的实际数字
        // keyName 从 Vue 定义的 keyCode 获取 aaaa 的别名
        // 并且以用户定义的为准,可以覆盖Vue 内部定义的
        var mappedKeyCode = config.keyCodes[key] || keyCode;    
        // 该键只在 Vue 内部定义的 keyCode 中 
        if (keyName && eventKeyName && !config.keyCodes[key]) {        
            return isKeyNotMatch(keyName, eventKeyName)
        }
        // 该键只在 用户自定义配置的 keyCode 中
        else if (mappedKeyCode) {        
            return isKeyNotMatch(mappedKeyCode, eventKeyCode)
        }
        // 原始键名
        else if (eventKeyName) {        
            return hyphenate(eventKeyName) !== key
        }
    }
    

    核心的genHandler方法

    function genHandler(name, handler) {
        // 没有绑定回调,返回一个空函数
        if (!handler) {
            return 'function(){}';
        }
        // 如果绑定的是数组,则逐个递归一遍
        if (Array.isArray(handler)) {
            return "[" + handler.map(handler => {
                return genHandler(name, handler);
            }).join(",") + "]";
        }
        // 开始解析单个回调
        var isMethodPath = simplePathRE.test(handler.value);    
        var isFunctionExpression = fnExpRE.test(handler.value);
    
        // 没有modifier
        if (!handler.modifiers) {
            if (isMethodPath || isFunctionExpression) {
                return handler.value;
            }
            // 内连语句,需要包裹一层
            return "function($event){" + handler.value + ";}";
        }
        else {
            var code = "";
            var genModifierCode = ""; // 保存内部修饰符
            for (var key in handler.modifier) {
                if (modifierCode[key]) {
                    
                }
                // 精确修饰符
                else if (key === 'exact') {
    
                }
                // 普通按键
                else {
                    keys.push(key);
                }
            }
        }
    
        // 开始拼接事件回调
        // 拼接Vue定义外的按键修饰符
        if (keys.length) {
            code += genKeyFilter(keys);
        }
        // 把prevent和stop这样的修饰符在按键过滤之后执行
        if (genModifierCode) {
            code += genModifierCode;
        }
        // 事件主体回调
        var handlerCode = isMethodPath ?
            // 执行你绑定的函数
            "return " + handler.value + "($event)" :
            (
                isFunctionExpression
                ? "return " + handler.value + "$event"
                : handler.value
            );
        return `function($event){
            ${code + handlerCode}
        }`
    }
    

    拼接事件回调的3个重点

    1. 拼接按键的修饰符
    2. 拼接内置修饰符
    3. 拼接事件回调
    展开全文
  • <p>Also, it take a lot of time to compile (about 7seconds) with watch on. This never happened with the previous version of laravel mix (1.0.7)</p><p>该提问来源于开源项目:JeffreyWay/laravel-mix...
  • ./node_modules/vue-loader/lib??vue-loader-options!./packages/form-item/src/form-item.vue?vue&type=script&lang=js @ ./packages/form-item/src/form-item.vue?vue&type=script&...
  • 一、Vue.use Vue.use(plugin); (1)参数 { Object | Function }plugin (2)用法 安装Vue.js插件。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。调用install方法时...

    一、Vue.use

    Vue.use(plugin);

    (1)参数

    { Object | Function } plugin

    (2)用法

    安装Vue.js插件。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。

    (3)作用

    注册插件,此时只需要调用install方法并将Vue作为参数传入即可。但在细节上有两部分逻辑要处理:

    1、插件的类型,可以是install方法,也可以是一个包含install方法的对象。

    2、插件只能被安装一次,保证插件列表中不能有重复的插件。

    (4)实现

    Vue.use = function(plugin){
    	const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
    	if(installedPlugins.indexOf(plugin)>-1){
    		return this;
    	}
    	<!-- 其他参数 -->
    	const args = toArray(arguments,1);
    	args.unshift(this);
    	if(typeof plugin.install === 'function'){
    		plugin.install.apply(plugin,args);
    	}else if(typeof plugin === 'function'){
    		plugin.apply(null,plugin,args);
    	}
    	installedPlugins.push(plugin);
    	return this;
    }

    1、在Vue.js上新增了use方法,并接收一个参数plugin

    2、首先判断插件是不是已经别注册过,如果被注册过,则直接终止方法执行,此时只需要使用indexOf方法即可。

    3、使用toArray方法得到arguments。除了第一个参数之外,剩余的所有参数将得到的列表赋值给args,然后将Vue添加到args列表的最前面。这样做的目的是保证install方法被执行时第一个参数是Vue,其余参数是注册插件时传入的参数。

    4、由于plugin参数支持对象和函数类型,所以通过判断plugin.install和plugin哪个是函数,即可知用户使用哪种方式祖册的插件,然后执行用户编写的插件并将args作为参数传入。

    5、最后,将插件添加到installedPlugins中,保证相同的插件不会反复被注册。

    二、Vue.mixin

    Vue.mixin(mixin);

    (1)参数

    { Object } mixin

    (2)用法

    1、全局注册一个混入(mixin),影响之后创建的每个Vue.js实例

    2、插件作者可以使用混入向组件注入自定义行为(例如:监听生命周期钩子)。不推荐在应用代码中使用。

    Vue.mixin({
    	created:function({
    		var myOption = this.$options.myOption;
    		if(myOption){
    			console.log(myOption);
    		}
    	})
    })
    
    new Vue({
    	myOption:'hello!'
    })
    // => "hello!"

    (3)Vue.mixin方法注册后,会影响之后创建的每个Vue.js实例,因为该方法会更改Vue.options属性。

    (4)实现

    import { mergeOptions } from '../util/index'
    
    export function initMixin(Vue){
    	Vue.mixin = function(minxin){
    		this.options = mergeOptions(this.options,mixin);
    		return this;
    	}
    }

    1、mergeOptions会将用户传入的mixin与this.options合并成一个新对象,然后将这个生成的新对象覆盖this.options属性,这里的this.options其实就是Vue.options。

    2、因为mixin方法修改了Vue.options属性,而之后创建的每个实例都会用到该属性,所以会影响创建的每个实例。

    三、Vue.compile

    Vue.compile(tempalte);

    (1)参数

    { string } template

    (2)用法

    编译模板字符串并返回包含渲染函数的对象。只在完整版中才有效。

    var res = Vue.compile('<div><span>{{msg}}</span></div>');
    new Vue({
    	data:{
    		msg:'hello'
    	},
    	render:res.render
    })

    (3)并不是所有Vue.js的构建版本都存在Vue.compile方法。与vm.$mount类似,Vue.compile方法只存在于完整版中。(只有完整版包含编译器)

    (4)实现

    Vue.compile方法只需要调用编译器就可以实现功能。

    Vue.compile = compileToFunctions;

    compileToFunctions方法可以将模板编译成渲染函数。

    四、Vue.version

    (1)作用

    提供字符串形式的Vue.js安装版本号。 这对社区的插件和组件来说非常有用,可以根据不同的版本号采取不同的策略。

    (2)用法

    var version = Number(Vue.version.split('.')[0]);
    if(version === 2){
    	<!-- Vue.js v2.x.x -->
    }else if(version ===1 ){
    	<!-- Vue.js v1.x.x -->
    }else{
    	<!-- 不支持的Vue.js版本 -->
    }

    (3)Vue.version是一个属性。在构建文件的过程中,会读取package.json文件中的version,并将读取出的版本号设置到Vue.version上。

    (4)具体步骤

    1、Vue.js在构建文件的配置中定义了_VERSION_ 常量,使用rollup-plugin-replace插件在构建的过程中将代码中的常量_VERSION_替换成package.json文件中的版本号。

    2、rollup-plugin-replace插件的作用是在构建过程中替换字符串。所以在代码中只需要将VERSION_ 赋值给Vue.version就可以在构建时将package.json文件中的版本号赋值给Vue.version。

    Vue.version = '_VERSION_'
    <!-- 构建完成后,将类似下面这样 -->
    Vue.version = '2.5.2'

     

    展开全文
  • 主要介绍了Vue 中的compile操作方法,非常不错,具有参考借鉴价值,需要的朋友参考下吧
  • <div><h3>Version <p>4.7.2 <h3>Reproduction link ...<h3>Steps to reproduce <p>yarn build <h3>What is expected? <p>compile success <h3>What is actually happening?...vuejs/rollup-plugin-vue</p></div>
  • ![clipboard1](https://user-images.githubusercontent.com/5669954/27072416-a37c6b48-5020-11e7-8147-1155ed6c7df7.png) ![clipboard2]...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,797
精华内容 718
关键字:

compilevue

vue 订阅