webpack
# webpack 的构建流程是什么
初始化参数: 解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果;
开始编译: 上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件 监听 webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译;
确定入口: 从配置的 entry 入口,开始解析文件构建 AST 语法树,找出依赖,递归下去;
编译模块: 递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
完成模块编译并输出: 递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 或分包配置生成代码块 chunk;
输出完成: 输出所有的 chunk 到文件系统;
# webpack 的热更新原理 HMR 实现原理
其实是自己开启了 express 应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接,当文件变化触发 webpack 进行编译并完成后,会通过 sokcet 消息告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块,webpack-dev-server 可以支持热更新,通过生成 文件的 hash 值来比对需要更新的模块,浏览器再进行热替换
服务端
- 启动 webpack-dev-server 服务器
- 创建 webpack 实例
- 创建 server 服务器
- 添加 webpack 的 done 事件回调
- 编译完成向客户端发送消息
- 创建 express 应用 app
- 设置文件系统为内存文件系统
- 添加 webpack-dev-middleware 中间件
- 中间件负责返回生成的文件
- 启动 webpack 编译
- 创建 http 服务器并启动服务
- 使用 sockjs 在浏览器端和服务端之间建立一个 websocket 长连接
- 创建 socket 服务器
客户端
- webpack-dev-server/client 端会监听到此 hash 消息
- 客户端收到 ok 消息后会执行 reloadApp 方法进行更新
- 在 reloadApp 中会进行判断,是否支持热更新,如果支持的话发生 webpackHotUpdate 事件,如果不支持就直接刷新浏览器
- 在 webpack/hot/dev-server.js 会监听 webpackHotUpdate 事件
- 在 check 方法里会调用 module.hot.check 方法
- HotModuleReplacement.runtime 请求 Manifest
- 通过调用 JsonpMainTemplate.runtime 的 hotDownloadManifest 方法
- 调用 JsonpMainTemplate.runtime 的 hotDownloadUpdateChunk 方法通过 JSONP 请求获取最新的模块代码
- 补丁 js 取回来或会调用 JsonpMainTemplate.runtime.js 的 webpackHotUpdate 方法
- 然后会调用 HotModuleReplacement.runtime.js 的 hotAddUpdateChunk 方法动态更新 模块代码
- 然后调用 hotApply 方法进行热更
# webpack treeShaking 机制的原理
treeShaking 也叫摇树优化,是一种通过移除多于代码,来优化打包体积的,生产环境默认开启。
原理:
- ES6 Module 引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
- 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码
# webpack 和 gulp 区别(模块化与流的区别)
- webpack 是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js 文件、css 文件等)都看成模块,通过 loader(加载器)和 plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
- gulp 强调的是前端开发的工作流程,我们可以通过配置一系列的 task,定义 task 处理的事务(例如文件压缩合并、雪碧图、启动 server、版本控制等),然后定义执行顺序,来让 gulp 执行这些 task,从而构建项目的整个前端开发流程。
# webpack Plugin 和 Loader 的区别
- Loader
用于对模块源码的转换,loader 描述了 webpack 如何处理非 javascript 模块,并且在 buld 中引入这些依赖。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或者将内联图像转换为 data URL。比如说:CSS-Loader,Style-Loader 等。
- Plugin
目的在于解决 loader 无法实现的其他事,它直接作用于 webpack,扩展了它的功能。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
# webpack 插件如何实现
- webpack 本质是一个事件流机制,核心模块:tabable(Sync + Async)Hooks 构造出 === Compiler(编译) + Compiletion(创建 bundles)
- compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options、loader 和 plugin。当在 webpack 环境中应用一插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境
- compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一个新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态的信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用
- 创建一个插件函数,在其 prototype 上定义 apply 方法,指定一个 webpack 自身的事件钩子
- 函数内部处理 webpack 内部实例的特定数据
- 处理完成后,调用 webpack 提供的回调函数
# 自定义 webpack 插件
编写一个 Bundle 大小的插件:创建一个文件夹 bundlesize-webpack-plugin,在文件夹里创建 index.js,复制下边的代码
const { resolve } = require("path");
const fs = require("fs");
module.exports = class BundlesizeWebpackPlugin {
constructor(options) {
this.options = options || {
sizeLimit: 3,
};
}
formatBytes(bytes, decimals = 2) {
console.log(bytes, "bytesbytesbytes");
if (bytes === 0) return "0 Bytes";
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
}
apply(compiler) {
compiler.hooks.done.tap("BundleSizePlugin", (stats) => {
const { path, filename } = stats.compilation.options.output;
const bundlePath = resolve(path, filename);
const { size } = fs.statSync(bundlePath);
const bundleSize = this.formatBytes(size);
const { sizeLimit } = this.options;
console.log(bundleSize, "bundleSize"); // size in bytes
if (bundleSize < sizeLimit) {
console.log("Safe:Bundle-Size", "\n SIZE LIMIT:", sizeLimit);
} else {
if (bundleSize === sizeLimit) {
console.warn("Warn:Bundle-Size", "\n SIZE LIMIT:", sizeLimit);
} else {
console.error("Unsafe:Bundle-Size", "\n SIZE LIMIT:", sizeLimit);
}
}
});
}
};
在 webpack.config.js 引入写好的自定义插件
const bundlesizeplugin = require("./bundlesize-webpack-plugin");
module.exports = {
// ...
plugins: [
// 引入写好的自定义插件
new bundlesizeplugin({
sizeLimit: 4, // 传参
}),
],
};
# 自定义 webpack loader
编写一个自定义 loader:创建一个文件夹 loader,在文件夹里创建 myLoader.js,复制下边的代码
const loaderUtils = require("loader-utils");
module.exports = function(source) {
// 获取到用户给当前 Loader 传入的 options
const options = loaderUtils.getOptions(this);
console.log(options, "ppp");
var str = `
body {
background: ${options.color};
height: 500px;
}
`;
return str;
};
在 webpack.config.js 引入写好的自定义 loader
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: ["babel-loader"],
},
{
test: /\.css$/,
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
},
{
// 引入写好的自定义loader
loader: resolve(__dirname, "./loader/myLoader.js"),
options: {
// loader的传参
color: "red",
},
},
],
},
],
},
};
# Webpack 有哪些优化手段
exclude/include
通过 exclude、include 配置来确保转译尽可能少的文件。
cache-loader
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。
npm install cache-loader -D
module.exports = {
//...
module: {
//我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader`
rules: [
{
test: /\.jsx?$/,
use: ["cache-loader", "babel-loader"],
},
],
},
};
3.happypack
让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力 它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
const Happypack = require('happypack');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'Happypack/loader?id=js',
include: [path.resolve(__dirname, 'src')]
},
{
test: /\.css$/,
use: 'Happypack/loader?id=css',
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist')
]
}
]
},
plugins: [
new Happypack({
id: 'js', //和rule中的id=js对应
//将之前 rule 中的 loader 在此配置
use: ['babel-loader'] //必须是数组
}),
new Happypack({
id: 'css',//和rule中的id=css对应
use: ['style-loader', 'css-loader','postcss-loader'],
})
]
}
4.externals:
我们可以将一些JS文件存储在 CDN 上(减少 Webpack打包出来的 js 体积),在 index.html 中通过 <script>
标签引入,如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root">root</div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>
复制代码我们希望在使用时,仍然可以通过 import 的方式去引用(如 import $ from 'jquery'),并且希望 webpack 不会对其进行打包,此时就可以配置 externals。
//webpack.config.js
module.exports = {
//...
externals: {
//jquery通过script引入之后,全局中即有了 jQuery 变量
'jquery': 'jQuery'
}
}
使用高版本的webpack (使用webpack4)
多线程/多实例构建:HappyPack(不维护了) thread-loader
缩小打包作用域
充分利用缓存提升二次构建速度
DLLPlugin 提前打包、分包,避免反复编译浪费时间
# 路由懒加载的原理
解决的问题:避免进入首页就加载全部的前端资源造成用户等待时间过长的问题。
# 参考资料
https://www.w3xue.com/exp/article/201810/3317.html (opens new window)