?本篇文章给大家带来的内容是关于webpack原理的深入介绍(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
|
本篇文章给大家带来的内容是关于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原理之前,需要掌握以下几个核心概念
1.2 流程概述webpack从启动到结束依次执行以下操作: graph TD 初始化参数 --> 开始编译 开始编译 -->确定入口 确定入口 --> 编译模块 编译模块 --> 完成编译模块 完成编译模块 --> 输出资源 输出资源 --> 输出完成 各个阶段执行的操作如下:
在以上过程中,webpack会在特定的时间点广播特定的事件,插件监听事件并执行相应的逻辑,并且插件可以调用webpack提供的api改变webpack的运行结果 1.3 流程细节webpack构建流程可分为以下三大阶段。
如果只执行一次,流程如上,但在开启监听模式下,流程如下图 graph TD 初始化-->编译; 编译-->输出; 输出-->文本发生变化 文本发生变化-->编译 1.3.1初始化阶段在初始化阶段会发生的事件如下
#### 1.3.2 编译阶段 (事件名全为小写)
在编译阶段最重要的事件是compilation,因为在compilation阶段调用了Loader,完成了每个模块的==转换==操作。在compilation阶段又会发生很多小事件,如下表
2.3 输出阶段输出阶段会发生的事件及解释:
在输出阶段已经得到了各个模块经过转化后的结果和其依赖关系,并且将相应的模块组合在一起形成一个个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 在网页中显示 运行构建前,先把要完成该功能的最基础的 JavaScript 文件和 HTML 建立好,需要如下文件: 页面入口文件 <html> <head> <meta charset="UTF-8"> </head> <body> <p id="app"></p> <!--导入 Webpack 输出的 JavaScript 文件--> <script src="./dist/bundle.js"></script> </body> </html> JS 工具函数文件 // 操作 DOM 元素,把 content 显示到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 通过 CommonJS 规范导出 show 函数
module.exports = show;JS 执行入口文件 // 通过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');Webpack 在执行构建时默认会从项目根目录下的 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 规范导出一个描述如何构建的 |-- index.html |-- main.js |-- show.js |-- webpack.config.js 一切文件就绪,在项目根目录下执行 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能直接在浏览器中运行的原因是,在输出的文件中通过 原来一个个独立的模块文件被合并到了一个单独的 bundle.js 的原因在于浏览器不能像 Node.js 那样快速地去本地加载一个个模块文件,而必须通过网络请求去加载还未得到的文件。 如果模块数量很多,加载时间会很长,因此把所有模块都存放在了数组中,执行一次网络加载。 修改main.js,改成import引入模块 import show from './show';
show('Webpack');在目录下执行
([//自执行函数和上面相同,参数不同
/* 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代码分析
import('./show').then(show=>{
show('Webpack')
})构建成功后会生成两个文件
其中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的区别在于:
(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')
})
/***/ })
]);以上就是webpack原理的深入介绍(附示例)的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
