摘要:源码是选用了作为,看的源码时发现对应了不同的构建选项。这也对应了最后打包构建后产出的不同的包。第四种构建方式对应的构建脚本为不同于前面种构建方式这一构建对应于将关于模板编译的成函数的多带带进行打包输出。
Vue源码是选用了rollup作为bundler,看Vue的源码时发现:npm script对应了不同的构建选项。这也对应了最后打包构建后产出的不同的包。
不同于其他的library,Vue为什么要在最后的打包构建环节输出不同类型的包呢?接下来我们通过Vue的源码以及对应的构建配置中简单的去分析下。
由于Vue是基于rollup进行构建的,我们先来简单了解下rollup这个bundler:rollup是默认使用ES Module规范而非CommonJS,因此如果你在你的项目中使用rollup作为构建工具的话,那么可以放心的使用ES Module规范,但是如果要引入只遵循了CommonJs规范的第三包的话,还需要使用相关的插件,插件会帮你将CommonJs规范的代码转为ES Module。得益于ES Module,rollup在构建前进行静态分析,进行tree-shaking。关于tree-shaking的描述请戳我。在构建输出环节,rollup提供了多种文件输出类型:
iife: 立即执行函数
cjs: 遵循CommonJs Module规范的文件输出
amd: 遵循AMD Module规范的文件输出
umd: 支持外链/CommonJs Module/AMD Module规范的文件输出
es: 将多个遵循ES6 Module的文件编译成1个ES6 Module
接下来我们就看看Vue的使用rollup进行构建的几个不同的版本(使用于browser的版本)。
npm run dev 对应 rollup -w -c build/config.js --environment TARGET:web-full-dev
rollup对应的配置信息为:
// Runtime+compiler development build (Browser) "web-full-dev": { entry: resolve("web/runtime-with-compiler.js"), dest: resolve("dist/vue.js"), format: "umd", env: "development", alias: { he: "./entity-decoder" }, banner },
开发环境下输出的umd格式的代码,入口文件是runtime-with-compiler.js,这个入口文件中是将Vue的构建时和运行时的代码都统一进行打包了,通过查看这个入口文件,我们注意到
... import { compileToFunctions } from "./compiler/index" ... const mount = Vue.prototype.$mount Vue.prototype.$mount = function () { }
我们发现,这个文件当中,首先将原来定义的Vue.prototype.$mount方法缓存起来,然后将这个方法进行重写,重写后的方法当中,首先判断是否有自定义的render函数,如果有自定义的render函数的话,Vue不会通过自带的compiler对模板进行编译并生成render函数。但是如果没有自定义的render函数,那么会调用compiler对你定义的模板进行编译,并生成render函数,所以通过这个rollup的配置构建出来的代码既支持自定义render函数,又支持template模板编译:
// 将模板编译成render函数,并挂载到vm实例的options属性上 const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters }, this) options.render = render options.staticRenderFns = staticRenderFns ... // 调用之前缓存的mount函数,TODO: 关于这个函数里面发生了什么请戳我 return mount.call(this, el, hydrating)
接下来看第二种构建方式:
npm run dev:cjs 对应的构建脚本 rollup -w -c build/config.js --environment TARGET:web-runtime-cjs
rollup对应的配置信息为:
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify "web-runtime-cjs": { entry: resolve("web/runtime.js"), dest: resolve("dist/vue.runtime.common.js"), format: "cjs", banner }
最后编译输出的文件是遵循CommonJs Module同时只包含runtime部分的代码,它能直接被webpack 1.x和Browserify直接load。它对应的入口文件是runtime.js:
import Vue from "./runtime/index" export default Vue
这里没有重写Vue.prototye.$mount方法,因此在vm实例的生命周期中,进行到beforeMount阶段时:
// vm挂载的根元素 if (vm.$options.el) { vm.$mount(vm.$options.el) }
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { // vm.$el为真实的node vm.$el = el // 如果vm上没有挂载render函数 if (!vm.$options.render) { // 空节点 vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== "production") { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== "#") || vm.$options.el || el) { warn( "You are using the runtime-only build of Vue where the template " + "compiler is not available. Either pre-compile the templates into " + "render functions, or use the compiler-included build.", vm ) } else { warn( "Failed to mount component: template or render function not defined.", vm ) } } } // 钩子函数 callHook(vm, "beforeMount") let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`${name} patch`, startTag, endTag) } } else { // updateComponent为监听函数, new Watcher(vm, updateComponent, noop) updateComponent = () => { // Vue.prototype._render 渲染函数 // vm._render() 返回一个VNode // 更新dom // vm._render()调用render函数,会返回一个VNode,在生成VNode的过程中,会动态计算getter,同时推入到dep里面 // 在非ssr情况下hydrating为false vm._update(vm._render(), hydrating) } } // 新建一个_watcher对象 // vm实例上挂载的_watcher主要是为了更新DOM // 在实例化watcher的过程中,就会执行updateComponent,完成对依赖的变量的收集过程 // vm/expression/cb vm._watcher = new Watcher(vm, updateComponent, noop) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, "mounted") } return vm }
首先判断vm实例上是否定义了render函数。如果没有,那么就会新建一个新的空vnode并挂载到render函数上。此外,如果页面的渲染是通过传入根节点的形式:
new Vue({ el: "#app" })
Vue便会打出log信息:
warn( "You are using the runtime-only build of Vue where the template " + "compiler is not available. Either pre-compile the templates into " + "render functions, or use the compiler-included build."
意思就是你当前使用的是只包含runtime打包后的代码,模板的编译器(即构建时)的代码并不包含在里面。因此,你不能通过挂根节点或者是声明式模板的方式去组织你的html内容,而只能使用render函数去书写模板内容。不过报错信息里面也给出了提示信息就是,你还可以选择pre-compile预编译工具去将template模板编译成render函数(vue-loader就起到了这个作用)或者是使用包含了compiler的输出包,也就是上面分析的即包含compiler,又包含runtime的包。
第三种构建方式:
npm run dev:esm 对应的构建脚本为: rollup -w -c build/config.js --environment TARGET:web-runtime-esm
入口文件及最后构建出来的代码内容和第二种一样,只包含runtime部分的代码,但是输出代码是遵循ES Module规范的。可以被支持ES Module的bundler直接加载,如webpack2和rollup。
第四种构建方式:
npm run dev:compiler 对应的构建脚本为: rollup -w -c build/config.js --environment TARGET:web-compiler
不同于前面3种构建方式:
// Web compiler (CommonJS). "web-compiler": { entry: resolve("web/compiler.js"), dest: resolve("packages/vue-template-compiler/build.js"), format: "cjs", external: Object.keys(require("../packages/vue-template-compiler/package.json").dependencies) },
这一构建对应于将关于Vue模板编译的成render函数的compiler.js多带带进行打包输出。最后输出的packages/vue-template-compiler/build.js文件会多带带作为一个node_modules进行发布,在你的开发过程中,如果使用了webpack作为构建工具,以及vue-loader,在开发构建环节,vue-loader便会通过web compiler去处理你的*.vue文件中的模板当中的内容,将这些模板字符串编译为render函数。
文档请戳我
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87271.html
摘要:在讲解之前先回顾一下笔者在项目开发中的工作流变化时代此时工作流大致为结合插件处理视图处理样式等库此时由于依赖少手动引入各种标签结合调试界面时代利用指令服务控制器将逻辑拆分为多个文件利用编译会将分为全局样式和组件样式下载各种依赖此时任需要手动 在讲解 webpack 之前先回顾一下笔者在项目开发中的工作流变化. jquery 时代 此时工作流大致为 jquery 结合插件处理视图 bo...
摘要:前端面试题总结持续更新中是哪个组件的属性模块的组件。都提供合理的钩子函数,可以让开发者定制化地去处理需求。 前端面试题总结——VUE(持续更新中) 1.active-class是哪个组件的属性? vue-router模块的router-link组件。 2.嵌套路由怎么定义? 在 VueRouter 的参数中使用 children 配置,这样就可以很好的实现路由嵌套。 //引入两个组件 ...
摘要:以为例,编写来帮助我们完成重复的工作编译压缩我只要执行一下就可以检测到文件的变化,然后为你执行一系列的自动化操作,同样的操作也发生在这些的预处理器上。的使用是针对第三方类库使用各种模块化写法以及语法。 showImg(https://segmentfault.com/img/bVbtZYK); 一:前端工程化的发展 很久以前,互联网行业有个职位叫做 软件开发工程师 在那个时代,大家可能...
阅读 2246·2021-09-30 09:47
阅读 2196·2021-09-26 09:55
阅读 2879·2021-09-24 10:27
阅读 1501·2019-08-27 10:54
阅读 947·2019-08-26 13:40
阅读 2471·2019-08-26 13:24
阅读 2385·2019-08-26 13:22
阅读 1701·2019-08-23 18:38