欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

浅谈webpack编译vue项目生成的代码探索

程序员文章站 2022-07-09 15:45:28
本文介绍了webpack编译vue项目生成的代码探索,分享给大家,具体如下: 前言 往 main.js 里写入最简单的 vue 项目结构如下 impo...

本文介绍了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号模块进行插入

太长也只大意上列出结构

  1. 7号模块由 style-loader 带入,把所有的css插入到 style 标签里
  2. 8号模块加载具体的css代码,
  3. 9号模块由css-loader代入,用于做css的sourcemap
  4. 10号模块返回具体插入动作函数,供7号模块调用
  5. 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项目生成的代码探索

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。