webpack5之Babel/ESlint/浏览器兼容
webpack系列目录
Babel用法
在实际开发中我们很少直接去接触babel,但是babel对于前端开发来说又是必不可少的。Babel到底是什么呢,它其实是一个工具链,跟postcss一样,它能够将ECMAScript后版本语法代码转为ES5代码,包括:语法转换、源代码转换、Polyfill实现功能。
babel核心安装
我们来安装babel核心库@babel/core
,如果我们想要在命令行使用需要安装@babel/cli
,安装npm install @babel/core @babel/cli
。我们如果想使用babel的功能,就需要安装bable的插件,我们可以使用babel的预设插件@babel/preset-env
,安装npm install @babel/preset-env -D
。
编写一个main.js文件
执行命令
npx babel src --out-dir dist --presets=@babel/preset-env
生成结果
可以看到输出文件给我们转换成了ES5的语法。
babel-loader
在实际开发中我们会使用babel-loader
来配置我们的babel,安装npm install babel-loader -D
。配置如下
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', {
targets: 'last 2 version' // 配置的targets属性会覆盖browserslist,实际推荐在browserslist中配置
}]
}
}
]
}
babel配置文件
除了在loader中配置babel预设插件,一般情况会独立抽取一个babel配置文件,官方为我们提供了两种编写方法
- babel.config.json(或者.js,.cjs,.mjs)文件
- .babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件
实际推荐使用babel.config.json/js ,我们新建一个babel.config.json
// babel.config.json
{
"presets": [
["@babel/preset-env"]
]
}
Polyfill
现在我们修改一下main.js,我们使用一个promise Api,并重新打包
// main.js
const a= '123'
const fn = Promise.resolve(3)
fn.then(res => {
console.log(res)
})
现在可以看到打包结果中并没有帮我们将Promise进行转换,那么这就有可能在一些低版本或者旧的浏览器上不支持Promise。因此我们提出了一个Polyfill的东西,我们使用了一些语法新特性(例如:Promise, Generator, Symbol等以及实例方法Array.prototype.includes等),它将会将给我们进行填充,就是将这些Api进行转换,用旧版本浏览器认识的语法api去实现这些新特性。那么我们该如何使用呢。
在之前我们是通过安装@babel/polyfill
这个库来实现的,我们会之间在入口文件头顶引入。但是现在我们已经不再使用了,在babel7.4.0之后,是引入core-js
和regenerator-runtime
这两个依赖来提供polyfill功能。安装npm install core-js regenerator-runtime
。并且需要在 @babel/preset-env 后添加额外属性
useBuiltIns
: 设置以什么样的方式来使用polyfill,它有三个值false,usage,entryfalse
: 不使用任何polyfill有关的代码usage
: 代码中需要哪些polyfill,就会自动引用相关的API(推荐)entry
: 代码中需要哪些polyfill,需要在入口加入 import "core-js/stable"; import "regenerator-runtime/runtime"; 体积会变大
corejs
: 设置corejs的版本,目前使用较多的是3.x的版本- 另外corejs可以设置是否对提议阶段的特性进行支持
- 设置proposals属性为true即可
babel.config.json配置如下所示
// babel.config.json
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
]
}
Plugin-transform-runtime
除了上述的polyfill作用的是全局的,所以当我们在编写工具库的时候,如果我们使用上述babel polyfill功能,那就可能出现污染全局代码,因此官方推荐了使用@babel/plugin-transform-runtime
这个插件来实现polyfill。安装npm install @babel/plugin-transform-runtime -D
。配置如下
// babel.config.json
{
"presets": [
["@babel/preset-env"]
],
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs": 3
}]
]
}
注意因为目前使用了corejs3,所以我们需要安装对应的库,安装npm install --save @babel/runtime-corejs3
。
Jsx支持
当我们在写React时,我们会使用jsx语法,babel官方提供了jsx的预设@babel/preset-react
,安装
npm install @babel/preset-react -D
,配置如下所示
// babel.config.json
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}],
["@babel/preset-react"],
]
}
Typescript支持
我们在项目中也会使用TypeScript来开发,而TypeScript代码是需要转换成JavaScript代码,要知道原来我们会使用ts-loader来处理ts文件。但是babel也为我们提供了预设插件 @babel/preset-typescript。那么这两种有什么区别呢,我们分吧来使用一下。
ts-loader
安装npm install ts-loader -D
,配置如下
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: [
'ts-loader'
]
}
我们先执行tsc --init,因为当我在下载ts-loader同时也会默认帮助我们安装了typescrit,现在初始化tsconfig.json文件,并且修改main.js为main.ts
// main.ts
const a: string = '123'
const fn = Promise.resolve(3)
fn.then(res => {
console.log(res)
})
重新打包后,我们发现ts-loader并没有帮助我们实现polyfill
@babel/preset-typescript
现在我们来使用一下@babel/preset-typescript
,安装npm install @babel/preset-typescript -D
,配置如下
// webpack
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: [
'babel-loader'
]
}
// babel.config.json
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}],
["@babel/preset-react"],
["@babel/preset-typescript"]
]
}
现在在重新打包试试
现在我们可以看到已经帮我们提供了polyfill
ts-loader和@babel/preset-typescript的区别
那么我们在开发中应该选择ts-loader还是babel-loader呢,我们区分一下两者
- ts-loader(TypeScript Compiler)
- 来直接编译TypeScript,那么只能将ts转换成js
- 如果我们还希望在这个过程中添加对应的polyfill,那么ts-loader是无能为力的
- 我们需要借助于babel来完成polyfill的填充功能
- babel-loader(Babel)
- 来直接编译TypeScript,也可以将ts转换成js,并且可以实现polyfill的功能
- 但是babel-loader在编译的过程中,不会对类型错误进行检测
那么如何做到既能实现polyfill又能提供类型错误检测呢,首先我们肯定希望使用babel的预设的,我们可以在package.json的script中添加一条命令
"script" {
"ts-check": "tsc --noEmit"
}
当我们在编译时执行一下该命令,但是个人推荐使用一个ForkTsCheckerWebpackPlugin
的插件,它能在开发和构建环境提供类型错误检测,安装npm install fork-ts-checker-webpack-plugin -D
。我们可以如下配置
// webpack
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
//...
{
plugins: [
//...
new ForkTsCheckerWebpackPlugin({
async: false,
}),
]
}
所以最终在编译ts文件我们采取的方案是@babel/preset-typescript
和ForkTsCheckerWebpackPlugin
这两插件的结合。
Babel编译的原理
我么通过一张图来查看Babel编译的整个过程
Babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码,工作流程总结:
- 解析阶段(Parsing)
- 转换阶段(Transformation)
- 生成阶段(Code Generation)
ESlint配置
ESLint是一个静态代码分析工具,它能帮助我们在项目中建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性。并且ESLint的规则是可配置的,我们可以自定义属于自己的规则。安装ESlint,npm install eslint -D
现在我们来配置一下我们的ESLint规则,我们先初始化ESlint
npx eslint --init
生成 .eslintrc.js文件
配置文件的主要属性介绍一下
env
: 运行的环境,比如是浏览器,并且我们会使用es2021(对应的ecmaVersion是12)的语法extends
: 可以扩展当前的配置,让其继承自其他的配置信息,可以跟字符串或者数组(多个)parserOptions
: 这里可以指定ESMAScript的版本、sourceType的类型parser
: 默认情况下是espree(也是一个JS Parser,用于ESLint),如果我们我们需要编译其他比如TypeScript,还需要指定对应的解释器plugins
: 指定我们用到的插件rules
: 自定义的一些规则
对应的规则我们可以从ESlint中文官网查找
现在我们用命令行工具测试一下,修改main.js文件
const a= '123'
const fn = () => {
return a
}
console.log(fn(a))
执行下eslint命令
npx eslint ./src/main.js
可以看到命令提示我们字符串需要使用单引号,并且提示我们可以使用--fix
来修复,我们除了通过命令来检测代码规范也可以通过vscode插件来帮助我们检测
VSCode ESlint
我们在插件商场里搜索ESlint,具体可见下方图片
它会根据当前目录的 .eslintrc.js 配置文件来做检查,如果没有该配置文件它有默认的配置规范, 现在再来看我们的main.js文件,编辑器已经帮助我们将不规范的地方划上了波浪形
ESlint Loader
那我们除了用这些方式外,能否在编译代码的时候,也希望进行代码的eslint检测,这个时候我们就可以使用eslint-loader来完成。安装npm install eslint-loader -D
,配置如下
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader'
]
}
自动修复保存
ESLint会帮助我们提示错误(或者警告),但是不会帮助我们自动修复,需要手动添加--fix
参数
eslint src --fix
在开发中我们希望文件在保存时,可以自动修复这些问题,我们可以选择使用另外一个工具: prettier,我们在VSCode插件商场里搜索prettier
但是在实际项目中并不太喜欢使用该插件,这个习惯因人而异吧。
碰到的问题
在使用eslint-loader碰到一个问题,我在编译时会报TypeError: Cannot read property 'getFormatter' of undefined这个错误,我当时安装的版本 "eslint": "^8.11.0","eslint-loader": "^4.0.2",
经过debugger我发现在eslint-loader的getOptions.js引用了eslint的CLIEngine
但是这个CLIEngine导出的是undefined,因此我去查看eslint的库默认引用的./lib/api.js,发现并没有导出CLIEngine。
看了一下eslint源码发现CLIEngine这玩意是来自eslint/lib/cli-engine/index.js导出的,那我们只需要将eslint-loader的options里给传入一个eslint/lib/cli-engine/index.js,重新打包后还是报错
它说我们不能在eslint中通过这个路径导出模块,我看了下eslint的package.json,发现它作了导出限制
因此我们可以这样配置
// webpack
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: 'eslint-loader',
options: {
eslintPath: 'eslint/cli-engine'
}
}
]
}
在eslint的package.json中的exports里加上"./cli-engine": "./lib/cli-engine/index.js"
现在再来重新打包
可以看到已经正常了,但是因为我们改的地方包含了node_module里的eslint文件,这始终不是个好办法,最终想到是我们将原来eslint删除,降低eslint版本至7.32.0。安装完后重新打包现在就都ok了。
浏览器兼容性
Browserslist
这里指的兼容性是针对不同的浏览器支持的特性: 比如css特性、js语法之间的兼容性,而我们市面上使用的有 Chrome、Safari、IE、Edge、Chrome for Android、UC Browser、QQ Browser 等,其实我们一般可以在一些脚手架中的package.json里面看到这样的信息
> 1%
last 2 versions
not dead
这里的 > 1%
意思是大于市面上占有率的浏览器,这里的浏览器不同版本以及市场占有率可以从 caniuse.com/usage-table…查看,如下图
而我们需要兼容这些大于1%这个条件的浏览器的兼容性,这时候就需要使用 Browserslist
工具,这是一个共享当前条件对应的目标浏览器的配置,当我们使用 Browserslist
工具配置好需要兼容的条件之后,我们将会使用以下的兼容工具帮助我们兼容css和js。
- Autoprefixer
- Babel
- postcss-preset-env
- eslint-plugin-compat
- stylelint-no-unsupported-browser-features
- postcss-normalize
- obsolete-webpack-plugin
这些工具就会依赖当前 Browserlist 配置的条件去做对应的兼容处理,而 Browserlist 在安装 webpack 时会自动安装,我们可以通过命令 browserslist
命令查看目标浏览器列表。
npx browserslist ">1%, last 2 version, not dead"
注意:
browserslist
命令后面不跟条件的话先会找.browserslistrc配置文件,否则会使用默认条件。
这些列出的就是符合我们条件并且需要兼容的目标浏览器。
配置browserslist有两种方式:
- package.json 配置
- 独立
.browserslistrc
文件配置
package.json 配置
{
//...
"browserslist": [
"> 1 %",
"last 2 version",
"not dead"
]
}
.browserlistrc文件配置,在工程目录下新建 .browserslistrc 文件
注意:当条件之间使用逗号或者空格或者or的时候,条件之间为并集。当条件之间使用and为交集。当条件之间使用not为不包含。
具体 browserslist 配置见 github地址
Postcss
接下来我们确认好需要兼容的目标浏览器后就需要使用工具去做兼容处理了,首先需要先介绍一下 postcss
。
PostCSS是一个通过JavaScript来转换样式的工具,这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置,但是实现这些工具,我们需要借助于PostCSS对应的插件,而这些插件就是上面所述的兼容工具。
我们可以使用命令行来测试下postcss工具下的autoprefixer插件,需要安装 postcss
、postcss-cli
,执行 npm intall postcss postcss-cli -D
安装。
使用终端命令需要借助 postcss-cli 依赖包,而转换又需要使用 postcss。
之后我们需要再下载用于做兼容处理的插件 autoprefixer,postcss需要指定某个插件来做处理,npm intall autoprefixer -D
。安装完后我们就可以使用终端命令行来测试下。
npx postcss --use autoprefixer -o result.css ./src/css/index.css
执行命令后
可以看到输出的result.css文件已经给我们的样式属性自动添加了兼容代码。但是在实际项目中我们不可能使用命令来做转换处理,因此我们需要一个loader来处理样式文件,webpack就使用 postcss-loader
来处理。安装 npm install postcss-loader -D
。
在webpack.config.js中添加postcss-loader。
{
{
test: /.css$/i,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'autoprefixer' // 需要指定使用的兼容插件,会转成require('autoprefixer')
]
}
}
}
]
}
}
但是我们除了在css文件中使用到postcss-loader,还会在其他比如预处理器less文件中使用到。这使用我们重复写postcss-loader就会变的很繁琐。此时我们便可以提取出postcss.config.js 文件,将配置都放在该文件内,而webpack配置中就变成
{
rules: [
{
test: /.css$/i,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /.less$/i,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
}
然后独立出postcss.config.js 文件
目前postcss的配置已经差不多了还有一点,目前autoprefixer已经不在使用,我们推荐更多的是使用 postcss-preset-env,该插件包含了autoprefixer的功能,并且能将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境添加所需的polyfill。安装 npm install postcss-preset-env -D
。修改postcss.config.js文件
module.exports = {
plugins: [
'postcss-preset-env'
]
}
比如样式属性 color: #28f2d334
,值设置成8位后,有些浏览器会识别,有些不会。这时 postcss-preset-env 会帮助我们兼容处理
重新打包构建后可以看到 color属性值已经被转换成rgba格式了,并且对之前的属性做的前缀兼容处理。
当然最后我们需要补充一个点的是,当我们在css文件中@import其他css文件时候,postcss-loader和css-loader在处理完当前的css文件后并不会对@import中的css文件再执行一次postcss-loader,所以我们需要在css-loader中添加配置
{
rules: [
{
test: /.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1 // css中引入css不会从postcss-loader开始而是从css-loader转换开始,因此该配置保证loader向上一层开始转换
}
},
'postcss-loader'
]
},
{
test: /.less$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2 // css中引入css不会从postcss-loader开始而是从css-loader转换开始,因此该配置保证loader向上一层开始转换
}
},
'postcss-loader',
'less-loader'
]
}
]
}
转载自:https://juejin.cn/post/7079987397894602788