一、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';
  2. const fun = (msg) => {
  3. console.log(msg);
  4. };
  5. fun(message);

npx babel src --out-dir dist后:

  1. const message = 'Hello Babel';
  2. const fun = msg => {
  3. console.log(msg);
  4. };
  5. fun(message);

可见,几乎没有变化,因为babel/core只是核心库,需要借助插件来进行编译转换。

四、插件的使用

比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:

  1. npm install @babel/plugin-transform-arrow-functions -D
  2. npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
  1. const message = 'Hello Babel';
  2. const fun = function (msg) {
  3. console.log(msg);
  4. };
  5. fun(message);

可见箭头函数已经转换为普通函数

但是会发现 const 并没有转成 var

这是因为 plugin-transform-arrow-functions并没有提供这样的功能

我们需要使用 plugin-transform-block-scoping 来完成这样的功能;

  1. npm install @babel/plugin-transform-block-scoping -D
  2. npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping
  3. ,@babel/plugin-transform-arrow-functions
  1. var message = 'Hello Babel';
  2. var fun = msg => {
  3. console.log(msg);
  4. };
  5. fun(message);

可以,都已经转为ES5的写法。

五、Babel的预设preset

如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset)

后面我们再具体来讲预设代表的含义

  • 安装@babel/preset-env预设

    • npm install @babel/preset-env -D
  • 执行如下命令:

    • npx babel src --out-dir dist --presets=@babel/preset-env
  1. "use strict";
  2. var message = 'Hello Babel';
  3. var fun = function fun(msg) {
  4. console.log(msg);
  5. };
  6. fun(message);

六、Babel的底层原理

babel是如何做到将我们的一段代码(ES6、TypeScript、React)转成另外一段代码(ES5)的呢?

从一种源代码(原生语言)转换成另一种源代码(目标语言),这是什么的工作呢?

  • 就是编译器,事实上我们可以将babel看成就是一个编译器
  • Babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码
  • Babel也拥有编译器的工作流程:

    • 解析阶段(Parsing)
    • 转换阶段(Transformation)
    • 生成阶段(Code Generation)

编译器执行原理

5、webpack-Babel深入解析 - 图1

七、babel-loader

实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用的,比如在webpack中。

那么我们就需要去安装相关的依赖:

如果之前已经安装了@babel/core,那么这里不需要再次安装

npm install babel-loader @babel/core

我们可以设置一个规则,在加载js文件时,使用我们的babel:

  1. module: {
  2. rules: [
  3. {
  4. test: /\.m?js$/,
  5. use: {
  6. loader: 'babel-loader',
  7. },
  8. },
  9. ],
  10. },

npm run build后:

  1. /******/ (function() { // webpackBootstrap
  2. var __webpack_exports__ = {};
  3. /*!*********************!*\
  4. !*** ./src/main.js ***!
  5. \*********************/
  6. const message = 'Hello Babel';
  7. const fun = msg => {
  8. console.log(msg);
  9. };
  10. fun(message);
  11. /******/ })()
  12. ;

ES6语法并没有转换,因为我们没用使用插件

指定使用的插件

  1. module: {
  2. rules: [
  3. {
  4. test: /\.m?js$/,
  5. use: {
  6. loader: 'babel-loader',
  7. options: {
  8. plugins: [
  9. '@babel/plugin-transform-block-scoping',
  10. '@babel/plugin-transform-arrow-functions',
  11. ],
  12. },
  13. },
  14. },
  15. ],
  16. },

npm run build后:

  1. /******/ (function() { // webpackBootstrap
  2. var __webpack_exports__ = {};
  3. /*!*********************!*\
  4. !*** ./src/main.js ***!
  5. \*********************/
  6. var message = 'Hello Babel';
  7. var fun = function (msg) {
  8. console.log(msg);
  9. };
  10. fun(message);
  11. /******/ })()
  12. ;

babel-preset

如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个 preset

webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel。

比如常见的预设有三个:

  • env
  • react
  • TypeScript

安装preset-env:

npm install @babel/preset-env

  1. /******/ (function() { // webpackBootstrap
  2. var __webpack_exports__ = {};
  3. /*!*********************!*\
  4. !*** ./src/main.js ***!
  5. \*********************/
  6. var message = 'Hello Babel';
  7. var fun = function fun(msg) {
  8. console.log(msg);
  9. };
  10. fun(message);
  11. /******/ })()
  12. ;

