webdpack构建流程
1、初始化:启动构建,读取与并配置参数,加载相应的Plugings,实例化Compiler 2、编译:从entry出发、针对每个module串行调用对应的loader去翻译文件内容,再找到该module依赖的module,递归进行编译处理 3、输出:对编译后的module组合成chunk,再把chunk转换成文件,输出到output指定目录下
入口(entry)
入口(entry point)指示webpack应该使用哪个模块,来作为构建内部依赖图的开始。 进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖
默认值是./src/index.js ,但你可以通过在webpack.config 配置entry 属性,来指定一个或多个不同的入口起点
单页面应用程序
module.exports = {
entry: {
main: './path/to/my/entry/file.js',
},
}
module.exports = {
entry: ['./src/file_1.js', './src/file_2.js'],
output: {
filename: 'xxx.bundle.js',
},
}
我们也可以将一个文件路径数组传递给entry属性。这将创建一个所谓的‘multi-mian entry’ 在你想要一次多个注入多个依赖文件,并且将他们的依赖绘制在一个chunk中时,这种方式很有效
多页面应用程序
module.exports = { entry: { moduleA: './src/moduleA/index.js', moduleB: './src/moduleB/index.js', moduleC: './src/moduleC/index.js', }, }
输出(output)
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
你可以通过在配置中指定一个 output 字段,来配置这些处理过程:
const path = require('path');
module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js', }, };
在上面的示例中,我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。
装载机
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,,以供应用程序使用,以及被添加到依赖图中。webpack 的其中一个强大的特性就是能通过 import 导入任何类型的模块(例如 .css 文件),其他打包程序或任务执行器的可能并不支持。在更高层面,在 webpack 的配置中,loader 有两个属性:
test属性,识别出哪些文件会被转换。
use属性,定义出在进行转换时,应该使用哪个 loader。
const path = require('path');
module.exports = { output: { filename: 'my-first-webpack.bundle.js', }, module: { rules: [{ test: /.txt$/, use: 'raw-loader' }], }, };
以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”
file-loader和url-loader的区别
url-loader内部使用了file-loader,都可以处理图片和字体,不过url-loader还可以将较小资源转化成base64字符串。
file-loader:文件加载器将文件的import/require()解析为URL,并将文件发送到输出目录。 url-loader:用于将文件转换为base64 URI的Webpack加载程序。
插件(plugin)
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装 const webpack = require('webpack'); // 用于访问内置插件
module.exports = { module: { rules: [{ test: /.txt$/, use: 'raw-loader' }], }, plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })], };
在上面的示例中,html-webpack-plugin 为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。
自定义插件
编写webpak插件步骤如下: 1、命名的js函数或js类 2、在原型中定义apply方法 3、指定要点击的事件挂钩 4、操纵网页包内部示例特定的数据 5、在功能完成后调用webpack提供的回调
定义一个类*
class MyExampleWebpackPlugin { // 将“apply”定义为其原型方法,该原型方法随编译器提供作为其参数 apply(compiler) { // 指定要附加到的事件挂钩 compiler.hooks.emit.tapAsync( 'MyExampleWebpackPlugin', (compilation, callback) => { console.log('我是一个简单的webpack插件'); console.log( '这是compilation对象', compilation );
// 使用webpack提供的插件API操作构建
compilation.addModule(/* ... */);
callback();
}
);
} }
模式(mode)
通过选择 development, production 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production
浏览器兼容性(browser compatibility)
webpack支持所有 符合es5标准的浏览器不支持 IE8 及以下版本)。webpack 的进口和要求确保承若,如果你想要支持旧版本浏览器,在使用这些表达式之前,还需要提前加载 polyfill
摇树
你可以将应用程序想象为一棵树。实际使用的源代码和库表示树的绿色活叶,死亡代码代表了被秋天消耗掉的棕色枯叶。为了除掉枯叶,你必须摇动这棵树,使它们落下,这就是所谓的“摇树优化”。
Webpack4里,配置参数mode为production(即生产环境)时,默认开启了Tree Shaking功能,若值为none则关闭了Tree Shaking。例如值引用了loadsh的isEqual方法,打包时只打引用过的模块。所以,我们为了利用摇树的优势,建议如下
使用ES2015模块语法(如import 和 export); 确保没有编译器将ES2015模块语法转换为CommonJS模块(这是流行的babel preset@babel/preset env的默认行为-有关详细信息, 将”sideEffects“(副作用)属性添加到项目的package.json文件中; 使用生产模式配置选项启用各种优化,包括压缩和Tree Shaking;
示波器吊装
原理:将所有模块的代码按照引用顺序放在一个函数作用域里 ,然后适当的重命名一些变量以防止变量名冲突。通过scope hoisting可减少函数声明代码合内存开销。
如果不开启scope hoisting,会导致大量作用域包裹代码,导致体积增大(模块越多越明显),运行代码时创建的函数作用域变多(大量的IIFE匿名闭包),内存开销大。
Webpack4环境,配置参数mode为production(即生产环境)时,默认开启了scope hoisting功能。
Webpack3环境,需要在plugins里手动配置,新的 webpack.optimize.ModuleConcatenationPlugin() 会将es6里的import转换成网页包要求,export转换成webpack_exports
代码拆分
代码拆分是Webpack最引人注目的特性之一。此功能允许您将代码拆分为各种捆绑包,然后可以按需或并行加载。它可以用来实现更小的捆绑包和控制资源负载优先级,如果正确使用,这会对加载时间产生重大影响。
有三种通用的代码拆分方法:
入口点:使用入口配置手动拆分代码。 防止重复:使用SplitChunksPlugin来删除和拆分块。 动态导入:通过模块内的内联函数调用拆分代码。
开发环境热更新
本话题主要想讲webpack-dev-server 与 hot-module-replacement-plugin之间的关系,需要从两者的功能上来分析说明。
单独写两个包也是出于功能的解耦来考虑的。简单来说就是:hot-module-replacement-plugin 包给 webpack-dev-server 提供了热更新的能力。
webpack-dev-server(WDS)的功能提供 bundle server的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访问,另外 WDS 也提供 livereload(浏览器的自动刷新)。 hot-module-replacement-plugin 的作用是提供 HMR 的 runtime,并且将 runtime 注入到 bundle.js 代码里面去。一旦磁盘里面的文件修改,那么 HMR server 会将有修改的 js module 信息发送给 HMR runtime,然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。
webpack 开发服务器
webpack-dev-server 是webpack-dev-middleware的封装版,细聊一下webpack-dev-middleware灵活的场景?
webpack-dev-server实际上相当于启用了一个express的Http服务器+调用webpack-dev-middleware。它的作用主要是用来伺服资源文件。这个Http服务器和client使用了websocket通讯协议,原始文件作出改动后,webpack-dev-server会用webpack实时的编译,再用webpack-dev-middleware将webpack编译后文件会输出到内存中。适合纯前端项目,很难编写后端服务,进行整合。
webpack-dev-middleware输出的文件存在于内存中。你定义了 webpack.config,webpack 就能据此梳理出entry和output模块的关系脉络,而 webpack-dev-middleware 就在此基础上形成一个文件映射系统,每当应用程序请求一个文件,它匹配到了就把内存中缓存的对应结果以文件的格式返回给你,反之则进入到下一个中间件。
文件指纹(hash值) hash,如果都使用hash的话,因为这是工程级别的,即每次修改任何一个文件,所有文件名的hash值都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。 chunkhash,它可根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。
在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,所以某个文件的改动只会影响它本身的hash,不会影响其它文件。引用了,所以它们共用相同的chunkhash值。但这样子是有问题的,如果list.js修改了代码,css文件就算内容没有任何改变,由于是该模块的 hash 发生了改变,其css文件的hash也会随之改变。这个时候我们就可以使用内容哈希了,保证即使css文件所处的模块里有任何内容的改变,只要 css 文件内容不变,那么它的hash就不会发生变化。contenthash 你可以简单理解为是模块ID + 内容所生成的哈希