探索webpack模塊以及webpack3新特性

本文從簡單的例子入手,從打包文件去分析如下三個問題:webpack打包文件是怎樣的?如何作到兼容各大模塊化方案的?webpack3帶來的新特性又是什麼?
css

一個簡單的例子

webpack配置webpack

// webpack.config.js
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
};複製代碼

簡單的js文件git

// src/index.js
 console.log('hello world');複製代碼

webpack打包後的代碼
一看你就會想,我就一行代碼,你給我打包那麼多???(黑人問號)github

// dist/bundle.js
 /******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     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)
/******/         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
/******/         module.l = true;
/******/
/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }
/******/
/******/
/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules;
/******/
/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules;
/******/
/******/     // define getter function for harmony exports
/******/     __webpack_require__.d = function(exports, name, getter) {
/******/         if(!__webpack_require__.o(exports, name)) {
/******/             Object.defineProperty(exports, name, {
/******/                 configurable: false,
/******/                 enumerable: true,
/******/                 get: getter
/******/             });
/******/         }
/******/     };
/******/
/******/     // getDefaultExport function for compatibility with non-harmony modules
/******/     __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
/******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/     // __webpack_public_path__
/******/     __webpack_require__.p = "";
/******/
/******/     // Load entry module and return exports
/******/     return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

console.log('hello world');


/***/ })
/******/ ]);複製代碼

咱們來分析一下這部分代碼,先精簡一下,其實總體就是一個自執行函數,而後傳入一個模塊數組web

(function(modules) { 
     //...
 })([function(module, exports) {
     //..
 }])複製代碼

好了,傳入模塊數組作了什麼(其實註釋都很明顯了,我只是大概翻譯一下)數組

/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache 緩存已經load過的模塊
/******/     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) 構造一個模塊並放入緩存
/******/         var module = installedModules[moduleId] = {
/******/             i: moduleId, //模塊id
/******/             l: false, // 是否已經加載完畢
/******/             exports: {} // 對外暴露的內容
/******/         };
/******/
/******/         // Execute the module function 傳入模塊參數,並執行模塊
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/         // Flag the module as loaded 標記模塊已經加載完畢
/******/         module.l = true;
/******/
/******/         // Return the exports of the module 返回模塊暴露的內容
/******/         return module.exports;
/******/     }
/******/
/******/
/******/     // expose the modules object (__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) {
/******/         if(!__webpack_require__.o(exports, name)) { // 假如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
/******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/     // __webpack_public_path__ webpack配置下的公共路徑
/******/     __webpack_require__.p = "";
/******/
/******/     // Load entry module and return exports 最後執行entry模塊而且返回它的暴露內容
/******/     return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

console.log('hello world');


/***/ })
/******/ ]);複製代碼

總體流程是怎樣的呢瀏覽器

  • 傳入module數組
  • 調用__webpack_require__(__webpack_require__.s = 0)
    • 構造module對象,放入緩存
    • 調用module,傳入相應參數modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); (這裏exports會被函數內部的東西修改)
    • 標記module對象已經加載完畢
    • 返回模塊暴露的內容(注意到上面函數傳入了module.exports,能夠對引用進行修改)
      • 模塊函數中傳入module, module.exports, __webpack_require__
      • 執行過程當中經過對上面三者的引用修改,完成變量暴露和引用

webpack模塊機制是怎樣的

咱們能夠去官網看下webpack模塊緩存

doc.webpack-china.org/concepts/mo…sass

webpack 模塊可以以各類方式表達它們的依賴關係,幾個例子以下:babel

  • ES2015 import 語句
  • CommonJS require() 語句
  • AMD define 和 require 語句
  • css/sass/less 文件中的 @import 語句。
  • 樣式(url(...))或 HTML 文件()中的圖片連接(image url)

強大的webpack模塊能夠兼容各類模塊化方案,而且無侵入性(non-opinionated)

咱們能夠再編寫例子一探究竟

CommonJS

修改src/index.js

var cj = require('./cj.js');

console.log('hello world');
cj();複製代碼

新增src/cj.js,保持前面例子其餘不變

// src/cj.js
function a() {
    console.log("CommonJS");
}
module.exports = a;複製代碼