设置目标浏览器 browserslist

我们最终打包的JavaScript代码,是需要跑在目标浏览器上的,那么如何告知babel我们的目标浏览器呢?

  • browserslist工具
  • ptarget属性

说明一点:babel是转换源代码且运行在浏览器上,那么就要知道浏览器支持的语法来进行源代码的转换

之前项目中已经使用了browserslist工具,可以对比一下不同的配置,打包的区别:

5、webpack-Babel深入解析 - 图2

chrome支持ES6语法,所以babel就不会进行转换了。

设置目标浏览器targets

  1. module: {
  2. rules: [
  3. {
  4. test: /\.m?js$/,
  5. use: {
  6. loader: 'babel-loader',
  7. options: {
  8. // plugins: [
  9. // '@babel/plugin-transform-block-scoping',
  10. // '@babel/plugin-transform-arrow-functions',
  11. // ],
  12. presets: ['@babel/preset-env',{targets:"last 2 version"}],
  13. },
  14. },
  15. },
  16. ],
  17. },

那么,如果两个同时配置了,哪一个会生效呢?

  • 配置的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 = {
  2. "presets":["stage-0"]
  3. }

它表达的含义是使用对应的 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 = {
  2. presets: ['@babel/preset-env'],
  3. };

八、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. {
  2. test: /\.m?jsx?$/,
  3. //忽略第三方包使用babel打包编译
  4. exclude:/node_modules/,
  5. use: {
  6. loader: 'babel-loader',
  7. },
  8. },

配置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 = {
  2. presets: [
  3. [
  4. '@babel/preset-env',
  5. {
  6. //false
  7. //usage
  8. //entry
  9. useBuiltIns: 'entry',
  10. corejs: 3,
  11. },
  12. ],
  13. ],
  14. };

入口文件main.js

  1. import 'core-js/stable';
  2. import 'regenerator-runtime/runtime';
  3. const message = 'Hello Babel';
  4. const fun = (msg) => {
  5. console.log(msg);
  6. };
  7. fun(message);
  8. let promise = new Promise((resolve, reject) => {});

打包后的文件有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 = {
  2. presets: [
  3. ['@babel/preset-env'],
  4. ],
  5. plugins: [
  6. [
  7. '@babel/plugin-transform-runtime',
  8. {
  9. corejs: 3,
  10. },
  11. ],
  12. ],
  13. };

注意:因为使用了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 = {
  2. presets: [
  3. [
  4. '@babel/preset-env',
  5. {
  6. useBuiltIns: 'usage',
  7. corejs: 3,
  8. },
  9. ],
  10. ['@babel/preset-react']
  11. ],
  12. };

十、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. {
  2. test: /\.ts$/,
  3. exclude: /node_modules/,
  4. use: ['ts-loader'],
  5. },

之后,我们通过npm run build打包,会报错,因为需要一个tsconfig.json文件

tsc —init 生成一个tsconfig.js文件

但是ts-loader会依赖本地的typescript,所以本地也需要安装下

npm install typescript -D

npm run build

bundle.js

  1. /******/ (function() { // webpackBootstrap
  2. /******/ "use strict";
  3. var __webpack_exports__ = {};
  4. // This entry need to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
  5. !function() {
  6. var exports = __webpack_exports__;
  7. /*!**********************!*\
  8. !*** ./src/index.ts ***!
  9. \**********************/
  10. Object.defineProperty(exports, "__esModule", ({ value: true }));
  11. var message = 'Hello TypeScript';
  12. var foo = function (msg) {
  13. console.log(msg);
  14. };
  15. foo(message);
  16. }();
  17. /******/ })()
  18. ;

使用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. {
  2. test: /\.ts$/,
  3. exclude: /node_modules/,
  4. use: ['babel-loader'],
  5. },
  1. presets: [
  2. [
  3. '@babel/preset-env',
  4. {
  5. useBuiltIns: 'usage',
  6. corejs: 3,
  7. },
  8. ],
  9. ['@babel/preset-react'],
  10. ['@babel/preset-typescript']
  11. ],

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官方文档有对其进行说明:

