溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

淺談webpack編譯vue項目生成的代碼探索

發(fā)布時間:2020-09-19 10:45:18 來源:腳本之家 閱讀:195 作者:大搜車前端團(tuán)隊博客 欄目:web開發(fā)

本文介紹了webpack編譯vue項目生成的代碼探索,分享給大家,具體如下:

前言

往 main.js 里寫入最簡單的 vue 項目結(jié)構(gòu)如下

import Vue from 'vue'; 
import App from './App.vue';

new Vue({ 
  el: '#app',
  template: '<App/>',
  components: {
    App
  }
})

App.vue 如下

<template> 
  <div id="app">
    <h2>{{ msg }}</h2>
    <h3>Essential Links</h3>
    <ul>
      <li>
        <a  rel="external nofollow" target="_blank">Core Docs</a>
      </li>
      <li>
        <a  rel="external nofollow" target="_blank">Forum</a>
      </li>
      <li>
        <a  rel="external nofollow" target="_blank">Community Chat</a>
      </li>
      <li>
        <a  rel="external nofollow" target="_blank">Twitter</a>
      </li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li>
        <a  rel="external nofollow" target="_blank">vue-router</a>
      </li>
      <li>
        <a  rel="external nofollow" target="_blank">vuex</a>
      </li>
      <li>
        <a  rel="external nofollow" target="_blank">vue-loader</a>
      </li>
      <li>
        <a  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;
}

h2, 
h3 { 
  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 

代碼分解

按順序往下解讀,本篇編譯后的代碼在這兒,如果只想看結(jié)論那么請拉到最后有一張結(jié)構(gòu)梳理圖。

webpack 模塊機(jī)制

前面70行還是熟悉的 webpack 模塊機(jī)制的基礎(chǔ)代碼,關(guān)于它的細(xì)致解讀參見上一篇webpack模塊機(jī)制,編譯后的代碼格式如下,并且我做了代碼美化,并且插上了中文注釋

/******/ (function(modules) { // webpackBootstrap
/******/   // The module cache
/******/   // 緩存模塊,所有被加載過的模塊都會成為installedModules對象的屬性,靠函數(shù)__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)
/******/     // 創(chuàng)造一個新模塊并放入緩存中,i是模塊標(biāo)識,l意為是否加載此模塊完畢,exports是此模塊執(zhí)行后的輸出對象
/******/     var module = installedModules[moduleId] = {
/******/       i: moduleId,
/******/       l: false,
/******/       exports: {}
/******/     };
/******/
/******/     // Execute the module function
/******/     // 傳入?yún)?shù)并執(zhí)行模塊函數(shù)
/******/     modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/     // Flag the module as loaded 標(biāo)為true代表模塊執(zhí)行完成。
/******/     module.l = true;
/******/
/******/     // Return the exports of the module 返回此模塊輸出的對象
/******/     return module.exports;
/******/   }
/******/
/******/
/******/   // expose the modules object (__webpack_modules__)
/******/   // webpack 私有變量,保存?zhèn)魅氲膍odules,即所有的模塊組成的數(shù)組
/******/   __webpack_require__.m = modules;
/******/
/******/   // expose the module cache
/******/   // 保存緩存中的模塊數(shù)組
/******/   __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 有關(guān)吧,webpack 的公共路徑
/******/   __webpack_require__.p = "/dist/";
/******/
/******/   // Load entry module and return exports 執(zhí)行第一個依賴模塊并且返回它輸出。
/******/   return __webpack_require__(__webpack_require__.s = 0);
/******/ })

0號模塊

導(dǎo)出一個全局變量,在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號模塊

實際上做的事情很明顯,就是導(dǎo)出了 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號模塊導(dǎo)出的一個叫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 有所配置,所以導(dǎo)出的 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參數(shù)傳入函數(shù)
(function (global, setImmediate) {

  // 省略近1w行的代碼,關(guān)于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 有關(guān),由于 vue 的 DOM 異步更新機(jī)制使用到了它,所以被引入。

這里也不做詳解,只給出結(jié)構(gòu)。

/* 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 的解析有關(guān),把 App.vue 中的 template 和 script 編譯為一個 vue components,并把 style 標(biāo)簽內(nèi)的樣式插入到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 函數(shù)。
  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函數(shù)
  function injectStyle(ssrContext) {
    // 由此可知7號模塊是編譯并插入vue中的css到DOM上的
    __webpack_require__(7)
  }
  // 12號模塊用于輸出components渲染函數(shù)
  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

都和樣式有關(guān),簡言之就是7號模塊加載8號模塊獲取css代碼作為參數(shù),并作為參數(shù)傳入10號模塊進(jìn)行插入

太長也只大意上列出結(jié)構(gòu)

  1. 7號模塊由 style-loader 帶入,把所有的css插入到 style 標(biāo)簽里
  2. 8號模塊加載具體的css代碼,
  3. 9號模塊由css-loader代入,用于做css的sourceMap
  4. 10號模塊返回具體插入動作函數(shù),供7號模塊調(diào)用
  5. 11號模塊把所有的樣式組成的數(shù)組轉(zhuǎn)為字符串,給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}h2,h3{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
  */

  // ...太長只貼了關(guān)鍵步驟,總之關(guān)鍵的函數(shù)就是這些
  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]))
      // ....
    }
  }

  // 總之先調(diào)用了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 函數(shù)。

/* 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 函數(shù),扔給vue處理最終編譯成Vnode節(jié)點在渲染成DOM輸出到視圖
  var render = function () {
    var _vm = this;
    var _h = _vm.$createElement;
    var _c = _vm._self._c || _h;
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_c('h2', [_vm._v(_vm._s(_vm.msg))]), _vm._v(" "), _c('h3', [_vm._v("Essential Links")]), _vm._v(" "), _vm._m(0, false, false), _vm._v(" "), _c('h3', [_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);

  /***/
})


總結(jié)

結(jié)構(gòu)梳理,一圖勝千言

淺談webpack編譯vue項目生成的代碼探索

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI