浅谈webpack编译vue项目生成的代码探索
本文介绍了webpack编译vue项目生成的代码探索,分享给大家,具体如下:
前言
往 main.js 里写入最简单的 vue 项目结构如下
import vue from 'vue'; import app from './app.vue'; new vue({ el: '#app', template: '<app/>', components: { app } })
app.vue 如下
<template> <div id="app"> <h1>{{ msg }}</h1> <h2>essential links</h2> <ul> <li> <a href="https://vuejs.org" rel="external nofollow" target="_blank">core docs</a> </li> <li> <a href="https://forum.vuejs.org" rel="external nofollow" target="_blank">forum</a> </li> <li> <a href="https://chat.vuejs.org" rel="external nofollow" target="_blank">community chat</a> </li> <li> <a href="https://twitter.com/vuejs" rel="external nofollow" target="_blank">twitter</a> </li> </ul> <h2>ecosystem</h2> <ul> <li> <a href="http://router.vuejs.org/" rel="external nofollow" target="_blank">vue-router</a> </li> <li> <a href="http://vuex.vuejs.org/" rel="external nofollow" target="_blank">vuex</a> </li> <li> <a href="http://vue-loader.vuejs.org/" rel="external nofollow" target="_blank">vue-loader</a> </li> <li> <a href="https://github.com/vuejs/awesome-vue" rel="external nofollow" target="_blank">awesome-vue</a> </li> </ul> </div> </template> <script> export default { name: 'app', data() { return { msg: 'welcome to your vue.js app' } } } </script> <style> #app { font-family: 'avenir', helvetica, arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
编译生成后得到一个316kb的文件,而在316kb中包含着什么,我很好奇想探索一番。
npm run build > learning-in-vue@1.0.0 build /users/everlose/workspace/github/learninginvue > cross-env node_env=production webpack --progress --hide-modules hash: 18d868a423b48dc263e9 version: webpack 3.9.1 time: 3693ms asset size chunks chunk names build.js 316 kb 0 [emitted] [big] main build.js.map 399 kb 0 [emitted] main
代码分解
按顺序往下解读,本篇编译后的代码在这儿,如果只想看结论那么请拉到最后有一张结构梳理图。
webpack 模块机制
前面70行还是熟悉的 webpack 模块机制的基础代码,关于它的细致解读参见上一篇webpack模块机制,编译后的代码格式如下,并且我做了代码美化,并且插上了中文注释
/******/ (function(modules) { // webpackbootstrap /******/ // the module cache /******/ // 缓存模块,所有被加载过的模块都会成为installedmodules对象的属性,靠函数__webpack_require__做到。 /******/ var installedmodules = {}; /******/ /******/ // the require function 核心加载方法 /******/ function __webpack_require__(moduleid) { /******/ /******/ // check if module is in cache /******/ // 检查模块是否已在缓存中,是则直接返回缓存中的模块不需要再次加载 /******/ if(installedmodules[moduleid]) { /******/ return installedmodules[moduleid].exports; /******/ } /******/ // create a new module (and put it into the cache) /******/ // 创造一个新模块并放入缓存中,i是模块标识,l意为是否加载此模块完毕,exports是此模块执行后的输出对象 /******/ var module = installedmodules[moduleid] = { /******/ i: moduleid, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // execute the module function /******/ // 传入参数并执行模块函数 /******/ modules[moduleid].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // flag the module as loaded 标为true代表模块执行完成。 /******/ module.l = true; /******/ /******/ // return the exports of the module 返回此模块输出的对象 /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ // webpack 私有变量,保存传入的modules,即所有的模块组成的数组 /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ // 保存缓存中的模块数组 /******/ __webpack_require__.c = installedmodules; /******/ /******/ // define getter function for harmony exports /******/ // 为 es6 exports 定义 getter /******/ __webpack_require__.d = function(exports, name, getter) { /******/ // 如果 exports 输出的对象本身不包含 name 属性时,定义一个。 /******/ if(!__webpack_require__.o(exports, name)) { /******/ object.defineproperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getdefaultexport function for compatibility with non-harmony modules /******/ // 解决 es module 和 common js module 的冲突,es 则返回 module['default'] /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esmodule ? /******/ function getdefault() { return module['default']; } : /******/ function getmoduleexports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // object.prototype.hasownproperty.call /******/ // 工具方法,判断是否object有property属性。 /******/ __webpack_require__.o = function(object, property) { return object.prototype.hasownproperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ // 大概和 webpack.config.js 的 output 有关吧,webpack 的公共路径 /******/ __webpack_require__.p = "/dist/"; /******/ /******/ // load entry module and return exports 执行第一个依赖模块并且返回它输出。 /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ })
0号模块
导出一个全局变量,在web端就是指代window
/* 0 */ (function (module, exports) { var g; // this works in non-strict mode g = (function () { return this; })(); try { // this works if eval is allowed (see csp) g = g || function("return this")() || (1, eval)("this"); } catch (e) { // this works if the window reference is available if (typeof window === "object") g = window; } // g can still be undefined, but nothing to do about it... // we return undefined, instead of nothing here, so it's // easier to handle this case. if(!global) { ...} module.exports = g; /***/ }),
1号模块
实际上做的事情很明显,就是导出了 main.js 的代码,一个vue实例对象
/* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; object.defineproperty(__webpack_exports__, "__esmodule", { value: true }); /* harmony import */ var __webpack_imported_module_0_vue__ = __webpack_require__(2); /* harmony import */ var __webpack_imported_module_1__app_vue__ = __webpack_require__(6); // 从2号模块导出的一个叫a的变量,就是vue对象本身 new __webpack_imported_module_0_vue__["a" /* default */]({ el: '#app', template: '<app/>', components: { app: __webpack_imported_module_1__app_vue__["a" /* default */] } }); /***/ })
2号模块
即是 vue 源码本身,从114行一直到了10818行,一共是10705行代码,啧啧啧
webpack 有所配置,所以导出的 vue 实际上是 vue/dist/vue.esm.js 的完整编译版本。
/* 2 */ /***/ (function (module, __webpack_exports__, __webpack_require__) { "use strict"; /*! * vue.js v2.5.9 * (c) 2014-2017 evan you * released under the mit license. */ // 作用域指向__webpack_exports__,并把__webpack_require__(0)作为global,实际上就是window // __webpack_require__(3).setimmediate)作为setsetimmediate参数传入函数 (function (global, setimmediate) { // 省略近1w行的代码,关于vue原本本身的解读以后再做...... // 最终 export 出来一个叫 vue$3的对象 /* harmony default export */ __webpack_exports__["a"] = (vue$3); /* webpack var injection */ }.call(__webpack_exports__, __webpack_require__(0), __webpack_require__(3).setimmediate)) }),
3,4,5号模块
都和 node_modules/setimmediate 有关,由于 vue 的 dom 异步更新机制使用到了它,所以被引入。
这里也不做详解,只给出结构。
/* 3 */ /***/ (function (module, exports, __webpack_require__) { // 省略代码... // setimmediate attaches itself to the global object __webpack_require__(4); exports.setimmediate = setimmediate; exports.clearimmediate = clearimmediate; /***/ }), /* 4 */ /***/ (function (module, exports, __webpack_require__) { /* webpack var injection */ (function (global, process) { // 省略代码... }.call(exports, __webpack_require__(0), __webpack_require__(5))) /***/ }), /* 5 */ /***/ (function (module, exports) { // shim for using process in browser var process = module.exports = {}; // 省略代码... process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new error('process.chdir is not supported'); }; process.umask = function () { return 0; }; /***/ }),
6号模块
和 app.vue 的解析有关,把 app.vue 中的 template 和 script 编译为一个 vue components,并把 style 标签内的样式插入到dom中。
/* 6 */ /***/ (function (module, __webpack_exports__, __webpack_require__) { "use strict"; // 返回具体 app.vue 中 的script 中的代码 var __webpack_imported_module_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_app_vue__ = __webpack_require__(13); // 把app.vue 的 template 解析为一堆 vue render 函数。 var __webpack_imported_module_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_66ce2159_hasscoped_false_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_app_vue__ = __webpack_require__(14); // 注入vue文件里写入的css函数 function injectstyle(ssrcontext) { // 由此可知7号模块是编译并插入vue中的css到dom上的 __webpack_require__(7) } // 12号模块用于输出components渲染函数 var normalizecomponent = __webpack_require__(12) /* script */ /* template */ /* template functional */ var __vue_template_functional__ = false /* styles */ var __vue_styles__ = injectstyle /* scopeid */ var __vue_scopeid__ = null /* moduleidentifier (server only) */ var __vue_module_identifier__ = null // 编译模块,混杂template和script。 var component = normalizecomponent( __webpack_imported_module_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_app_vue__["a" /* default */ ], __webpack_imported_module_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_66ce2159_hasscoped_false_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_app_vue__["a" /* default */ ], __vue_template_functional__, __vue_styles__, __vue_scopeid__, __vue_module_identifier__ ) /* harmony default export */ __webpack_exports__["a"] = (component.exports); /***/ }),
7、8、9、10、11
都和样式有关,简言之就是7号模块加载8号模块获取css代码作为参数,并作为参数传入10号模块进行插入
太长也只大意上列出结构
- 7号模块由 style-loader 带入,把所有的css插入到 style 标签里
- 8号模块加载具体的css代码,
- 9号模块由css-loader代入,用于做css的sourcemap
- 10号模块返回具体插入动作函数,供7号模块调用
- 11号模块把所有的样式组成的数组转为字符串,给10号模块做插入。
/* 7 */ /***/ (function (module, exports, __webpack_require__) { // style-loader: adds some css to the dom by adding a <style> tag // load the styles var content = __webpack_require__(8); if (typeof content === 'string') content = [ [module.i, content, ''] ]; if (content.locals) module.exports = content.locals; // add the styles to the dom var update = __webpack_require__(10)("15459d21", content, true); /***/ }), /* 8 */ /***/ (function (module, exports, __webpack_require__) { // css-loader 用于做css的sourcemap exports = module.exports = __webpack_require__(9)(undefined); // imports // module // 这就是 app.vue 文件中 style 里的的 css exports.push([module.i, "#app{font-family:avenir,helvetica,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px}h1,h2{font-weight:400}ul{list-style-type:none;padding:0}li{display:inline-block;margin:0 10px}a{color:#42b983}", ""]); // exports /***/ }), /* 9 */ /***/ (function (module, exports) { // css base code, injected by the css-loader module.exports = function (usesourcemap) { // 省略代码... } }), /* 10 */ /***/ (function (module, exports, __webpack_require__) { /* mit license http://www.opensource.org/licenses/mit-license.php author tobias koppers @sokra modified by evan you @yyx990803 */ // ...太长只贴了关键步骤,总之关键的函数就是这些 var hasdocument = typeof document !== 'undefined' // ... var listtostyles = __webpack_require__(11) // ... var head = hasdocument && (document.head || document.getelementsbytagname('head')[0]) // ... module.exports = function (parentid, list, _isproduction) { // ... var styles = listtostyles(parentid, list) addstylestodom(styles) return function update (newlist) { // ... } } function addstylestodom (styles /* array<styleobject> */) { for (var i = 0; i < styles.length; i++) { // ... domstyle.parts.push(addstyle(item.parts[j])) // .... } } // 总之先调用了addstylestodom,接着是addstyle,再是createstyleelement插入样式到head中。 function createstyleelement () { var styleelement = document.createelement('style') styleelement.type = 'text/css' head.appendchild(styleelement) return styleelement } function addstyle (obj /* styleobjectpart */) { // ... styleelement = createstyleelement() // ... } /***/ }), /* 11 */ /***/ (function (module, exports) { /** * translates the list format produced by css-loader into something * easier to manipulate. */ module.exports = function listtostyles(parentid, list) { var styles = [] var newstyles = {} for (var i = 0; i < list.length; i++) { var item = list[i] var id = item[0] var css = item[1] var media = item[2] var sourcemap = item[3] var part = { id: parentid + ':' + i, css: css, media: media, sourcemap: sourcemap } if (!newstyles[id]) { styles.push(newstyles[id] = { id: id, parts: [part] }) } else { newstyles[id].parts.push(part) } } return styles } /***/ }),
12、13、14号模块
12号做 .vue 文件中的 template 和 script 解析并供6号输出,最终返回了一个 vue components 对象,在浏览器控制台打印如下:
object beforecreate: [ƒ] data: ƒ data() inject: {} name: "app" render: ƒ () staticrenderfns: (2) [ƒ, ƒ, cached: array(2)] _ctor: {0: ƒ} _compiled: true __proto__: object
而13号模块返回具体 script 中的代码,而14号模块则是把 template 解析为一堆 vue render 函数。
/* 12 */ /***/ (function (module, exports) { /* globals __vue_ssr_context__ */ // important: do not use es2015 features in this file. // this module is a runtime utility for cleaner component module output and will // be included in the final webpack user bundle. module.exports = function normalizecomponent( rawscriptexports, compiledtemplate, functionaltemplate, injectstyles, scopeid, moduleidentifier /* server only */ ) { var esmodule var scriptexports = rawscriptexports = rawscriptexports || {} // es6 modules interop var type = typeof rawscriptexports.default if (type === 'object' || type === 'function') { esmodule = rawscriptexports scriptexports = rawscriptexports.default } // vue.extend constructor export interop var options = typeof scriptexports === 'function' ? scriptexports.options : scriptexports // render functions if (compiledtemplate) { options.render = compiledtemplate.render options.staticrenderfns = compiledtemplate.staticrenderfns options._compiled = true } // functional template if (functionaltemplate) { options.functional = true } // scopedid if (scopeid) { options._scopeid = scopeid } var hook if (moduleidentifier) { // server build hook = function (context) { // 2.3 injection context = context || // cached call (this.$vnode && this.$vnode.ssrcontext) || // stateful (this.parent && this.parent.$vnode && this.parent.$vnode.ssrcontext) // functional // 2.2 with runinnewcontext: true if (!context && typeof __vue_ssr_context__ !== 'undefined') { context = __vue_ssr_context__ } // inject component styles if (injectstyles) { injectstyles.call(this, context) } // register component module identifier for async chunk inferrence if (context && context._registeredcomponents) { context._registeredcomponents.add(moduleidentifier) } } // used by ssr in case component is cached and beforecreate // never gets called options._ssrregister = hook } else if (injectstyles) { hook = injectstyles } if (hook) { var functional = options.functional var existing = functional ? options.render : options.beforecreate if (!functional) { // inject component registration as beforecreate hook options.beforecreate = existing ? [].concat(existing, hook) : [hook] } else { // for template-only hot-reload because in that case the render fn doesn't // go through the normalizer options._injectstyles = hook // register for functioal component in vue file options.render = function renderwithstyleinjection(h, context) { hook.call(context) return existing(h, context) } } } return { esmodule: esmodule, exports: scriptexports, options: options } } /***/ }), /* 13 */ /***/ (function (module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony default export */ // 这就是 app.vue 中 script 的部分 __webpack_exports__["a"] = ({ name: 'app', data: function data() { return { msg: 'welcome to your vue.js app' }; } }); /***/ }), /* 14 */ /***/ (function (module, __webpack_exports__, __webpack_require__) { "use strict"; // 把template 解析为一堆 render 函数,扔给vue处理最终编译成vnode节点在渲染成dom输出到视图 var render = function () { var _vm = this; var _h = _vm.$createelement; var _c = _vm._self._c || _h; return _c('div', { attrs: { "id": "app" } }, [_c('h1', [_vm._v(_vm._s(_vm.msg))]), _vm._v(" "), _c('h2', [_vm._v("essential links")]), _vm._v(" "), _vm._m(0, false, false), _vm._v(" "), _c('h2', [_vm._v("ecosystem")]), _vm._v(" "), _vm._m(1, false, false)]) } var staticrenderfns = [function () { var _vm = this; var _h = _vm.$createelement; var _c = _vm._self._c || _h; return _c('ul', [_c('li', [_c('a', { attrs: { "href": "https://vuejs.org", "target": "_blank" } }, [_vm._v("core docs")])]), _vm._v(" "), _c('li', [_c('a', { attrs: { "href": "https://forum.vuejs.org", "target": "_blank" } }, [_vm._v("forum")])]), _vm._v(" "), _c('li', [_c('a', { attrs: { "href": "https://chat.vuejs.org", "target": "_blank" } }, [_vm._v("community chat")])]), _vm._v(" "), _c('li', [_c('a', { attrs: { "href": "https://twitter.com/vuejs", "target": "_blank" } }, [_vm._v("twitter")])])]) }, function () { var _vm = this; var _h = _vm.$createelement; var _c = _vm._self._c || _h; return _c('ul', [_c('li', [_c('a', { attrs: { "href": "http://router.vuejs.org/", "target": "_blank" } }, [_vm._v("vue-router")])]), _vm._v(" "), _c('li', [_c('a', { attrs: { "href": "http://vuex.vuejs.org/", "target": "_blank" } }, [_vm._v("vuex")])]), _vm._v(" "), _c('li', [_c('a', { attrs: { "href": "http://vue-loader.vuejs.org/", "target": "_blank" } }, [_vm._v("vue-loader")])]), _vm._v(" "), _c('li', [_c('a', { attrs: { "href": "https://github.com/vuejs/awesome-vue", "target": "_blank" } }, [_vm._v("awesome-vue")])])]) }] var esexports = { render: render, staticrenderfns: staticrenderfns } /* harmony default export */ __webpack_exports__["a"] = (esexports); /***/ })
总结
结构梳理,一图胜千言
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 列表
下一篇: 数据结构与算法之稀疏数组
推荐阅读
-
详解webpack打包vue项目之后生成的dist文件该怎么启动运行
-
vue-cli+webpack在生成的项目中使用bootstrap实例代码
-
详解基于Vue cli生成的Vue项目的webpack4升级
-
基于Vue cli生成的Vue项目的webpack4升级
-
浅谈webpack编译vue项目生成的代码探索
-
vue项目配置 webpack-obfuscator 进行代码加密混淆的实现
-
webpack vue 项目打包生成的文件,资源文件报404问题的修复方法(总结篇)
-
详解webpack打包vue项目之后生成的dist文件该怎么启动运行
-
vue-cli+webpack在生成的项目中使用bootstrap实例代码
-
详解基于Vue cli生成的Vue项目的webpack4升级