前言
本文初衷:平时的项目大多用create-react-app
开发,从开发到打包都只是在敲命令而见不到webpack.config.js
配置文件,简直是面向黑箱编程,遇到问题之后即使解决了也不知道怎么回事,这种感觉非常不好。
学习 webpack 的重要性不言而喻,即使市面上已经有如此众多的成熟脚手架,比如普通项目可以用 CRA,SPA 管理系统可以用 antdpro,打包组件库可以用 tsdx 等等,但如果不懂这些打包工具的原理甚至基础用法,总有一天你会遇到奇葩问题而不知道如何解决。
本文将以问题导向的形式,在实际搭建过程中逐个剖析webpack重要配置,深浅适宜,整体内容较基础,适合初入坑 webpack
的小伙伴们参考。
本示例 webpack 版本为5.x
,webpack-cli
版本为4.x
话不多说,马上开始吧~
正文
1.项目初始化
1 | mkdir webapck-ts-react |
空项目中初始化为以下结构:
🤔 问题1:webpack是什么?
👉 展开查看答案
- webpack是一个打包工具;将符合`ES Module`和`CommonJS`模块化规范的工程文件打包成一个静态资源(可部署到服务器)一张图讲清楚webpack的作用
此时直接执行npx webpack
命令试试看吧:
1 | webpack |
神奇的事情发生了,会发现多出了个文件夹dist
,里面是打包编译好的文件main.js
,如果我们没有在webpack.config.js
中配置任何内容,则默认按照相应出入口进行打包,默认命令类似:
1 | // webpack.config.js |
再次命令行执行npx webpack
结果一样,验证默认配置就是上面这样的~
配置项中entry
为入口,可以配置为相对路径;output
为出口,path
属性必须设置为绝对路径;
为什么输出路径要求绝对路径?
以上差异原因在于项目中入口一般可以确定为本项目中,但是出口理论上可以是磁盘上任意值,所以output的path必须为绝对路径。
output
的filename
在单入口项目中可以写任意固定值,在多入口项目中不能写固定值,[name]
为变量占位符表示不固定的值;
🤔 问题2:为什么要npx webpack而不是直接webpack?
👉 展开查看答案
webpack打包命令默认有两种方式:全局和本地(局部); 如果直接执行webpack则用的是全局webpack编译,结果一样的嗷; 如果使用npx webpack则会在当前项目中寻找webpack指令执行,查找路径为/node_modules/bin/webpack
🤔 问题3:全局有webpack命令不就够了吗?为啥本地还要安装webpack?
👉 展开查看答案
全局安装的都是固定版本(比如最新的5.x),有些年代久远的项目需要需要使用更早期的webpack版本(比如4.x),为了防止版本冲突,所以开发中一般都是用项目本地版本不过每次都要npx webpack
未免太麻烦了,所以我们可以在package.json
中做如下配置:
1 | // package.json |
之后直接执行yarn build
就和执行npx webpack
效果一样啦~
2.处理图片loader
接着我们发挥下webpack模块化打包的特性,新建一个模块专门在页面上加载图片:
1 | // src/loadImg.js |
index.js中引入
1 | require('./loadImg') |
执行yarn build
发现报错:
提示得很清楚啦,由于webpack
默认只认识.js
和.json
文件,对于图片文件的识别是需要借助loader的;
🤔 问题4:loader是什么?
👉 展开查看答案
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。在webpack4.x版本中处理图片需要用到file-loader,url-loader或raw-loader,但是在webpack5.x中不需要了,对于图片和字体文件等,可以通过type: asset声明直接处理文件。
这里我们采用5.x的方式处理图片:
1 | const path = require('path') |
打包成功:
新建HTML文件,引入打包后的main.js
文件测试,注意script标签一定要加defer
属性:
1 | dist/index.html |
🤔 机智如你已经发现了,直接在dist
文件夹中新建额外文件的操作不对劲吧,别急,后面会有plugin帮我们自动处理的。
打开dist/index.html
预览,一切正常:
3.处理css文件loader
让我们新建一个css文件
1 | // src/css/index.css |
引入
1 | src/index.js |
不出所料,还是同样内容的报错:缺少合适的loader,因为上面我们已经知道了,webpack默认只能识别js文件和JSON文件,其他格式文件都需要loader帮助识别处理。
安装处理css的loader
1 | yarn add style-loader css-loader -D |
配置文件中指定.css文件的解析所用loader
1 | ... |
再次yarn build
,无报错而且样式生效
🤔 问题5:css-loader我猜是解析css的,那么style-loader是干啥的?
👉 展开查看答案
css-loader仅能识别并打包css文件,而style-loader将打包出来的css样式插入到HTML的head中,使其在页面上生效4.打包模式mode
接下来解决打包模式警告问题:
只需要在webpack.config.js
中指定mode
配置项即可
mode
参数有两种:development
和production
,默认为production
,这两种模式各有一套默认配置:
Mode: development
1 | // webpack.development.config.js |
Mode: production
1 | // webpack.production.config.js |
5.借助babel打包react项目
当前的webpack配置已经能够打包js和css以及图片文件了,接下来我们让它支持react项目的打包;
众所周知,打包react项目的核心工作就是转化其jsx语法,这就不得不提到babel
了。
🤔 问题6:什么是babel?
👉 展开查看答案
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。除此之外还能为你做的事情有:语法转换
通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
源码转换 (codemods)
babel的使用方法:
一个核心包@babel/core
必须安装的,其余功能可以通过配置插件plugins或预设presets实现,这里我们要转化jsx语法,可以直接使用@babel/preset-react
这个预设(预设就是一堆插件的合集方案),考虑到在webpack中使用babel,所以还要用到babel-loader
1 | yanr add @babel/core @babel/preset-react babel-loader -D |
当然react和react-dom也需要安装到生产依赖中
1 | yarn add react react-dom |
新建index.jsx
文件写入react代码
1 | // src/index.jsx |
配置文件中更改打包入口并增加jsx解析规则:
1 | // webpack.config.js |
执行yarn build
打包;
更改dist/index.html
文件新增id为root的节点
1 | ... |
可以发现编译成功:
6.配置plugin
(1)html-webpack-plugin
之前的操作中我们多次手动修改dist文件夹下的内容,这种操作肯定是不被允许的,所以我们需要配置模板,借助html-webpack-plugin
自动生成这个测试用的HTML文件
1 | yarn add html-webpack-plugin -D |
src目录先新建index.html
模板文件
1 | // src/index.html |
修改配置项,增加插件
1 | // webpack.config.js |
此时yarn build
打包,发现dist文件夹下已经自动生成了模板文件,并且自动引入了main.js
打包文件
(2)clean-webpack-plugin
见名知意,这个插件作用很简单,就是在每次打包生成新的打包文件之前自动删除所有老的打包文件
1 | yarn add clean-webpak-plugin -D |
1 | // webapck.config.js |
7.支持TS版React项目编译
如何让webpack支持ts呢,其实这个问题和如何支持jsx语法
一样性质,对于代码转化工作都是要loader去做。
以下提供两种方案用来支持React组件的TS写法,无论哪种都要先在本地安装typescript
1 | yarn add typescript -D |
生成tsconfig.json
配置文件
1 | yarn tsc --init |
方案一:babel-loader的@babel/preset-typescript
一种方法就是沿用babel-loader
,通过增加预设preset
来支持ts解析:
1 | yarn add @babel/preset-typescript -D |
src/index.jsx
改名为src/index.tsx
,同时打包配置文件中的entry也要改为entry: './src/index.tsx'
为babel-loader
的presets数组增加预设:@babel/preset-typescript
1 | // webpack.config.js |
执行yarn build
可以成功打包;
但是这种方案下,很多typescript语法是不被支持的,比如我们新建一个Comp组件故意写出错误的类型定义:
1 | const Comp = () => { |
执行yarn build
可以看到:
说明这种方案虽然能够打包TS,但是无法在打包过程中对TS错误语法进行校验,如果既想打包又想校验怎么办呢?这是就要用到另一个loader了:
方案二:ts-loader
1 | yarn add ts-loader -D |
配置文件中移除@babel/preset-typescript
预设并增加ts-loader
后执行打包:
可以看到一下子出了16个error,可见ts-loader
是能在打包过程中对不符合规则的ts语法做校验的。
解决报错的过程分别为
tsconfig.json配置”jsx”: “react”
yarn add @types/react @types/react-dom
解决具体语法报错
8.优化开发体验webpack-dev-server
目前每次重新打包之后都要手动查看HTML文件变更,太不“自动化”了,其实webpack允许我们开启一个本地服务监听打包过程自动更新页面,而且还能热更新。
1 | yarn add webpack-dev-server -D |
开启打包服务,在4.x版本中需要修改命令为:webpack-dev-server
;
而在5.x版本中只要:webpack serve
1 | // package.json |
此时执行yarn dev
即可观察到已经开启打包监听,devSer的具体配置项如下:
1 | // webpack.config.js |
那么至此,一个ts版的react开发环境就搭建好了,剩下一些自定义配置完全根据各自公司项目需要了,比如我们项目习惯用sass module模式开发。
9.支持sass module开发模式
安装sass-loader和node-sass
1 | yarn add sass-loader node-sass -D |
1 | // src/comp.module.scss |
1 | // src/Comp.tsx |
配置文件中增加一个解析规则:
为了配合一下TS,还要新建个类型声明文件
1 | // typed-css.d.ts |
10.实现react模块热替换(HMR)
1 | yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh -D |
1 | // webpack.config.js |
11.配置路径别名
一定要照着下面的配
1 | // webpack.config.js |
1 | // tsconfig.json |
结语
至此,一款工作中能用的TS版React开发环境已经搭建完毕~
现已具备功能:
- typescript语法
- sass module
- 模块热替换
- 路径别名
- 解析图片和CSS
- source-map
后期可支持项: - 第三方包优化,treeshaking,cdn等
- 生产环境配置文件分离
- 生产环境包体积和chunkname优化
文中项目源码:webpack-ts-react-lead