🛴序言
在前面的两篇文章中,我们讲解了 webpack
的入门知识。但是呢,入门知识了解了之后,总得应用到具体的案例当中来。
因此,在下面的这篇文章中,将带领大家来了解关于 webpack
的一些实战案例配置,包括第三方库、 PWA
、 ts
的打包配置,以及 WebpackDevServer
的进阶操作,还有需重点掌握的,关于 webpack
如何做性能优化处理这个问题。
下面开始本文的介绍~🚦
🚌一、Library的打包
假设我们现在要开发一个组件库或者一个函数库时,对于这样的库代码,我们应该如何让 webpack
来进行打包呢?
1. webpack打包库
假如我们现在写了很多逻辑代码,同时呢,我们将这些逻辑代码进行打包,打包之后它全部生成在 dist
文件夹下的 mondaylib.js
文件中。
好了,现在这个库生成了。那如何让我们的用户,来引入 mondaylib
这个库呢?
一般情况下,别人引入我们的库的方法,具体有以下几种方式:
//方式一 import mondaylib from 'mondaylib' //方式二 const mondaylib = require('mondaylib') //方式三 require(['mondaylib'], function(){ }) 复制代码
所以,如果想让我们的用户来引入这个库,我们需要在 webpack.config.js
下进行配置。具体配置如下:
const path = require('path'); module.exports = { mode: 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'mondaylib.js', //此处配置libraryTarget,umd表示支持commonJS这种语法 libraryTarget: 'umd' } } 复制代码
libraryTarget: 'umd'
表示支持 commonJS
这种语法,因此可以引入上面三种方式。同时, libraryTarget
也可以设置为其他值,比如 this
和 window
。
libraryTarget: 'this'
的意思为,我要让 mondaylib
这个变量,挂载到页面上。 libraryTarget: 'window'
则表示为, mondaylib
这个变量,将挂载到 window
上。
除了以上三种情况,还有另外特殊情况,具体如下:
<script src="mondaylib.js"></script> 复制代码
此时我们需要在 webpack.config.js
中进行以下配置:
const path = require('path'); module.exports = { mode: 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'mondaylib.js', //将打包生成的代码挂载到页面的全局变量上 library: 'mondaylib', //此处配置libraryTarget,umd表示支持commonJS这种语法 libraryTarget: 'umd' } } 复制代码
其中,第一个 library
是属性值,第二个 mondaylib
是我们的库名,这个配置的意思为,将打包生成的代码,即 mondaylib.js
这个库,给挂载到全局的变量上。
2. 库引用冲突
假设现在,我们在上面 mondaylib
这个库中,引入了 lodash
这个库。然后呢,当用户使用的时候,用户又再引入了一次lodash这个库。像下面代码这样:
import _ from 'lodash'; //mondaylib这个库原先已经引入过lodash这个库 import mondaylib from 'mondaylib'; 复制代码
所以现在,我们该如何来避免这种问题发生呢?我们再 webpack.config.js
中进行配置,具体代码如下:
const path = require('path'); module.exports = { externals: ["lodash"] } 复制代码
从以上代码中我们可以知道,通过 externals: ["lodash"]
这个配置,来告诉 webpack
,告诉它说,如果在打包过程中, mondaylib
这个库中如果有遇到 lodash
这个库,那么就避开它,不要进行打包。通过这种方式,可以有效地避免库多次引用的问题,减少代码的打包大小。
externals
还有另外一种特殊配置,如下代码所示:
module.exports = { externals: { // 表明lodash这个库如果在commonjs这种环境下被使用,那么要求lodash加载的时候必须叫做lodash lodash: { commonjs: 'lodash' } } } 复制代码
如果这样配置,意在表明 lodash
这个库如果在 commonjs
这种环境下被使用,那么要求 lodash
加载的时候,必须命名为 lodash
,而不能随意命名。像下面这样:
//✔可使用方式:命名为lodash import lodash from 'lodash' //✘不可使用方式:未命名为lodash import _ from 'lodash' 复制代码
🚍二、PWA的打包配置
1. PWA是什么
PWA,全称为 Progressive Web Application
,即渐进式Web应用程序。
PWA
是一门比较新的前端技术,那它是一种什么样的技术呢?
PWA
可以实现的效果是,如果你访问一个网站,有可能第一次你访问成功了,但是呢,突然间这个网站的服务器挂掉了。那么这个时候你访问网站应该就是没办法访问了。但是呢, PWA
会将你第一次访问的页面给缓存起来。之后即使服务器挂掉了,你依然可以把之前看到的页面再展示出来。
因此, webpack
中有一个插件,可以来实现这样的效果。我们来了解一下~
2. webpack中的PWA
第一步: 安装插件。具体代码如下:
npm install workbox-webpack-plugin --save-dev 复制代码
第二步: 在 webpack.prod.js
中引入该插件并使用。具体代码如下:
const WorkboxPlugin = require('workbox-webpack-plugin'); module.exports = { plugins: [ new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true }) ], } 复制代码
通常情况下,我们只需要在线上环境 prod
引入 PWA
,而在开发环境中不需要考虑这个问题。通过以上的配置,我们在项目打包完成之后, dist
目录下将生成两个新的文件,一个是 service-worker.js
,另外一个是 precache.js
文件,这两个文件就是供我们来使用 PWA 的。
第三步: 引入以上文件。具体代码如下:
if('serviceWorker' in navigator){ window.addEventListener('load', () => { navigator.serviceWorker.register('/service-worker.js') .then(registration => { console.log('service-worker registed'); }).catch(error => { console.log('service-worker regist error'); }) }) } 复制代码
我们需要在入口文件中,写一段业务代码,引入 service-worker.js
文件,来帮我们做 PWA
。这个时候我们对项目进行打包,之后呢,如果出现服务器突然挂了的情况,那也不用担心, PWA
会帮我们加载原先的页面来提供给我们浏览。
🚎三、TypeScript的打包配置
1. 引例阐述
我们都知道,对于不同的开发者来说,不同的人写出的代码风格形式各异,这样在后期,项目的维护性就很难得到保证。那么,这个时候,风靡于2018年的 Typescript
出现了。 ts
规范了一套 js
的标准,因此,我们在项目代码的编写中,通过 ts
,就可以规范我们的代码,并且使得我们项目的维可维护性和可扩展性变得更好了。
接下来,我们就来了解一下,如何通过 webpack
的配置变更,来实现对 ts
语法的支持。
2. webpack对ts的配置
(1)背景
假设我们现在有这么一段 ts
的代码需要进行编译,具体代码如下:
class Greeter{ greeting: string; constructor(message: string){ this.greeting = message; } greet(){ return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); let button = document.createElement('button'); button.textContent = "Say Hello"; button.click = function(){ alert(greeter.greet()); } document.body.appendChild(button); 复制代码
现在,我们想要让 webpack
来对这段 ts
代码进行编译,该怎么处理呢?
(2)配置步骤
第一步: 安装 ts-loader
。具体命令如下:
npm install ts-loader typescript -D 复制代码
第二步: 我们在 webpack.config.js
文件下进行配置。具体代码如下:
const path = require('path'); module.exports = { mode: 'production', entry: './src/index.tsx', module: { rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }] }, output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } } 复制代码
第三步: 配置 tsconfig.json
文件。具体代码如下:
{ "compilerOptions": { "outDir": "./dist", "module": "es6", "target": "es5", "allowJs": true } } 复制代码
3. ts识别第三方库
有时候,我们会调用 lodash
中的 join
方法,但是呢,如果我们不进行特殊处理的话,在 tsx
文件中正常引入 lodash
这个库并使用,是不会报错的。因此,我们需要来安装另外一个 ts
的库,来对它进行处理一下。具体步骤如下:
第一步: 安装 @types/lodash
库。具体命令如下:
npm install @types/lodash --save-dev 复制代码
安装完这个库之后, ts
就可以去识别 lodash
的一些函数和方法,一旦出现引用不对,就会进行报错提示。
那有小伙伴就会有疑问说,是不是 ts
拥有所有这样的库(像 jQuery
等各种库)的类型文件呢?
答案当然是否定的。我们可以到 github
上的一个网址👉microsoft.github.io/TypeSearch/来进行搜索,如果搜索得到,那么我们就可以用 @type/库名
来进行安装,之后 tsx
文件就会支持对该库的类型检查。
🚕四、WebpackDevServer进阶操作
1. WebpackDevServer实现请求转发
一般情况下,我们可以通过 charles fiddler
工具,在本地搭建一个代理服务器。通过这台代理服务器,将我们想要请求的接口地址进行转发。
那在 webpack
中,给我们提供了一个工具, devServer.Proxy
。接下来,我们在 webpack.config.js
中进行配置,具体代码如下:
module.exports = { devServer: { proxy: { '/react/api': { target: 'http://www.mondaylab.com', //实现对https网址的请求转发 secure: false, bypass: function(req, res, proxyOptions){ //如果请求的内容是一个html地址,那么就直接返回根路径下index.html的内容 if(req.headers.accept.indexOf('html') !== -1){ console.log('Skipping proxy for browser request'); return './index.html'; // return false; //表示如果遇到html请求,那么该给你返回什么就返回什么 } }, //在前端请求时写header.json,webpack会间接的帮我们拿到demo.json的数据(请求转发】) pathRewrite: { 'header.json': 'demo.json' }, //如果有一些网站做了防爬虫,那么我们可能没办法进行跨域。需要进行如下配置,就可以突破对origin的限制 changeOrigin: true, // 在请求头中自定义一些内容 headers: { host: 'www.mondaylab.com', //在做请求转发时模拟一些登录等操作 cookie: 'gfhgfh' } } } } } 复制代码
2. WebpackDevServer解决单页面应用路由问题
对于现代的的主流框架来说,像 vue.js
、React.js
等等框架,基本都是单页面应用。那么,在单页面应用里,比如我们想要从 http://mondaylab.com
跳转到 http://mondaylab.com/list
,该怎么样进行跳转呢?
这就要谈到一个单页面应用的路由问题。我们需要在 webpack.config.js
中进行如下配置:
module.exports = { devServer: { //第一种方式 historyApiFallback: true, /*等同于 historyApiFallback: { rewrites: [{ from: /\.*\/, to: '/list.html/' }] } */ /*第二种方式 historyApiFallback: { rewrites: [{ from: /abc.html/, //进行转换,当访问abc.html时,会把list.html的内容展示出来 to: '/list.html/' }] } */ /*第三种方式:更为灵活 historyApiFallback: { rewrites: [{ //表明在做页面的替换时,通过一个函数function的形式,结合context的一些参数 //做一些js的逻辑放在里面,来决定最终它跳转到哪 from: /^\/(libs)\/.*$/, to: function(context){ return '/bower_components' + context.match[0]; } }] } */ } } 复制代码
值得注意的是, historyApiFallback
只能用于开发环境下。如果到了线上环境的话,需要让后端的小伙伴去 nginx
或者 apache
上,仿照 webpackDevServer
的一些配置,在后端的服务器上做同样的配置,配置之后前端才可以使用对应的路由。
🚖五、ESLint在Webpack中的配置
1. ESLint是什么
在我们日常的团队开发中,每个人写的代码都各式各样,比如有的人喜欢在代码后边加个分号,有的人又不喜欢加。这间接地,就很容易导致我们项目的可维护性变差了。因此呢,我们就引入了 ESLint
这个内容,来约束的代码规范,让项目的可维护性和可扩展性变高。
那 ESLint
在 Webpack
中是怎么样配置的呢?
2. 如何安装ESLint
第一步: 安装 ESLint
工具。具体命令如下:
npm install eslint --save-dev 复制代码
第二步: 约束我们的代码。我们需要新建一个配置文件,来对我们的ESLint规范进行配置。具体命令如下:
npx eslint --init > Use a popular style guide 使用通用的代码检测模板 > Airbnb > Do you use React 根据自身需求填y或者n > Javascript > Would you like to install them now with npm? Y 复制代码
第三步: 使用 eslint
检测代码规范。具体代码如下:
npx eslint src 复制代码
以上代码表示的是,使用eslint来检测src目录下的代码规范。
3. 为什么要在webpack中配置ESLint
在上面中我们了解到,每个开发的小伙伴都可以用命令行来检测自己的代码规范,但是如果每回写完一次代码,我们都要去运行这样的命令,才能看我们的代码写的合不合理,这样会不会就有点麻烦了。同时,我们又也不能保证每个人的 eslint
的代码规范设置是不是一样的。
因此,我们可以在 webpack
中来进行配置,解决上述所说的问题。具体步骤如下:
第一步: 安装 eslint-loader
。具体命令行如下:
npm install eslint-loader --save-dev 复制代码
第二步: 配置 webpack.config.js
。具体代码如下:
module.exports = { devServer: { /*当我们运行webpack做打包时, 一旦代码出现规范问题, webpack将会在浏览器上弹出一个报错层来提示我们*/ overlay: true }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: ['babel-loader', 'eslint-loader'] }] } } 复制代码
了解完基础配置之后,接下来我们来了解一些 eslint-loader
的一些其他配置。具体代码如下:
module.exports = { module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: ['babel-loader', { loader: 'eslint-loader', options: { //如果代码有一些比较浅显的问题,eslint-loader将会帮助我们自动修复 fix: true, //降低eslint在打包过程中对项目性能的损耗 cache: true }, //强制eslint-loader先执行 fore: 'pre' }] }] } } 复制代码
🏎️六、Webpack性能优化
细心的小伙伴可能已经发现,webpack的打包速度有时候可能会有一点点慢。这间接地,会浪费我们很多不该浪费的时间。所以,接下来就来谈谈,提升 Webpack
打包速度的几种方法。
1. 跟上技术的迭代(Node,Npm,Yarn)
如果我们想提升 webpack
的打包速度,我们可以升级 webpack
的版本,或者升级我们的node、npm管理器或者yarn的版本。
那为什么升级这些工具可以提升 webpack
的打包速度呢。
大家想一下, webpack
在做每一个版本的更新时,内部肯定会做很多版本的优化,因此,当我们做 webpack
的版本更新时,在速度上肯定会有所提升。 node
、 npm
、 yarn
的更新也是同样的道理。
试想一下,如果不提升,那它升级的意义又在哪呢?对吧。
2. 在尽可能少的模块上应用Loader
一般情况下,第三方模块的库都是已经进行打包编译过的,所以我们需要在引入 loader
来进行编译时,对 node_module
的文件给忽略掉,或者只在某个文件夹下使用某个 loader
,以此来增加我们的打包速度。我们可以在 webpack.config.js
中进行配置,具体代码如下:
module.exports = { module: { rules: [{ test: /\.js$/, exclude: /node_modules/, //或者以下这种方式->include //include: path.resolve(__dirname, '../src'), use: [{ loader: 'babel-loader' }] }] } } 复制代码
当然,依据上面这个思路,还有其他的 loader
也都有其相对应的注意事项,这里不再展开细述。
3. 合理使用插件
插件要合理的使用,不要使用那些冗余的,没有意义的插件。同时呢,也要选择那些,性能比较好的,官方认可的插件来使用,这样,可以有效的来提升 webpack
的打包速度。
4. resolve参数合理配置
(1)常见配置
有时候,我们想要对我们引入的文件进行一些自定义的配置,该怎么处理呢?具体配置如下:
module.exports = { resolve: { extensions: ['js', 'jsx'], /** * 1.当你只引入一个目录时,比如 import Child from './child/child' , * 这个时候webpack不知道我们具体是要引入哪个文件, * 那么会先去找index文件,如果找不到,那它会继续去找child文件 */ mainFiles: ['index', 'child'], /** * alias, 顾名思义是别名。 * 比如,想要将某个文件的引入方式改为自己的名字 * import Child from './Child' -> import Child from 'monday' */ alias: { monday: path.resolve(__dirname, '../src/Child'), } }, } 复制代码
接下来对以上的几个参数进行详细讲解。
(2)参数讲解
1)extensions
- 比如当引入
import Child from './child/child'
时,会先去找'./child/child.js'
文件,找不到再去找'./child/child.jsx'
文件。 - 一般不配置css和图片文件,因为css和图片可能数量会比较多,相应的会去执行很多次查找。间接地,本来想提升性能,结果又变成了浪费性能了。
- 所以,如果是像
css
和jpg
等之类的资源文件,应该进行显式的引入;如果是像js
和jsx
之类的逻辑文件,可以在extesions
中配置,进行显式的引入。
2)mainFiles
当你只引入一个目录时,比如 import Child from './child/child'
,这个时候 webpack
不知道我们具体是要引入哪个文件,那么会先去找 index
文件,如果找不到,那它会继续去找 child
文件。
3)alias
alias
, 顾名思义是别名。比如,想要将某个文件的引入方式改为自己的名字 →import Child from './Child' -> import Child from 'monday'
。- 常用于多层级目录的情况下。
5. 使用DllPlugin提高打包速度
有时候,我们希望引入的第三方模块,只在第一次做打包的时候进行分析,而后续再做打包时就不要再进行分析了。那该怎么处理呢?
第一步: 新建 webpack.dll.js
文件,并进行配置。具体代码如下:
const path = require('path'); const webpack = require('webpack'); module.exports = { mode: 'production', entry: { //此处填写我们想要单独进行打包的第三方库名 vendors: ['react', 'react-dom', 'lodash'] }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, '../dll'), /*用library把第三方模块里面的所有代码, 通过全局变量的方式暴露出去*/ library: '[name]' }, plugins: [ /*暴露完成之后,借助DllPlugin插件对暴露出来的模块代码进行分析,最终生成manifest.json的文件 */ new webpack.DllPlugin({ //对生成的vendors的库进行DllPlugin的分析(文件映射) name: '[name]', // 分析结果所放的路径,分析之后结合全局变量,在common.js中进行配置 path: path.resolve(__dirname, '../dll/[name].manifest.json') }) ] } 复制代码
第二步: 安装 add-asset-html-webpack-plugin
插件。具体命令如下:
npm install add-asset-html-webpack-plugin --save 复制代码
第三步: 配置 webpack.common.js
文件。具体配置如下:
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: { main: './src/index.js', plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), /** * 指的是要往HtmlWebpackPlugin插件生成的index.html中添加一些内容 */ new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(__dirname, '../dll/vendors.dll.js') }), /** * 1.使用DllReferencePlugin插件, * 这个插件会到'../dll/vendors.manifest.json'中找第三方模块的映射关系, * 如果能找到映射关系,那么webpack就会知道,这个第三方模块没有必要再打包进来了, * 直接从vendors.dll.js中拿过来用就可以了 * 2.如果发现并不再映射关系里面,那么才会再到node_modules中去找 * */ new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, '../dll/vendors.manifest.json') }) ] } } 复制代码
第四步: 配置 package.json
文件。具体代码如下:
{ "scripts": { "build:dll": "webpack --config ./build/webpack.dll.js" } } 复制代码
通过运行 npm run build:dll
命令,来实现对第三方库的打包。
6. 控制包文件大小
在我们做项目打包时,我们应该让我们打包生成的文件尽可能的小。有时我们在写代码的时候,经常会在页面里面引入一些没有用的模块,也就是引入一些我们使用的模块。
这个时候如果你没有配置 Tree-Shaking
,就会很容易造成打包的时候,出现大量冗余的代码。这些冗余代码呢,间接地,就会拖累我们 webpack
的打包速度。
所以呢,我们在做打包时,要控制好文件的大小。具体步骤参考如下:
- 配置
Tree-shaking
; - 通过
SplittingChunk
来将一个大的文件分割成多个小的文件。
7. 多进程打包
webpack
是通过node来运行的,所以它的打包过程是单线程的。那有时候呢,我们也可以借助 node
里面的多进程,来帮助我们提升 webpack
的打包速度。
常见工具有 thread-loader
, parallel-webpack
, happypack
等工具,大家可以依据自身需求查找相关资料,选出最适合自己项目的工具,进行打包。这一块不再进行详细讲解~
8. 合理使用sourceMap
通常情况下, sourceMap
越详细,那么打包的速度就会越慢。所以在做打包的时候,要根据当前是开发环境还是生产环境,选出最合适的 sourceMap
配置,来生成我们对应的代码调错文件。
这样,一方面能保证我们即使发现代码里的错误问题。另一方面呢,也可以尽可能地提升打包速度。
9. 结合 stats 分析打包结果
在打包项目时,我们可以通过命令,来生成这次打包情况的 stats
文件,之后呢,通过借助一些线上或者本地的打包分析工具,来达到分析我们本次打包过程中的打包情况。
比如说,分析哪个模块打包的时间比较长,哪个模块打包分析的时间比较短等等内容,依据具体的情况进行优化。
10. 开发环境内存编译
使用 webpackDevServer
来进行编译,它不会把打包生成的文件放在 dist
目录中去,而是把其放到了我们电脑的内存中。
11. 开发环境无用插件剔除
比如说,我们在开发环境的情况下,并不需要对代码进行压缩。因此,对应的压缩插件我们就不要在开发环境中配置,只在生产环境中配置就好了。这样,可以减少一些不必要的打包时间。
🏍️七、多页面打包配置
通常情况来说,但凡我们在做打包的时候,基本上都是对单页面应用做打包。什么是单页面呢,也就是只有一个 index.html
文件。像目前比较主流的框架, vue
和 react
等框架,都是单页面应用。但是如果像一些比较老的框架,比如 jquery
和 zepto
,可能就需要进行多页面应用打包。
因此,顺着这个话题,我们来谈谈,在 webpack
中,如何对多页面应用,进行打包配置。
我们在 webpack.common.js
中进行配置,具体代码如下:
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { //引入多个入口文件 main: './src/index.js', list: './src/list.js' }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html', filename: 'index.html', //chunk表明这些html文件要引入的文件有哪些 chunks: ['runtime', 'vendors', 'main'] }), new HtmlWebpackPlugin({ template: 'src/index.html', filename: 'list.html', chunks: ['runtime', 'vendors', 'list'] }) ] } 复制代码
通过以上代码我们可以知道,增加 entry
和 plugins
的配置,来增加多个入口页面,从而达到了多页面应用配置的效果。
🛵八、结束语
通过上文的讲解,相信大家对 webpack
在一些场景中的实战配置有了一定的了解。上面讲述的也是比较浅显的内容,大家可以根据相对应的知识点,进行知识面的拓宽,以便更好的应用到实际的项目当中。
到这里,关于 webpack
的实战案例配置就讲解结束啦!希望对大家有帮助~