本文從簡單的例子入手,從打包文件去分析以下三個問題:webpack打包文件是怎樣的?如何做到兼容各大模塊化方案的?webpack3帶來的新特性又是什么?
一個簡單的例子
webpack配置
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
};
簡單的js文件
// src/index.js
console.log('hello world');
webpack打包后的代碼
一看你就會想,我就一行代碼,你給我打包那么多???(黑人問號)
// 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');
/***/ })
/******/ ]);
我們來分析一下這部分代碼,先精簡一下,其實整體就是一個自執行函數,然后傳入一個模塊數組
(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');
/***/ })
/******/ ]);
整體流程是怎樣的呢
__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…
webpack 模塊能夠以各種方式表達它們的依賴關系,幾個例子如下:
強大的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還有很多地方需要去探索深究,敬請期待以后的文章吧~關于本文如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。