知识点:
Loader
HMR
Create React App
Caching
Plugin
SourceMap
Vue Cli 3.0
Shimming
WebpackDevServer
TreeShaking
CodeSplitting
Babel
React
Library
Eslint
PWA
Vue
Mode
性能优化
多页应用
原理
PreLoading
PreFetching
环境变量
TypeScript
Webpack 是什么
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
webapack 打包浅析
1 | const path = require('path'); |
Loader
module 处理方案,loader 有先后顺序,从下到上,从右到左
file-loader
处理图片
1 | module.exports = { |
url-loader
会把图片打包到js 文件中,如果图片很小(1-2kb)的话就适合以base64的形式打包到js里面
1 | module.exports = { |
也可以用来处理字体文件
1 | module: { |
style-loader、css-loader
处理css 样式
css-loader 分析有几个css文件以及它们之间的关系是怎么样的然后合并为一个css
style-loader 会把 css-loader 合并的内容挂载到页面上
node-sass、sass-loader:应对 sass 文件,上面的组合处理sass 文件不会报错,但是不会转义sass 特殊的语法
1 | module: { |
postcss-loader、autoprefixer
自动添加css前缀
在文件的根目录配置 postcss.config.js
1 | module.exports = { |
webapck.config.js 更改
1 | module: { |
如果有样式引用其他样式的情况要重新定义 css-loader 使得被引入的 scss 等有语义的css 文件可以重新跑多一次全部的 loader
1 | module: { |
开启模块化:
1 | module: { |
Plugins
可以在webpack 运行打包到某个时刻帮你实现一些事情
html-webpack-plugin
会在打包结束后自动生成一个 html 文件,并把打包生成的 js 自动引入到这个 html 文件中
1 | module.exports = { |
clean-webpack-plugin
打包之前去掉旧的dist 项目
1 | plugins: [ |
Output、Entry
多文件输出预设的名字
1 | module.export = { |
SouceMap
development 模式下 devtool 默认是true,开启souceMap 通过映射当代码出错的时候可以找到源文件是哪里出错而不是编译后的文件哪里出错.
当然建立这种映射会影响打包速度
1 | module.exports = { |
souce-map会在dist 自动生成一个 js.map 映射文件,而inline-souce-map 则是将这个关系包含在 打包的js文件里面。
使用 cheap-xxx–xxx 的参数会加快打包方式,但是只会提示到几行不会精确到几列。
开发环境(development)推荐使用:cheap-module-eval-source-map(module代表module里面的错误也可以检测到,eval 可以加快编译速度)
生产环境(pruduction)则是:cheap-module-source-map
WebpackDevServer
webpack-dev-server 帮助打包后的运行在自动打开的服务器,并会跟随文件的改变而改变
1 | module.exports = { |
webpack-dev-server 打包的时候会把打包的 dist 目录内置到内存里面而不会显示出来,可以提高编译速度
一开始的时候webpack-dev-server 因为配置不是很完善,一般人都会自己创建一个 server.js 文件来自己建一个服务器
这里使用 express 和 webpack-dev-middleware 来实现
1 | const express = require('express'); |
但是上面的内容是没有实现浏览器自动加载的。
Hot Module Replacement(HMR)
热模块更新,不刷新页面,重新加载css文件
1 | module.exports = { |
只修改对应的 js 文件
1 | if(module.hot){ |
而css 对应的代码是在 css-loader 里面底层实现了,所以不用手写 module.hot
Babel 处理 ES6 语法
babel-loader 与 @babel-core(webpack与 babel 桥梁)
@babel/preset-env
1 | module.exports = { |
这里可以将 ES6 语法转换为 ES5 语法,但是还不完善,接着在index.js里面直接引入
1 | // index.js |
@babel/polyfill 可以自动添加内容实现兼容低版本的浏览器。打包之后会发现打包的js会很大,因为它把所有低版本可能要兼容的代码都写了进去,而不是按需要来增加内容。这个时候可以通过配置来达到按需
1 | module.exports = { |
webpack 进行打包的时候发现会报错
1 | Module not found: Error: Can't resolve 'core-js/modules/es6.array.map' |
之类的错误,通过查阅发现要下载一个 core-js 的插件便可以解决问题,具体可以查看 core-js
而当你使用这个按需加载的时候,就会提示你去掉文件中 import ‘@babel/polyfill’ ,因为会自动加载,另外可以配置要兼容的浏览器版本来判断需要引入那些兼容代码
1 | module.exports = { |
而上面的版本的谷歌浏览器是支持 ES6 语法的,因为打包出来的文件就会很小。可以看出来上面的方式有可能会全局污染(适合小项目),可以使用另外一种方式去配置(适合多模块)
1 | module.exports = { |
配置上面的内容之前需要安装以下几个依赖
1 | npm i -D @babel/runtime @babel/plugin-transfrom-runtime @babel/runtime-corejs2 |
具体可以查看这里
另外可以把 babel-loader options 里面的内容放在一个单独名为 .babelrc 的文件中
1 | { |
打包 React 代码
安装依赖包
1 | npm i -D @babel/preset-react |
配置 .babelrc
1 | { |
这里的顺序也是从下往上
Tree Shaking
development 环境,按需加载,只支持 ES Module ,底层是一个静态引入实现
1 | // index.js 只引入 add 的打包结果 |
production 环境甚至需要 optimization 配置,会自动配置,但是 package.json 中的 sideEffects 需要保留
Development 和 Production 模式的区分打包
1 | { |
1 | // webpack.dev.js |
可以看出上面有很多重复的代码,可以进行抽取优化,用 webpack-merge 进行合并
1 | // webpack.common.js |
Webpack 和 Code Splitting
同步代码
遇到公共类库,会自动打包出来一个文件,例如 vendors~main.js
1 | module.exports = { |
异步代码
安装 babel-plugin-dynamic-import-webpack 后在 .babelrc 里面配置,这个插件不是官方的,所以不支持魔法注释
1 | { |
官方的 @babel/plugin-syntax-dynamic-import 可以支持魔术注释
重新配置 .babelrc
1 | { |
webpack.common.js
1 | module.exports = { |
index.js
1 | // 使用魔法注释 |
打包编译之后就可以看到异步引入的库被自己定义的名字打包出来了
splitChunksPlugins
默认配置:
1 | module.exports = { |
Lazy loading 懒加载
通过 import 语法来异步加载,什么时候要使用则是取决于个人
1 | function getComponent(){ |
打包分析
配置 package.json
1 | { |
上面代码的意思是分析webapck 打包的文件生成后生成 stats.json 。
点击这个网站可以将这个json 文件上传,会帮你自动分析版本,时长以及相关错误,模块之间的关系等等
Preloading,Prefetching
在谷歌浏览器 ctrl+shift+p 输入show Coverage 点击录制后可以看到一个网站首屏加载 js 文件的利用率,以及可以优化的可能。
1 | // 当浏览器空闲的时候,可以先加载这个文件 |
性能优化在缓存上面可以优化的地方有限,可以考虑更多是代码的使用率,将一开始不会用的代码尽量使用异步加载的方式来加载
CSS 文件的代码分割
mini-css-extract-plugin,没有实现 HMR 需要手动配置,所以一般是适用在线上环境使用。
配置如下:
1 | // webpack.common.js |
将之前共同配置的 webpack,common.js module rules 里面关于css 的配置移动到 webpack.dev.js 里面不作修改。然后同样复制一遍到 webpack.prod.js 里面,把关于css-loader 里面的的 style-loader 换成 MiniCssExtractPlugin.loader 作为最后一步来处理。
一个要注意的点是在公共的 optimization 里面加了一个 usedExports:true,同时也要在 package.json 里面配置 sideEffects:[‘@babel/polyfill’,’*.css’]
运行 npm run build 便可以打包分割开 css 文件了
压缩 css 代码
optimize-css-assets-webpack-plugin
1 | // webpack.prod.js |
多个css文件打包到同一个css里面
1 | // webpack.prod.js |
多个css文件按入口文件来打包
1 | // webpack.prod.js |
Webpack 与浏览器缓存(Caching)
增加 hash 值,如果文件没有改变的话,那么打包前后几次打包出来的文件里面的哈希值就不会发生变化,浏览器的缓存也就起到了作用。如果改变了,浏览器则会去请求先的文件而不是继续用原来的缓存文件
1 | // webpack.prod.js |
上面的配置是新版本的webpack直接有的,但是旧版本的 webpack 每次打包 hash 值都会发生变化,可以在 webpack.common.js 里面配置一个参数
1 | module.exports = { |
这个参数会把旧版本中的 manifest 单独抽离出来一个 runtime 开头的 js文件里面,这个 文件主要描述的是库与业务逻辑代码之间的一些关系,旧版本webpack 中这个关系会因为每次打包而发生变化。
Shimming
自动引入某个库
1 | module.exports ={ |
全局this 指向 window
imports-loader
1 | module.exports ={ |
环境变量
可以考虑使用,用个人,具体是在 package.json 里面传递一个 env 参数
1 | { |
而 开发与生产环境的webpack 则是都放到 common 里面来判断分发
1 | // webpack.dev.js |
Library 的打包
简单配置
1 | // webpack.config.js |
PWA(Progressive Web Application) 的打包
网站在被访问过一次之后,如果服务器挂掉了,浏览器还可以利用缓存来访问这个网站。
workbox-webpack-plugin
1 | // 配置 webpack.prod.js |
之后打包编译的是时候会生成多一个 service-worker.js 文件,在项目中运用
1 | // index.js |
这样配置之后,当用户访问过一次网站之后便会进行缓存,如果当服务器挂掉的时候,用户还是可以在浏览器访问到网站
TypeScript 的打包配置
webpack基本配置
1 | const path = require('path'); |
除了webpack 配置之后,在 打包 ts 的时候还必须要配置一个 tsconfig.json
1 | { |
如果要引入其他的库,例如 lodash ,需要安装对应的说明,@types/lodash ,具体的可以查看这里
WebpackDevServer
开发环境实现请求代理转发
1 | // 基本配置 |
解决单页面应用路由问题
当配置路由发现找不到内容的时候,可以配置 historyApiFallback
ESLint 的配置
先安装
1 | npm i eslint -D |
接着是初始化配置
1 | eslint -init |
采用目前流行 airbnb 的方式
会看到项目生成一个 .eslintrc.js
如果没有使用vscode 只能用 eslint src 命令行来查看错误信息
可以进行基本配置:
1 | module.exports = { |
eslint-loader
webpack 也有先关的配置,但是会影响打包的速度,一般会建议使用这种方式来配置。还是使用 vscode 的插件式
1 | module.exports = { |
devServer 配置 overlay 可以在页面提示错误信息
webpack 性能优化
1.升级工具的版本(node,npm,yarn)
2.loader(include/exclude)在尽可能少的模块上使用
3.plugin 尽可能精简可靠
4.resolve 参数合理
1 | module.exports ={ |
5.使用 DllPlugin 提高打包速度
add-asset-html-webpack-plugin
1 | // webpack.dll.js |
6.控制包文件的大小
7.thread-loader,parallel-webpack,happypack 多进程打包
8.合理使用 sourceMap(信息越详细打包越久)
9.结合打包分析 stats.json 优化分析
10.开发环境内存编译,无用插件剔除
多页面打包配置
1 | // webpack.common.js |
如何编写一个 Loader
简单的实现
1 | // replaceLoader.js |
自定义 loader 的用途很多,可以拿来做一个简单的错误检验
1 | const loaderUtils = require('loader-utils'); |
或者是国际化,在相关页面弄一个占位符
1 | module.exports = function (source) { |
可以具体参数可以点击这里
如何编写一个 Plugin
简单的实现
1 | // plugin/copyright-webpack-plugin |
调试
1 | { |
npm run debug
进入下一步,添加 watch compilation 可以对一些类似这样的参数进行调试观察
具体的可以点击这里
Bundler 源码编写(模块分析)
文件目录src 下面有三个文件,分别是 index.js message.js word.js ,引用关系是从左到右导入下一个的文件,具体代码如下:
1 | // word.js |
1 | // 基本配置 |
运行
1 | node bundler.js | highlight |
控制台输出
1 | (function (graph) { |
复制粘贴到浏览器便可以运行
graph 输出内容是
1 | { |
可以看到 code 里面的内容有一个require 函数和一个 exports 对象,所以为了这些代码能够在浏览器运行,我们需要自己创建一个 require函数和exports 空对象。