webpack打包分析
包装
参照官网例子,删掉lodash引用,只保留简短代码:
function component() {
var element = document.createElement('div');
// Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
element.innerHTML = 'hello'
return element;
}
document.body.appendChild(component());
查看输出的bundle.js,折叠代码,主干如下:
(function(modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {...}
// 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) {...};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {...};
// 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) {...})
]);
可以看到输出的代码为一个立即模式的代码块,我们写的代码被包裹在一个一个函数中,作为匿名方法的数组参数元素传给了这个匿名函数。
先看这个匿名函数:
在该函数体中定义了一个 webpack_require 函数,参数为moduleId。接着给这个函数定义了 m,c,d,n,o,p 属性。根据注释中的解释,改为便于阅读的代码就是:
(function(modules) { // webpackBootstrap
// The module cache
// 已加载的 module 缓存池
var installedModules = {};
// require 函数
function webpackRequire(moduleId) {
// 检查模块是否在缓存中,存在则直接返回该模块的 exports
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建一个新模块,并且放入缓存
var module = installedModules[moduleId] = {
identify: moduleId,
loaded: false,
exports: {}
};
// 运行模块函数
modules[moduleId].call(module.exports, module, module.exports, webpackRequire);
// 标记模块已加载
module.loaded = true;
// 返回模块的 exports 属性
return module.exports;
}
// 暴露 webpack 模块,modules 就是参数传进来的数组,是个匿名函数数组
webpackRequire.modules = modules;
// 模块缓存
webpackRequire.cache = installedModules;
// define getter function for harmony exports
webpackRequire.defineGetter = function(exports, name, getter) {
if(!webpackRequire.hasOwnProperty(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// getDefaultExport
webpackRequire.getDefaultExport = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
webpackRequire.defineGetter(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
webpackRequire.hasOwnProperty = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
webpackRequire.publicPath = "";
// 加载入口模块并返回exports
return webpackRequire(webpackRequire.s = 0);
})
/************************************************************************/
([
/* 0 */
/***/ (function(module, exports) {
function component() {
var element = document.createElement('div');
// Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
element.innerHTML = 'hello'
return element;
}
document.body.appendChild(component());
/***/ })
]);
执行的时候,首先我们的代码被匿名函数包装后作为数组的唯一元素传递给了上面一大坨代码的函数。跳过中间的又一大坨定义,最后调用了
return webpackRequire(webpackRequire.s = 0);
即:
return __webpack_require__(__webpack_require__.s = 0);
这个函数传递的参数就是0,然后看webpackRequire函数:
// require 函数
function webpackRequire(moduleId) {// 是0,
// 检查模块是否在缓存中,存在则直接返回该模块的 exports
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建一个新模块,并且放入缓存 // 刚执行的时候肯定不存在,先放入installedModules里,放进去只是一个描述对象
var module = installedModules[moduleId] = {
identify: moduleId, // 原文i
loaded: false, // 原文l
exports: {}
};
// 运行模块函数 // 相当于是包装的那个匿名函数执行了: function(module, exports) {...}
// 指向第一个参数 module.exports ,传入的参数为后面三个参数
modules[moduleId].call(module.exports, module, module.exports, webpackRequire);
// 标记模块已加载
module.loaded = true;
// 返回模块的 exports 属性
return module.exports;
}
这里我们index.js里的代码没有任何依赖,所以只是被webpack call 调用了。
模块引用
新建一个 common.js,简单导出一个变量 name
const name = 'china';
export {name}
在 index.js 中引入
import {name} from './common'
function component() {
var element = document.createElement('div');
// Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
element.innerHTML = name;
return element;
}
document.body.appendChild(component());
编译后参数参数段为:
[
/* 0 */
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", {value: true});
/* harmony import */
var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(1);
function component() {
var element = document.createElement('div');
// Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
element.innerHTML = __WEBPACK_IMPORTED_MODULE_0__common__["a" /* name */];
return element;
}
document.body.appendChild(component());
/***/
}),
/* 1 */
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, "a", function () {
return name;
});
const name = 'china';
/***/
})
]
首先还是第一个函数 [0]被 __webpack_require__
执行,首先传入的参数 __webpack_exports__
是一个对象:
{
i: moduleId,
l: false,
exports: {}
}
添加属性__esModule
为 true
,这里分析到后面代码需要 common 模块的变量,则调用 __webpack_require__(1)
。
__webpack_require__.d(__webpack_exports__, "a", function () {
return name;
});
查看代码可以看到,给exports对象,添加了 a [变量压缩] 属性,定义 get 为一个匿名函数,返回内部的一个常量。回到模块0,__WEBPACK_IMPORTED_MODULE_0__common__
拿到的是 require 函数调用后返回的 exports ,原来的 name 变成了 从该对象获取 a。
对于入口 模块 ,没有导出任何变量或者函数,没有别的模块对它有依赖,因此他的 exports 里只有内部创建的一些标记变量。
loader
还是官网import style.css 的例子,
const path = require('path');
module.exports = {
entry: './src/index.js', // 必须使用相对路径 ./
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
打包之后:
[
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__style_css__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__style_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1__style_css__);
function component() {
var element = document.createElement('div');
// Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
element.innerHTML = __WEBPACK_IMPORTED_MODULE_0__common__["a" /* name */];
element.classList.add('hello');
return element;
}
document.body.appendChild(component());
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) { /**common模块**/}),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
//..
}),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(4)(undefined);
// imports
// module
exports.push([module.i, ".hello {\n\tcolor: red;\n}", ""]);
// exports
}),
/* 4 */
/***/ (function(module, exports) {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
// css base code, injected by the css-loader
// ...
}),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
// ...
}),
/* 6 */
/***/ (function(module, exports) {
/**
* When source maps are enabled, `style-loader` uses a link element with a data-uri to
* embed the css on the page. This breaks all relative urls because now they are relative to a
* bundle instead of the current page.
*
* One solution is to only use full urls, but that may be impossible.
*
* Instead, this function "fixes" the relative urls to be absolute according to the current page location.
*
* A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command.
*
*/
//...
})
/******/ ]
改日分析。。。
多入口
https://doc.webpack-china.org/guides/output-management/
官网管理输出的例子,一个 app 和一个 print 两个入口:
输出后
print.bundle.js
...
return __webpack_require__(__webpack_require__.s = 0); // 这个文件里只有 print 一个模块
...
app.bundle.js
return __webpack_require__(__webpack_require__.s = 1); // 这里包含 4 个模块,index.js的代码包裹在第二个元素中(index:1),因为是入口,所以执行的时候传的是1
CommonsChunkPlugin
使用CommonsChunkPlugin 之后,输出的内容就不同了:
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [], result;
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/ if(executeModules) {
/******/ for(i=0; i < executeModules.length; i++) {
/******/ result = __webpack_require__(__webpack_require__.s = executeModules[i]);
/******/ }
/******/ }
/******/ return result;
/******/ };
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // objects to store loaded and loading chunks
/******/ var installedChunks = {
/******/ 2: 0
/******/ };
由上面的代码可以看到,在 window 注入了 webpackJsonp 函数,在其他模块中去掉了模块 __webpack__require__
:
webpackJsonp([0],{
/***/ 5:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__math_js__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_lodash__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_lodash___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_lodash__);
function component() {
var element = document.createElement('pre');
element.innerHTML = __WEBPACK_IMPORTED_MODULE_1_lodash___default.a.join([
'xxx',
'5 cubed is equal to ' + Object(__WEBPACK_IMPORTED_MODULE_0__math_js__["a" /* cube */])(5)
]);
return element;
}
document.body.appendChild(component());
/***/ })
},[5]);
调用webpackJsonp 函数