webpack道明白读
本文抄自《深入浅出webpack》,发起想进修道理的手打一遍,操纵一遍,给他人讲一遍,然后就会了
在浏览前愿望您已有webpack相干的实践经验,不然读了也读不懂
本文浏览须要几分钟,明白须要本身着手操纵蛮长时候
0 设置文件
起首简朴看一下webpack设置文件(webpack.config.js):
var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');
module.exports = {
// 进口文件,是模块构建的动身点,同时每一个进口文件对应末了天生的一个 chunk。
entry: {
bundle: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname, 'app/app.js')
]
},
// 文件途径指向(可加快打包历程)。
resolve: {
alias: {
'react': pathToReact
}
},
// 天生文件,是模块构建的尽头,包括输出文件与输出途径。
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
// 这里设置了处置惩罚各模块的 loader ,包括 css 预处置惩罚 loader ,es6 编译 loader,图片处置惩罚 loader。
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
],
noParse: [pathToReact]
},
// webpack 各插件对象,在 webpack 的事宜流中实行对应的要领。
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
1. 事变道理概述
1.1 基本观点
在相识webpack道理之前,须要控制以下几个中心观点
- Entry: 进口,webpack构建第一步从entry最先
- module:模块,在webpack中一个模块对应一个文件。webpack会从entry最先,递归找出统统依靠的模块
- Chunk:代码块,一个chunk由多个模块组合而成,用于代码兼并与支解
- Loader: 模块转换器,用于将模块的原内容依据需求转换成新内容
- Plugin:拓展插件,在webpack构建流程中的特定时机遇播送对应的事宜,插件能够监听这些事宜的发作,在特定的机遇做对应的事变
1.2 流程概述
webpack从启动到终了顺次实行以下操纵:
graph TD
初始化参数 --> 最先编译
最先编译 -->肯定进口
肯定进口 --> 编译模块
编译模块 --> 完成编译模块
完成编译模块 --> 输出资本
输出资本 --> 输出完成
各个阶段实行的操纵以下:
- 初始化参数:从设置文件(默许webpack.config.js)和shell语句中读取与兼并参数,得出终究的参数
- 最先编译(compile):用上一步获得的参数初始化Comiler对象,加载统统设置的插件,经由过程实行对象的run要领最先实行编译
- 肯定进口:依据设置中的entry找出统统的进口文件
- 编译模块:从进口文件动身,挪用统统设置的Loader对模块举行翻译,再找出该模块依靠的模块,再递归本步骤直到统统进口依靠的文件都经由处置惩罚
- 完成编译模块:经由第四步以后,获得了每一个模块被翻译以后的终究内容以及他们之间的依靠关联
- 输出资本:依据进口和模块之间的依靠关联,组装成一个个包括多个模块的chunk,再将每一个chunk转换成一个零丁的文件到场输出列表中,这是能够修正输出内容的末了机遇
- 输出完成:在肯定好输出内容后,依据设置(webpack.config.js && shell)肯定输出的途径和文件名,将文件的内容写入文件体系中(fs)
在以上历程当中,webpack会在特定的时候点播送特定的事宜,插件监听事宜并实行响应的逻辑,而且插件能够挪用webpack供应的api转变webpack的运转效果
1.3 流程细节
webpack构建流程可分为以下三大阶段。
- 初始化:启动构建,读取与兼并设置参数,加载plugin,实例化Compiler
- 编译:从Entry动身,针对每一个Module串行挪用对应的Loader去翻译文件中的内容,再找到该Module依靠的Module,递归的举行编译处置惩罚
- 输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件体系中
假如只实行一次,流程如上,但在开启监听形式下,流程以下图
graph TD
初始化-->编译;
编译-->输出;
输出-->文本发作变化
文本发作变化-->编译
1.3.1初始化阶段
在初始化阶段会发作的事宜以下
事宜 | 形貌 |
---|---|
初始化参数 | 从设置文件和shell语句中读取与兼并参数,得出终究的参数,这个历程还会实行设置文件中的插件实例化语句 new Plugin() |
实例化Compiler | 实例化Compiler,传入上一步获得的参数,Compiler担任文件监听和启动编译。在Compiler实例中包括了完全的webpack设置,全局只要一个Compiler实例。 |
加载插件 | 顺次挪用插件的apply要领,让插件能够监听后续的统统事宜节点。同时向插件中传入compiler实例的援用,以轻易插件经由过程compiler挪用webpack的api |
environment | 最先运用Node.js作风的文件体系到compiler对象,以轻易后续的文件寻觅和读取 |
Entry-option | 读取设置的Entrys,为每一个Entry实例化一个对应的EntryPlugin,为背面该Entry的递归剖析事变做准备 |
After-plugins | 挪用完统统内置的和设置的插件的apply要领 |
After-resolvers | 依据设置初始化resolver,resolver担任在文件体系中寻觅指定途径的文件 |
#### 1.3.2 编译阶段 (事宜名全为小写)
事宜 | 诠释 |
---|---|
run | 启动一次编译 |
Watch-run | 在监听形式下启动编译,文件发作变化会从新编译 |
compile | 通知插件一次新的编译将要启动,同时会给插件带上compiler对象 |
compilation | 当webpack以开辟形式运转时,每当检测到文件的变化,便有一次新的compilation被建立。一个Compilation对象包括了当前的模块资本、编译天生资本、变化的文件等。compilation对象也供应了许多事宜回调给插件举行拓展 |
make | 一个新的compilation对象建立终了,即将从entry最先读取文件,依据文件范例和编译的loader对文件举行==编译==,编译完后再找出该文件依靠的文件,递归地编译和剖析 |
after-compile | 一次compilation实行完成 |
invalid | 当碰到毛病解触发改事宜,该事宜不会致使webpack退出 |
在编译阶段最重要的事宜是compilation,因为在compilation阶段挪用了Loader,完成了每一个模块的==转换==操纵。在compilation阶段又会发作许多小事宜,以下表
事宜 | 诠释 |
---|---|
build-module | 运用响应的Loader去转换一个模块 |
Normal-module-loader | 在运用loader转换完一个模块后,运用acorn剖析转换后的内容,输出对应的笼统语法树(AST),以轻易webpack对代码举行剖析 |
program | 从设置的进口模块最先,剖析其AST,当碰到require等导入其他模块的语句时,便将其到场依靠的模块列表中,同时关于新找出来的模块递归剖析,终究弄清楚统统模块的依靠关联 |
seal | 统统模块及依靠的模块都经由过程Loader转换完成,依据依靠关联天生Chunk |
2.3 输出阶段
输出阶段会发作的事宜及诠释:
事宜 | 诠释 |
---|---|
should-emit | 统统须要输出的文件已天生,讯问插件有哪些文件须要输出,有哪些不须要输出 |
emit | 肯定好要输出哪些文件后,实行文件输出,==能够在这里猎取和修正输出的内容== |
after-mit | 文件输出终了 |
done | 胜利完成一次完全的编译和输出流程 |
failed | 假如在编译和输出中涌现毛病,致使webpack退出,就会直接跳转到本步骤,插件能够在本事宜中猎取详细的毛病缘由 |
在输出阶段已获得了各个模块经由转化后的效果和其依靠关联,而且将响应的模块组合在一起构成一个个chunk.在输出阶段依据chunk的范例,运用对应的模板天生终究要输出的文件内容. |
//以下代码用来包括webpack运转历程当中的每一个阶段
//file:webpack.config.js
const path = require('path');
//插件监听事宜并实行响应的逻辑
class TestPlugin {
constructor() {
console.log('@plugin constructor');
}
apply(compiler) {
console.log('@plugin apply');
compiler.plugin('environment', (options) => {
console.log('@environment');
});
compiler.plugin('after-environment', (options) => {
console.log('@after-environment');
});
compiler.plugin('entry-option', (options) => {
console.log('@entry-option');
});
compiler.plugin('after-plugins', (options) => {
console.log('@after-plugins');
});
compiler.plugin('after-resolvers', (options) => {
console.log('@after-resolvers');
});
compiler.plugin('before-run', (options, callback) => {
console.log('@before-run');
callback();
});
compiler.plugin('run', (options, callback) => {
console.log('@run');
callback();
});
compiler.plugin('watch-run', (options, callback) => {
console.log('@watch-run');
callback();
});
compiler.plugin('normal-module-factory', (options) => {
console.log('@normal-module-factory');
});
compiler.plugin('context-module-factory', (options) => {
console.log('@context-module-factory');
});
compiler.plugin('before-compile', (options, callback) => {
console.log('@before-compile');
callback();
});
compiler.plugin('compile', (options) => {
console.log('@compile');
});
compiler.plugin('this-compilation', (options) => {
console.log('@this-compilation');
});
compiler.plugin('compilation', (options) => {
console.log('@compilation');
});
compiler.plugin('make', (options, callback) => {
console.log('@make');
callback();
});
compiler.plugin('compilation', (compilation) => {
compilation.plugin('build-module', (options) => {
console.log('@build-module');
});
compilation.plugin('normal-module-loader', (options) => {
console.log('@normal-module-loader');
});
compilation.plugin('program', (options, callback) => {
console.log('@program');
callback();
});
compilation.plugin('seal', (options) => {
console.log('@seal');
});
});
compiler.plugin('after-compile', (options, callback) => {
console.log('@after-compile');
callback();
});
compiler.plugin('should-emit', (options) => {
console.log('@should-emit');
});
compiler.plugin('emit', (options, callback) => {
console.log('@emit');
callback();
});
compiler.plugin('after-emit', (options, callback) => {
console.log('@after-emit');
callback();
});
compiler.plugin('done', (options) => {
console.log('@done');
});
compiler.plugin('failed', (options, callback) => {
console.log('@failed');
callback();
});
compiler.plugin('invalid', (options) => {
console.log('@invalid');
});
}
}
#在目次下实行
webpack
#输出以下内容
@plugin constructor
@plugin apply
@environment
@after-environment
@entry-option
@after-plugins
@after-resolvers
@before-run
@run
@normal-module-factory
@context-module-factory
@before-compile
@compile
@this-compilation
@compilation
@make
@build-module
@normal-module-loader
@build-module
@normal-module-loader
@seal
@after-compile
@should-emit
@emit
@after-emit
@done
Hash: 19ef3b418517e78b5286
Version: webpack 3.11.0
Time: 95ms
Asset Size Chunks Chunk Names
bundle.js 3.03 kB 0 [emitted] main
[0] ./main.js 44 bytes {0} [built]
[1] ./show.js 114 bytes {0} [built]
2 输出文件剖析
2.1 举个栗子
下面经由过程 Webpack 构建一个采纳 CommonJS 模块化编写的项目,该项目有个网页会经由过程 JavaScript 在网页中显现 Hello,Webpack
。
运转构建前,先把要完成该功用的最基本的 JavaScript 文件和 HTML 建立好,须要以下文件:
页面进口文件 index.html
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--导入 Webpack 输出的 JavaScript 文件-->
<script src="./dist/bundle.js"></script>
</body>
</html>
JS 东西函数文件 show.js
// 操纵 DOM 元素,把 content 显现到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 经由过程 CommonJS 范例导出 show 函数
module.exports = show;
JS 实行进口文件 main.js
// 经由过程 CommonJS 范例导入 show 函数
const show = require('./show.js');
// 实行 show 函数
show('Webpack');
Webpack 在实行构建时默许会从项目根目次下的 webpack.config.js
文件读取设置,所以你还须要新建它,其内容以下:
const path = require('path');
module.exports = {
// JavaScript 实行进口文件
entry: './main.js',
output: {
// 把统统依靠的模块兼并输出到一个 bundle.js 文件
filename: 'bundle.js',
// 输出文件都放到 dist 目次下
path: path.resolve(__dirname, './dist'),
}
};
因为 Webpack 构建运转在 Node.js 环境下,所以该文件末了须要经由过程 CommonJS 范例导出一个形貌如何构建的 Object
对象。
|-- index.html
|-- main.js
|-- show.js
|-- webpack.config.js
统统文件停当,在项目根目次下实行 webpack
敕令运转 Webpack 构建,你会发明目次下多出一个 dist
目次,内里有个 bundle.js
文件, bundle.js
文件是一个可实行的 JavaScript 文件,它包括页面所依靠的两个模块 main.js
和 show.js
及内置的 webpackBootstrap
启动函数。 这时候你用浏览器翻开 index.html
网页将会看到 Hello,Webpack
。
2.2 bundle.js文件做了什么
看之前记着:一个模块就是一个文件,
起首看下bundle.js长什么模样:
注重:序号1处是个自实行函数,序号2作为自实行函数的参数传入
详细代码以下:(发起把以下代码放入编辑器中检察,最好让index.html实行下,弄清楚实行的递次)
(function(modules) { // webpackBootstrap
// 1. 缓存模块
var installedModules = {};
// 2. 定义能够在浏览器运用的require函数
function __webpack_require__(moduleId) {
// 2.1搜检模块是不是在缓存里,在的话直接返回
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 2.2 模块不在缓存里,新建一个对象module=installModules[moduleId] {i:moduleId,l:模块是不是加载,exports:模块返回值}
var module = installedModules[moduleId] = {
i: moduleId,//第一次实行动0
l: false,
exports: {}
};//第一次实行module:{i:0,l:false,exports:{}}
// 2.3 实行传入的参数中对应id的模块 第一次实行数组中传入的第一个参数
//modules[0].call({},{i:0,l:false,exports:{}},{},__webpack_require__函数)
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 2.4 将这个模块标记为已加载
module.l = true;
// 2.5 返回这个模块的导出值
return module.exports;
}
// 3. webpack暴露属性 m c d n o p
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
__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;
};
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
__webpack_require__.p = "";
// 4. 实行reruire函数引入第一个模块(main.js对应的模块)
return __webpack_require__(__webpack_require__.s = 0);
})
([ // 0. 传入参数,参数是个数组
/* 第0个参数 main.js对应的文件*/
(function(module, exports, __webpack_require__) {
// 经由过程 CommonJS 范例导入 show 函数
const show = __webpack_require__(1);//__webpack_require__(1)返回show
// 实行 show 函数
show('Webpack');
}),
/* 第1个参数 show.js对应的文件 */
(function(module, exports) {
// 操纵 DOM 元素,把 content 显现到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 经由过程 CommonJS 范例导出 show 函数
module.exports = show;
})
]);
以上看上去庞杂的代码实际上是一个自实行函数(文件作为自实行函数的参数),能够简写以下:
(function(modules){
//模仿require语句
function __webpack_require__(){}
//实行寄存统统模块数组中的第0个模块(main.js)
__webpack_require_[0]
})([/*寄存统统模块的数组*/])
bundles.js能直接在浏览器中运转的缘由是,在输出的文件中经由过程__webpack_require__
函数,定义了一个能够在浏览器中实行的加载函数(加载文件运用ajax完成),来模仿Node.js中的require语句。
本来一个个自力的模块文件被兼并到了一个零丁的 bundle.js 的缘由在于浏览器不能像 Node.js 那样疾速地去当地加载一个个模块文件,而必需经由过程收集请求去加载还未获得的文件。 假如模块数目许多,加载时候会很长,因而把统统模块都寄存在了数组中,实行一次收集加载。
修正main.js,改成import引入模块
import show from './show';
show('Webpack');
在目次下实行webpack
,会发明:
- 天生的代码会有所差别,然则重要的区分是自实行函数的参数差别,也就是2.2代码的第二部份差别
([//自实行函数和上面雷同,参数差别
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__show__["a" /* default */])('Webpack');
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = show;
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
})
]);
参数差别的缘由是es6的import和export模块被webpack编译处置惩罚过了,实在作用是一样的,接下来看一下在main.js中异步加载模块时,bundle.js是如何的
2.3异步加载时,bundle.js代码剖析
main.js
修正以下
import('./show').then(show=>{
show('Webpack')
})
构建胜利后会天生两个文件
- bundle.js 实行进口文件
- 0.bundle.js 异步加载文件
个中0.bundle.js文件的内容以下:
webpackJsonp(/*在其他文件中寄存的模块的ID*/[0],[//本文件所包括的模块
/* 0 */,
/* 1 show.js对应的模块 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */
__webpack_exports__["default"] = show;
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
})
]);
bundle.js文件的内容以下:
注重:bundle.js比上面的bundle.js的区分在于:
- 多了一个
__webpack_require__.e
,用于加载被支解出去的须要异步加载的chunk对应的文件 - 多了一个webpackJsonp函数,用于从异步加载的文件中装置模块
(function(modules) { // webpackBootstrap
// install a JSONP callback for chunk loading
var parentJsonpFunction = window["webpackJsonp"];
// webpackJsonp用于从异步加载的文件中装置模块
// 将webpackJsonp挂载到全局是为了轻易在其他文件中挪用
/**
* @param chunkIds 异步加载的模块中须要装置的模块对应的id
* @param moreModules 异步加载的模块中须要装置模块列表
* @param executeModules 异步加载的模块装置胜利后须要实行的模块对应的index
*/
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()();
}
};
// The module cache
var installedModules = {};
// objects to store loaded and loading chunks
var installedChunks = {
1: 0
};
// 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;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
/**
* 用于加载被支解出去的须要异步加载的chunk对应的文件
* @param chunkId 须要异步加载的chunk对应的id
* @returns {Promise}
*/
__webpack_require__.e = function requireEnsure(chunkId) {
var installedChunkData = installedChunks[chunkId];
if(installedChunkData === 0) {
return new Promise(function(resolve) { resolve(); });
}
// a Promise means "currently loading".
if(installedChunkData) {
return installedChunkData[2];
}
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
// start chunk loading
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = "text/javascript";
script.charset = 'utf-8';
script.async = true;
script.timeout = 120000;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
function onScriptComplete() {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
installedChunks[chunkId] = undefined;
}
};
head.appendChild(script);
return promise;
};
// 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 = "";
// on error function for async loading
__webpack_require__.oe = function(err) { console.error(err); throw err; };
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([//寄存没有经由异步加载的,跟着实行进口文件加载的模块
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then(show=>{
show('Webpack')
})
/***/ })
]);