5、webpack-Babel深入解析 - 图3

  • 也就是说我们使用Babel来完成代码的转换,使用tsc来进行类型的检查。
  • 但是,如何可以使用tsc来进行类型的检查呢?
  • 在这里,在scripts中添加了两个脚本,用于类型检查
  1. "scripts": {
  2. "test": "echo \"Error: no test specified\" && exit 1",
  3. "build": "webpack",
  4. "type-check": "tsc --noEmit",
  5. "type-check-watch": "npm run type-check -- --watch"
  6. },
  • 执行 npm run type-check可以对ts代码的类型进行检测

5、webpack-Babel深入解析 - 图4

检测完后就会停止

  • 执行 npm run type-check-watch可以实时的检测类型错误

5、webpack-Babel深入解析 - 图5

会持续监控代码变化,不会停止,需要手动停止。

十一、ESLint

ESLint是一个静态代码分析工具(Static program analysis,在没有任何程序执行的情况下,对代码进行分析)

ESLint可以帮助我们在项目中建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性

并且ESLint的规则是可配置的,我们可以自定义属于自己的规则

早期还有一些其他的工具,比如JSLint、JSHint、JSCS等,目前使用最多的是ESLint。

使用ESLint

使用脚手架的话一般都会配置ESLint

如果从零开始进行搭建的话需要

  • 手动安装

    • npm install eslint -D
  • 新建配置文件,没有配置文件无法进行代码规范检测

    • npx eslint --init
  • 选择想要使用的ESLint配配置:

5、webpack-Babel深入解析 - 图6

  • 执行检测命令

    • npx eslint ./src/main.js
  1. var message = 'Hello TypeScript';
  2. var foo = function (msg) {
  3. console.log(msg);
  4. };
  5. foo("qwe");

会报警告

5、webpack-Babel深入解析 - 图7

ESLint文件解析

  1. module.exports = {
  2. "env": {
  3. "browser": true,
  4. "commonjs": true,
  5. "es2021": true,
  6. //我使用了node的全局变量 不配置这个会报错
  7. "node": true
  8. },
  9. "extends": [
  10. "eslint:recommended",
  11. "plugin:vue/essential",
  12. "plugin:@typescript-eslint/recommended"
  13. ],
  14. "parserOptions": {
  15. "ecmaVersion": 13,
  16. "parser": "@typescript-eslint/parser"
  17. },
  18. "plugins": [
  19. "vue",
  20. "@typescript-eslint"
  21. ],
  22. "rules": {
  23. //我使用了require导入包 不配置这个会报错
  24. '@typescript-eslint/no-var-requires': 0,
  25. }
  26. };

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. {
  2. test: /\.m?jsx?$/,
  3. exclude: /node_modules/,
  4. use: ['babel-loader', 'eslint-loader'],
  5. },

这样在编译打包js文件时先回进行eslint检测,不符合规范就会报错。

注意:2021.12.15我安装的eslint是v8版本的,这个版本貌似更新了一些东西,可以回退到v7版本,兼容性貌似更好一些

十二、加载Vue文件

编写vue代码

npm i vue

src -> index.js:

  1. import Vue from 'vue';
  2. import App from './App.vue';
  3. new Vue({ render: (h) => h(App) }).$mount('app');

src -> App.vue

  1. <template>
  2. <div>
  3. <h2 class="title">{{ msg }}</h2>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. data: () => {
  9. msg: 'Hello Vue In Webpack';
  10. },
  11. };
  12. </script>
  13. <style scope lang="less">
  14. .title {
  15. color: aqua;
  16. }
  17. </style>

安装依赖

npm install vue-loader -D

npm install vue-loader -D 这个用于解析template模板

配置webpack

  1. // 加载vue必须使用这个插件
  2. const VueLoaderPlugin = require('vue-loader/lib/plugin');
  3. {
  4. test: /\.vue$/,
  5. use: ['vue-loader'],
  6. },
  7. {
  8. test: /\.less$/,
  9. use: [
  10. 'style-loader',
  11. {
  12. loader: 'css-loader',
  13. options: {
  14. importLoaders: 2,
  15. },
  16. },
  17. 'postcss-loader',
  18. 'less-loader',
  19. ],
  20. },
  21. plugins: [
  22. new VueLoaderPlugin(),
  23. ],

注意:

  • 必须使用VueLoaderPlugin这个插件
  • App.vue文件中使用了less,所以需要对less进行配置