
五、webpack-Babel深入解析
一、Babel 是什么
Babel 是一个 JavaScript 编译器
- 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
- babel 如果本身没有任何插件,则基本就是源代码不发生变化,要想进行相应的转化,就必须有相应的插件带动它。
- babel 可以帮助我们做什么:
- 语法转换
- 源代码转换
- Polyfill 实现目标缓解缺少的功能等;
二、为什么需要 babel
事实上,在开发中很少直接去接触 babel,但是 babel 对于前端开发来说,目前是不可缺少的一部分:
- 开发中,我们想要使用 ES6+的语法,想要使用 TypeScript,开发 React 项目,它们都是离不开 Babel
- 学习 Babel 对于我们理解代码从编写到线上的转变过程直观重要
- 了解真相,才能获得真知的自由
三、Babel 的命令行使用
babel 本身可以作为一个独立的工具(和 postcss 一样),不用和 webpack 等构建工具配置,也可以单独使用
如果我们希望在命令行尝试使用 babel,需要安装如下库:
- @babel/core:babel 的核心代码,必须安装;
- @babel/cli:可以让我们在命令行使用 babel
npm install @babel/cli @babel/core
使用 babel 来处理我们的源代码:
- src:是源文件的目录
- --out-dir:指定要输出的文件夹 dist
npx babel src --out-dir dist
源代码文件:
1 | const message = "Hello Babel"; |
npx babel src --out-dir dist
后:
1 | const message = "Hello Babel"; |
可见,几乎没有变化,因为 babel/core 只是核心库,需要借助插件来进行编译转换。
四、插件的使用
比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
1 | npm install @babel/plugin-transform-arrow-functions -D |
1 | const message = "Hello Babel"; |
可见箭头函数已经转换为普通函数
但是会发现 const 并没有转成 var
这是因为 plugin-transform-arrow-functions 并没有提供这样的功能
我们需要使用 plugin-transform-block-scoping 来完成这样的功能;
1 | npm install @babel/plugin-transform-block-scoping -D |
1 | var message = "Hello Babel"; |
可以,都已经转为 ES5 的写法。
五、Babel 的预设 preset
如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset)
后面我们再具体来讲预设代表的含义
- 安装@babel/preset-env 预设
npm install @babel/preset-env -D
- 执行如下命令:
npx babel src --out-dir dist --presets=@babel/preset-env
1 | ; |
六、Babel 的底层原理
babel 是如何做到将我们的一段代码(ES6、TypeScript、React)转成另外一段代码(ES5)的呢?
从一种源代码(原生语言)转换成另一种源代码(目标语言),这是什么的工作呢?
- 就是编译器,事实上我们可以将 babel 看成就是一个编译器
- Babel 编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码
- Babel 也拥有编译器的工作流程:
- 解析阶段(Parsing)
- 转换阶段(Transformation)
- 生成阶段(Code Generation)
编译器执行原理
七、babel-loader
实际开发中,我们通常会在构建工具中通过配置 babel 来对其进行使用的,比如在 webpack 中。
那么我们就需要去安装相关的依赖:
如果之前已经安装了@babel/core,那么这里不需要再次安装
npm install babel-loader @babel/core
我们可以设置一个规则,在加载 js 文件时,使用我们的 babel:
1 | module: { |
npm run build 后:
1 | /******/ (function () { |
ES6 语法并没有转换,因为我们没用使用插件
指定使用的插件
1 | module: { |
npm run build 后:
1 | /******/ (function () { |
babel-preset
如果我们一个个去安装使用插件,那么需要手动来管理大量的 babel 插件,我们可以直接给 webpack 提供一个 preset
webpack 会根据我们的预设来加载对应的插件列表,并且将其传递给 babel。
比如常见的预设有三个:
- env
- react
- TypeScript
安装 preset-env:
npm install @babel/preset-env
1 | /******/ (function () { |
设置目标浏览器 browserslist
我们最终打包的 JavaScript 代码,是需要跑在目标浏览器上的,那么如何告知 babel 我们的目标浏览器呢?
- browserslist 工具
- ptarget 属性
说明一点:babel 是转换源代码且运行在浏览器上,那么就要知道浏览器支持的语法来进行源代码的转换
之前项目中已经使用了 browserslist 工具,可以对比一下不同的配置,打包的区别:
chrome 支持 ES6 语法,所以 babel 就不会进行转换了。
设置目标浏览器 targets
1 | module: { |
那么,如果两个同时配置了,哪一个会生效呢?
- 配置的 targets 属性会覆盖 browserslist
- 但是在开发中,更推荐通过 browserslist 来配置,因为类似于 postcss 工具,也会使用 browserslist,进行统一浏览器的适配
Stage-X 的 preset
要了解 Stage-X,我们需要先了解一下 TC39 的组织:
- TC39 是指技术委员会(Technical Committee)第 39 号
- 它是 ECMA 的一部分,ECMA 是 “ECMAScript” 规范下的 JavaScript 语言标准化的机构
- ECMAScript 规范定义了 JavaScript 如何一步一步的进化、发展
- TC39 遵循的原则是:分阶段加入不同的语言特性,新流程涉及四个不同的 Stage
- Stage 0:strawman(稻草人),任何尚未提交作为正式提案的讨论、想法变更或者补充都被认为是第 0 阶段的” 稻草人”
- Stage 1:proposal(提议),提案已经被正式化,并期望解决此问题,还需要观察与其他提案的相互影响
- Stage 2:draft(草稿),Stage 2 的提案应提供规范初稿、草稿。此时,语言的实现者开始观察 runtime 的具体 实现是否合理
- Stage 3:candidate(候补),Stage 3 提案是建议的候选提案。在这个高级阶段,规范的编辑人员和评审人员必须在最终规范上签字 Stage 3 的提案不会有太大的改变,在对外发布之前只是修正一些问题
- Stage 4:finished(完成),进入 Stage 4 的提案将包含在 ECMAScript 的下一个修订版中
Babel 的 Stage-X 设置
在 babel7 之前(比如 babel6 中),我们会经常看到这种设置方式:
1 | module.exports = { |
它表达的含义是使用对应的 babel-preset-stage-x 预设
但是从 babel7 开始,已经不建议使用了,建议使用 preset-env 来设置;
Babel 的配置文件
像之前一样,我们可以将 babel 的配置信息放到一个独立的文件中,babel 给我们提供了两种配置文件的编写:
- babel.config.json(或者.js,.cjs,.mjs)文件
- .babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件
它们两个有什么区别呢?
- 目前很多的项目都采用了多包管理的方式(babel 本身、element-plus、umi 等)
- .babelrc.json:早期使用较多的配置方式,但是对于配置 Monorepos 项目是比较麻烦的
- babel.config.json(babel7):可以直接作用于 Monorepos 项目的子包,更加推荐
babel.config.js:
1 | module.exports = { |
八、polyfill
Polyfill 是什么
- 翻译:一种用于衣物、床具等的聚酯填充材料, 使这些物品更加温暖舒适
- 理解:更像是应该填充物(垫片),一个补丁,可以帮助我们更好的使用 JavaScript
为什么时候会用到 polyfill
- 比如我们使用了一些语法特性(例如:Promise, Generator, Symbol 等以及实例方法例如 Array.prototype.includes 等)
- 但是某些浏览器压根不认识这些特性,必然会报错
- 我们可以使用 polyfill 来填充或者说打一个补丁,那么就会包含该特性了
使用 polyfill
babel7.4.0 之前,可以使用 @babel/polyfill 的包,但是该包现在已经不推荐使用了
babel7.4.0 之后,可以通过单独引入 core-js 和 regenerator-runtime 来完成 polyfill 的使用
npm install core-js regenerator-runtime --save
全局引入 polyfill 和第三方包自己集成的可能会有冲突,所以 babel 打包时需要忽略第三方包
1 | { |
配置 babel.config.js
需要在 babel.config.js 文件中进行配置,给 preset-env 配置一些属性:
- useBuiltIns:设置以什么样的方式来使用 polyfill
- corejs:设置 corejs 的版本,目前使用较多的是 3.x 的版本
- 另外 corejs 可以设置是否对提议阶段的特性进行支持,设置 proposals 属性为 true 即可
useBuiltIns 属性设置
第一个值:false
- 打包后的文件不使用 polyfill 来进行适配
- 并且这个时候是不需要设置 corejs 属性的
第二个值:usage
- 会根据源代码中出现的语言特性,自动检测所需要的 polyfill;
- 这样可以确保最终包里的 polyfill 数量的最小化,打包的包相对会小一些
- 可以设置 corejs 属性来确定使用的 corejs 的版本
第三个值:entry
- 如果我们依赖的某一个库本身使用了某些 polyfill 的特性,但是因为我们使用的是 usage,所以之后用户浏览器可能会报错
- 所以,如果你担心出现这种情况,可以使用 entry,并且需要在入口文件中添加
import 'core-js/stable'
import 'regenerator-runtime/runtime'
- 这样做会根据 browserslist 目标导入所有的 polyfill,但是对应的包也会变大
babel.config.js
1 | module.exports = { |
入口文件 main.js
1 | import "core-js/stable"; |
打包后的文件有 15000 多行,就不展示了。
Plugin-transform-runtime
在前面我们使用的 polyfill,默认情况是添加的所有特性都是全局的
如果我们正在编写一个工具库,这个工具库需要使用 polyfill
别人在使用我们工具时,工具库通过 polyfill 添加的特性,可能会污染它们的代码
所以,当编写工具时,babel 更推荐我们使用一个插件:
@babel/plugin-transform-runtime
来完成 polyfill 的功能
npm install @babel/plugin-transform-runtime -D
使用 plugins 来配置 babel.config.js:
1 | module.exports = { |
注意:因为使用了 corejs3,所以我们需要安装对应的库:
npm i @babel/runtime-corejs3
九、React 的 jsx 支持
在编写 react 代码时,react 使用的语法是 jsx,jsx 是可以直接使用 babel 来转换的
对 react jsx 代码进行处理需要如下的插件:
- @babel/plugin-syntax-jsx
- @babel/plugin-transform-react-jsx
- @babel/plugin-transform-react-display-name
但是开发中,我们并不需要一个个去安装这些插件,我们依然可以使用 preset 来配置:
npm install @babel/preset-react -D
1 | module.exports = { |
十、TypeScript 的编译
我们会使用 TypeScript 来开发,那么 TypeScript 代码是需要转换成 JavaScript 代码。
可以通过 TypeScript 的 compiler 来转换成 JavaScript:
npm install typescript -g
之后我们可以运行 npx tsc 来编译自己的 ts 代码:
tsc index.ts
使用 ts-loader
如果希望在 webpack 中使用 TypeScript,那么我们可以使用 ts-loader 来处理 ts 文件:
npm install ts-loader -D
配置 ts-loader:
1 | { |
之后,我们通过 npm run build 打包,会报错,因为需要一个 tsconfig.json 文件
tsc –init 生成一个 tsconfig.js 文件
但是 ts-loader 会依赖本地的 typescript,所以本地也需要安装下
npm install typescript -D
npm run build
bundle.js
1 | /******/ (function () { |
使用 babel-loader
除了可以使用 TypeScript Compiler 来编译 TypeScript 之外(ts-loader 也是基于 tsc 的),我们也可以使用 Babel
- Babel 是有对 TypeScript 进行支持
- 我们可以使用插件:
- @babel/tranform-typescript
- 但是更推荐直接使用 preset:
- @babel/preset-typescript
- 来安装@babel/preset-typescript:
npm install @babel/preset-typescript -D
1 | { |
1 | presets: [ |
ts-loader 和 babel-loader 选择
那么我们在开发中应该选择 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 在编译的过程中,不会对类型错误进行检测
- 那么在开发中,我们如何可以同时保证两个情况都没有问题呢?
事实上 TypeScript 官方文档有对其进行说明:
- 也就是说我们使用 Babel 来完成代码的转换,使用 tsc 来进行类型的检查。
- 但是,如何可以使用 tsc 来进行类型的检查呢?
- 在这里,在 scripts 中添加了两个脚本,用于类型检查
1 | "scripts": { |
- 执行 npm run type-check 可以对 ts 代码的类型进行检测
检测完后就会停止
- 执行 npm run type-check-watch 可以实时的检测类型错误
会持续监控代码变化,不会停止,需要手动停止。
十一、ESLint
ESLint 是一个静态代码分析工具(Static program analysis,在没有任何程序执行的情况下,对代码进行分析)
ESLint 可以帮助我们在项目中建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性
并且 ESLint 的规则是可配置的,我们可以自定义属于自己的规则
早期还有一些其他的工具,比如 JSLint、JSHint、JSCS 等,目前使用最多的是 ESLint。
使用 ESLint
使用脚手架的话一般都会配置 ESLint
如果从零开始进行搭建的话需要
- 手动安装
npm install eslint -D
- 新建配置文件,没有配置文件无法进行代码规范检测
npx eslint --init
- 选择想要使用的 ESLint 配配置:
- 执行检测命令
npx eslint ./src/main.js
1 | var message = "Hello TypeScript"; |
会报警告
ESLint 文件解析
1 | module.exports = { |
env:
- 运行的环境,比如是浏览器,并且我们使用 es2021(对应的 ecmaVersion 是 12)的语法
extends:
- 可以扩展当前的配置,让其继承自其他的配置信息,可以跟字符串或者数组(多个)
parserOptions:
- 这里可以指定 ESMAScript 的版本、sourceType 的类型
parser:
- 默认情况下是 espree(也是一个 JS Parser,用于 ESLint),但是因为我们需要编译 TypeScript,所以需要指定对应的解释器
plugins:
- 指定我们用到的插件
rules:
- 自定义的一些规则
eslint-loader
可以安装 loader,在编译打包 js 文件时自动校验规范
npm i eslint-loader
1 | { |
这样在编译打包 js 文件时先回进行 eslint 检测,不符合规范就会报错。
注意:2021.12.15 我安装的 eslint 是 v8 版本的,这个版本貌似更新了一些东西,可以回退到 v7 版本,兼容性貌似更好一些
十二、加载 Vue 文件
编写 vue 代码
npm i vue
src -> index.js:
1 | import Vue from "vue"; |
src -> App.vue
1 | <template> |
安装依赖
npm install vue-loader -D
npm install vue-loader -D 这个用于解析 template 模板
配置 webpack
1 | // 加载vue必须使用这个插件 |
注意:
- 必须使用 VueLoaderPlugin 这个插件
- App.vue 文件中使用了 less,所以需要对 less 进行配置