再次運行webpack

/******/ (function(modules) { // webpackBootstrap
  //... 省略代碼
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

let cj = __webpack_require__(1);

console.log('hello world');
cj();


/***/ }),
/* 1 */
/***/ (function(module, exports) {

function a() {
    console.log("CommonJS");
}
module.exports = a;


/***/ })
/******/ ]);複製代碼

咱們能夠看到模塊數組多了個引入的文件,而後index.js模塊函數多了個參數__webpack_require__,去引用文件(__webpack_require__在上一節有介紹),總體上就是依賴的模塊修改了module.exports,而後主模塊執行依賴模塊,獲取exports便可

ES2015 import

新增src/es.js

// src/es.js
export default function b() {
    console.log('ES Modules');
}複製代碼

修改src/index.js

// src/index.js
import es from './es.js';

console.log('hello world');
es();複製代碼

webpack.config.js不變,執行webpack

/******/ (function(modules) { // webpackBootstrap
// ... 省略代碼
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__es_js__ = __webpack_require__(1);


console.log('hello world');
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["a" /* default */])();


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = b;
function b() {
    console.log('ES Modules');
}


/***/ })
/******/ ]);複製代碼

咱們能夠看到它們都變成了嚴格模式,webpack自動採用的

表現其實跟CommonJS類似,也是傳入export而後修改,在主模塊再require進來,

咱們能夠看到這個

Object.defineProperty(__webpack_exports__, "__esModule", { value: true });複製代碼

這個幹嗎用的?其實就是標記當前的exports是es模塊,還記得以前的__webpack_require__.n嗎,咱們再拿出來看看

/******/     // 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;
/******/     };複製代碼

爲了不跟非ES Modules衝突?衝突在哪裏呢?
其實這部分若是你看到babel轉換ES Modules源碼就知道了,爲了兼容模塊,會把ES Modules直接掛在exports.default上,而後加上__esModule屬性,引入的時候判斷一次是不是轉換模塊,是則引入module['default'],不是則引入module

咱們再多引入幾個ES Modules看看效果

// src/es.js
export function es() {
    console.log('ES Modules');
}

export function esTwo() {
    console.log('ES Modules Two');
}

export function esThree() {
    console.log('ES Modules Three');
}

export function esFour() {
    console.log('ES Modules Four');
}複製代碼

咱們多引入esTwo和esFour,可是不使用esFour

// src/index.js
import { es, esTwo, esFour} from './es.js';

console.log('hello world');
es();
esTwo();複製代碼

得出

/******/ (function(modules) { // webpackBootstrap
// ...
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__es_js__ = __webpack_require__(1);


console.log('hello world');
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["a" /* es */])();
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["b" /* esTwo */])();


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = es;
/* harmony export (immutable) */ __webpack_exports__["b"] = esTwo;
/* unused harmony export esThree */
/* unused harmony export esFour */
function es() {
    console.log('ES Modules');
}

function esTwo() {
    console.log('ES Modules Two');
}

function esThree() {
    console.log('ES Modules Three');
}

function esFour() {
    console.log('ES Modules Four');
}



/***/ })
/******/ ]);複製代碼

嗯嗯其實跟前面是同樣的,舉出這個例子重點在哪裏呢,有沒有注意到註釋中

/* unused harmony export esThree */
/* unused harmony export esFour */複製代碼

esThree是咱們沒有引入的模塊,esFour是咱們引用可是沒有使用的模塊,webpack均對它們作了unused的標記,其實這個若是你使用了webpack插件uglify,經過標記,就會把esThree和esFour這兩個未使用的代碼消除(其實它就是tree-shaking)

AMD

咱們再來看看webpack怎麼支持AMD

新增src/amd.js

// src/amd.js
define([
],function(){
    return {
        amd:function(){
            console.log('AMD');
        }
    };
});複製代碼

修改index.js

// src/index.js
define([
    './amd.js'
],function(amdModule){
    amdModule.amd();
});複製代碼

獲得

/******/ (function(modules) { // webpackBootstrap
// ... 省略代碼
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
    __webpack_require__(1)
], __WEBPACK_AMD_DEFINE_RESULT__ = function(amdModule){
    amdModule.amd();
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
                __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
    return {
        amd:function(){
            console.log('AMD');
        }
    };
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
                __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ })
/******/ ]);複製代碼

先看amd.js整理一下代碼

function(module, exports, __webpack_require__) {
    var __WEBPACK_AMD_DEFINE_ARRAY__,
        __WEBPACK_AMD_DEFINE_RESULT__;

    !(
        __WEBPACK_AMD_DEFINE_ARRAY__ = [],

        __WEBPACK_AMD_DEFINE_RESULT__ = function() {
            return {
                amd: function() {
                    console.log('AMD');
                }
            };
        }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),

        __WEBPACK_AMD_DEFINE_RESULT__ !== undefined &&
        (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)

    );
})複製代碼

簡單來說收集define Array而後置入返回函數,根據參數獲取依賴

apply對數組拆解成一個一個參數

再看index.js模塊部分

function(module, exports, __webpack_require__) {

    var __WEBPACK_AMD_DEFINE_ARRAY__,
        __WEBPACK_AMD_DEFINE_RESULT__;
    !(
        __WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(1)],
        __WEBPACK_AMD_DEFINE_RESULT__ = function(amdModule) {
                amdModule.amd();
        }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),

        __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && 
        (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)
    );
}複製代碼

其實就是引入了amd.js暴露的{amd:[Function: amd]}

css/image?

css和image也能夠成爲webpack的模塊,這是使人震驚的,這就不能經過普通的hack commonjs或者函數調用簡單去調用了,這就是anything to JS,它就須要藉助webpack loader去實現了

像css就是轉換成一段js代碼,經過處理,調用時就是能夠用js將這段css插入到style中,image也相似,這部分就不詳細闡述了,有興趣的讀者能夠深刻去研究

webpack3新特性

咱們能夠再順便看下webpack3新特性的表現
具體能夠看這裏medium.com/webpack/web…

Scope Hoisting

咱們能夠發現模塊數組是一個一個獨立的函數而後閉包引用webpack主函數的相應內容,每一個模塊都是獨立的,而後帶來的結果是在瀏覽器中執行速度變慢,而後webpack3學習了Closure Compiler和RollupJS這兩個工具,鏈接全部閉包到一個閉包裏,放入一個函數,讓執行速度更快,而且總體代碼體積也會有所縮小

咱們能夠實際看一下效果(要注意的是這個特性只支持ES Modules,是不支持CommonJs和AMD的)
使用上面的例子,配置webpack.config.js,增長new webpack.optimize.ModuleConcatenationPlugin()

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
  },
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin(),
  ]
};複製代碼

打包

/******/ (function(modules) { // webpackBootstrap
// ... 省略代碼
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

// CONCATENATED MODULE: ./src/es.js
function es() {
    console.log('ES Modules');
}

function esTwo() {
    console.log('ES Modules Two');
}

function esThree() {
    console.log('ES Modules Three');
}

function esFour() {
    console.log('ES Modules Four');
}


// CONCATENATED MODULE: ./src/index.js
// src/index.js


console.log('hello world');
es();


/***/ })
/******/ ]);複製代碼

咱們能夠驚喜的發現沒有什麼require了,它們拼接成了一個函數,good!😃

Magic Comments

code splitting是webpack一個重點特性之一,涉及到要動態引入的時候,webpack可使用 require.ensure去實現,後來webpack2支持使用了符合 ECMAScript 提案 的 import() 語法,可是它有個不足之處,沒法指定chunk的名稱chunkName,爲了解決這個問題,出現了Magic Comments,支持用註釋的方式去指定,以下

import(/* webpackChunkName: "my-chunk-name" */ 'module');複製代碼

小結

webpack是一個強大的模塊打包工具,在處理依賴、模塊上都很優秀,本文從bundle.js文件分析出發去探索了不一樣模塊方案的加載機制,初步去理解webpack,而且對webpack3特性進行闡述,固然,webpack還有不少地方須要去探索深究,敬請期待之後的文章吧~

最後

謝謝閱讀~
歡迎follow我哈哈github.com/BUPT-HJM
歡迎繼續觀光個人新博客~

歡迎關注

相關文章
相關標籤/搜索