WebPack 笔记
一、webpack的认识?
1. webpack诞生的由来
- ES Modules 存在环境兼容问题,如万恶的ie浏览器
- 模块文件过多,网络请求频繁。每一个模块脚本都需要去服务器进行获取,如果网络带宽不好,影响应用效率从而影响用户体验感。
- 随着前端应用的日益复杂,所有的前端资源都需要模块化,工程化。
二、webpack的基本使用
1. 快速上手
// 安装webpack
yarn add webpack webpack-cli -D
// 运行webpack, 4.0以上的版本支持无webpack.config.js文件打包,默认文件入口为src下的index.js文件,输出到根目录下的dist文件下。
yarn webpack
打包成功且运行结果正常输出,如果没有打包成功不能够使用ESM规范,因为没有再script标签指定type=module
也可以添加自定义配置文件
通过创建webpack.config.js 为webpack运行时指定入口文件
2. 基本配置
demo的webpack版本如下,如果不是指定版本可能会有不一样的情况。
展示配置代码其实并没有多大用,webpack版本不断在迭代,配置的方法都会发生变化最好参考官方文档
1)webpack的工作模式
提供 mode 配置选项,告知 webpack 使用相应模式的内置优化。
mode= production(默认配置) | development | none(不进行任何优化操作,仅打包)
用法
-
通过配置项
-
通过cli传递命令行参数 yarn webpack --mode=development
2)webpack打包运行原理
?? 学到源码在分析
3) webpack的资源模块加载
webpack默认只处理js文件, 如果需要处理不同的资源文件,需要使用 webpack loader来进行处理。
如:需要打包处理css文件。
// 入口文件,导出配置项
const path = require('path')
module.exports = {
entry: './src/style/mian.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'output')
},
mode: 'none',
module: {
rules: [
{
test: /\.css$/,
// 如果配置了多个loader,webpack执行时从后往前进行执行,也就是先执行css-loader , style-loader
use: ['style-loader','css-loader']
// use: 'css-loader'
}
]
}
}
webpack 打包的正确入口文件还是main.js,如果需要使用其他资源在main.js种引入即可。
main.js就相当于前端应用的程序入口。
4) file-loader 与 url-loader
- file-loader 将一个文件中的 import/require() 解析为 url,并且将文件发送到输出文件夹。
- 用于将文件转换为 base64 URI 的 loader。
应用:
直接使用url-loader,通过配置limit选项对于10kb以下的资源使用 url-loader转换为base64 DataURL从而减少资源请求,对于超过10kb因为配置了url-loader的limit属性,所以url-loader会调用file-loader来进行处理。
3. webpack loader的分类
- 编译类
css-loader, babel-loader - 文件类
- 代码检查类
4. webpack兼容多种模块化标准
- ES Modules 标准的 import 声明
- CommonJS 标准的 require 函数
- AMD 标准的 define 函数 和 require 函数
以上模块化标准在项目开发中应该保持模块话风格统一 - 所有样式代码中的@import 指令和 url函数( 如:background: url() )
- 所有HTML 代码中图片的src属性
webpack核心工作过程 - 从项目的入口js文件开始
- 根据资源引用代码如require,import ,url,src等建立代码和资源的依赖关系树
- 递归这个依赖关系树找到每个节点对于的资源文件,根据配置的rules属性对依赖资源使用不同的loader进行处理。
- 将处理的结果生成bundle.js
5. 开发一个 简单的 webpack loader
Loader 的核心原理跟函数式编程中的函数组合一样,将输入根据 Loader 管道进行处理最后返回输出结果。
比如:在调用多个 Loader 去转换一个文件时,每个 Loader 会链式的顺序执行, 第一个 Loader 将会拿到需处理的原内容,上一个 Loader 处理后的结果会传给下一个接着处理,最后的 Loader 将处理后的最终结果返回给 Webpack。
webpack 要求最后的输出结果需要是 Javascript 代码
需求:
开发一个能够处理md文件的loader,最后要将md文件转换为html显示在页面上。
- 先利用 markdown-loader 将md语法转换为html语法
// 每一个loader插件都是一个函数,通过source来接受输入,通过return来返回输出
// marked模块可以将md格式的文件转化为html格式
const marked = require('marked')
module.exports = (source) => {
const html = marked(source)
return html
}
- 在利用 html-loader 将输出结果转换为 js
{
test: /\.md$/,
use: ['html-loader','./markdown-loader.js']
}
打包结果被转换为了 js 代码
6. wepack 常用插件
注意:插件本身也会进行迭代,有可能下面代码会发生报错,详情请参考官方文档。
webpack 插件主要是来配合webpack实现前端工程化的相关的功能。
1. clean-webpack-plugin
自动清除dist目录(打包之后的文件目录)
2. html-webpack-plugin
自动生成一个index.html文件并将打包之后的结果bundle.js通过script标签进行引入。
html-webpack-plugin 还支持根据 lodash模板 来生成对应的html文件。
plugins: [new CleanWebpackPlugin(), new HtmlWebpackPlugin({
title: 'webpackDemo',
customProp: '卧槽',
template: './public/index.html'
})]
也可以生成多个html文件,只需要多次调用html-webpack-plugin,每一个实例只生成一个html文件。
3. copy-webpack-plugin
有时候需要对一些静态资源进行copy,比如 Favicon 图标
将单个文件或整个目录(已存在)复制到构建目录。
copy-webpack-plugin
将public目录下的所有资源除了html文件都copy到打包目录
7. 编写webpack插件
8. webpackDevServe
web开发服务器
当运行开发服务器命令会自动将打包结果存放到内存中,并监听打包依赖的文件的变化,如果发生改变会重新执行打包并自动刷新浏览器。
9. sourcemap
sourcemap源代码地图,因着前端工程化思想我们会将代码打包编译之后运行在开发服务器上,我们在开发阶段引入了代码打包压缩的概念这就导致我们编写的源代码与运行时被编译打包的代码有很多的差异,如果遇到代码报错就无法定位代码出错在什么地方。sourcemap文件是源代码与打包编译之后的代码之间的映射记录文件,通过sourcemap可以使打包编译之后的代码逆向编译成为源代码。
在打包之后的代码中通过注释映入sourcemap文件,我们在浏览器开发工具对报错代码既可以定位错误的位置。
下图配置了soucemap
1) webpack 中 devtool sourceMap 多种配置
webpack 中 devtool的配置有非常的多,不过很多都是关键字的组合排序,理解关键字的意思就能够大致明白devtool的模式了。
https://juejin.cn/post/6844904201311485966
10. HMR 热更新
HMR 模块热替换
具体配置参考官方文档,
我们使用的 vue-cli 提供的vue框架 已经为我们集成了 vue组件的模块热替换的实现,使我们可以在更改组件代码实现模块热替换。react也是。
样式的热替换只要我们使用了style-loader, 该loader内部也已经实现了热替换。
11. webpack不同环境不同配置
1. 通过导出函数来实现
// webpack.config.js
module.expots = (env, args) => {
// 这里的env就是 yarn webpack --env=development, env命令行参数的值。
// args是所有的命令行参数
return webpackconfig
}
2. 对于大型项目使用配置文件
参考webpack官方文档
https://webpack.docschina.org/guides/production/
12. definedPlugin
definedPlugin 为webpack内置插件,可以设置全局变量,并在任何被使用到的模块中使用
const webpack = require('webpack')
module.exports = {
...
plugins: [
new webpack.definedPlugin({
key : value
})
]
}
13. tree-shaking
tree-shaking摇树是一个概念,意思是将代码中没有引入使用到的代码在打包的过程中进行剔除,webpack配置mode为production会自动开启。
tree-shaking工作的前提必须是当前代码所使用的模块话规范是ESM,只有在ESM的情况下才能够生效
(ps: 老版本的babel-loader会将ESM的规范转换为CMJ规范,如果tree-shaking不生效需要检查这里,并更改babel-loader的配置)
optimization: {
usedExports: true, // 打包之后对导出但是未使用的模块,不在实现模块导出,参考图一
minimize: true, // 实现代码压缩,对于无用代码不在打包。
concatenateModules: true, // 打包后将所有模块合并在一起输出在一个函数中,目前webpack5版本仅在生产模式下被使用,其他模块会被禁用。
sideEffects: true // 详见下文
},
sideEffects配置
告知 webpack 去辨识 package.json 中的 副作用 标记或规则,以跳过那些当导出不被使用且被标记不包含副作用的模块。
请注意的是 (副作用)sideEffects 需要在 npm 模块的 package.json 文件中,但并不意味着你需要在你自己的引用那个大模块的项目中的 package.json 中将 sideEffects 设置成 false。
该配置所定义的副作用是指对于那些未有任何成员导出的模块,他们是无用的,在打包的时候会忽略。如:css文件就是最典型的。
webpack.config.js
module.exports = {
//...
optimization: {
sideEffects: true,
},
};
package.json
{
"name": "awesome npm module",
"version": "1.0.0",
"sideEffects": false
}
// params
// false 表示无副作用,此时对于代码中有副作用的代码都会忽略
// [./src/**/*.css, './src/effect.js] 表示下面的文件都是有副作用,但是不能够忽略
13. webpack 代码分割
为什么需要进行代码分割?
如果将所有模块都打到一个包里,会造成单个bundle体积过大,对于网络不好的用户第一次加载会有太长加载的时间。
1. 对于多页应用程序可以采用多打包入口的方式
module.exports = {
mode: 'none',
entry: {
index1: './src/index.js',
album: './src/album.js'
},
output: {
// 使用[name]占位符,动态输出文件名
// [name]最终替换为入口的名称,即entry的key
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
optimization: {
splitChunks: {
// all 表示将所有的公共模块都提取到单独的bundle当中
chunks: 'all'
}
}
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index'] // entry所配置的入口bundle的名称 即entry的key
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
在实现多入口打包的时候肯定会有写公共的模块如css,公共的js等,如果不进行公共的模块的提取的话打包的时候会多次打包公共的内容。
webpack可以在optimization优化配置中开启splitChunks功能,实现提取公共模块打包。
打包后,就会多生成一个album~index.bundle.js文件。
它存放的就是index.js和album.js中公共的模块。
2. import函数动态引入
这里的按需加载指的是:应用运行过程中,需要哪个模块时,才会加载那个模块。
webpack支持使用动态导入的方式,实现模块的按需加载。
在webpack中通过ESM的import函数导出模块可以实现自动分包。
打包结果也生成了3个包。
魔法注释
可以通过魔法注释给动态导入的模块指定chunk名称
// 格式:/*webpackChunkName:'<name>'*/
import(/*webpackChunkName: 'moduleA'*/'./moduleA/index').then(({ default: moduleA }) => {
console.log(moduleA)
for (let i in moduleA) {
const p = document.createElement('p')
p.innerHTML = `<strong>${i}:</strong>${moduleA[i]}`
main.appendChild(p)
}
})
}
14. css 模块的打包
如果css内容超过了150kb,是需要考虑单独将css文件提取出来,通过link的方式引入css。
MiniCssExtractPlugin 可以实现css文件的提取。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 通过该插件自带的loader可以实现以link的方式注入css样式
// 'style-loader', // style-loader 将样式代码通过style标签的形式注入
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin()
]
}
使用MiniCssExtractPlugin 会带来一个问题,生产环境下打包css文件不会进行代码压缩。默认webpack生产模式打包只会讲js文件进行压缩,可以使用OptimizeCssAssetsWebpackPlugin实现css文件的压缩。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebbpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 通过该插件自带的loader可以显示link的方式注入css样式
// 'style-loader', // style-loader 将样式代码通过style标签的形式注入
'css-loader'
]
}
]
},
optimization: {
// 如果配置了minimizer,webpack就默认我们要自定义压缩器插件,所以webpack自带的js压缩就不会工作
minimizer: [
// 如果在此处使用,仅会再开启了optimization才会使用这个插件
new TerserWebpackPlugin(),
new OptimizeCssAssetsWebbpackPlugin(),
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin(),
// 不能够再plugins中使用这个插件,因为这个会造成无论再开发还是生产都会执行这个插件将css代码进行压缩
// new OptimizeCssAssetsWebbpackPlugin()
]
}
15. webpack输出文件名hash
webpack提供了hash占位符
[hash] 整体打包的hash值
[chunkhash] 当前打包模块的hash
[contenthash] 每个文件的hash
为什么要设置输出文件名为hash?
我们再项目上线的时候,会启动服务器的静态资源缓存,后续不在需要请求服务器得到静态资源文件,这样整体的应用相应速度会提升。如果缓存失效的设置的时间过长,我们在应用更新的时候就看到更新的变化,因为不会重新请求服务器。
当我们如果给输出文件名都设置了hash值,每次打包都是不同的hash这样浏览器在请求资源的时候会去向服务端去请求资源而不是从缓存中去取资源。
output: {
filename: '[name]-[contenthash].bundle.js'
},