SkyWT / 博客 / 从零开始,配置一套现代前端工具链

从零开始,配置一套现代前端工具链

2024 年 3 月 16 日 07:31
共 2 条评论


文章目录

现代前端应用框架(如 Next.js、Nuxt.js 等)都直接集成了完整的工具链,按照官方文档做,一行命令就可以配置完毕。这整套工具在我们调试和构建项目时,在背后做了大量工作。虽然这有助于快速上手,但是非常不利于我们了解其中的原理。

然而,各种工具纷繁复杂,文档浩如烟海。由于工具之多,即使文档再友好、工具本身再易用,也很难快速入门。

本文将带你踏上一段旅程,从一个空文件夹开始,一步一步添加工具,最终配置一套完整的前端工具链。在其中,我们可以对各个工具的概念、用途和原理有一个比较系统的认识。每个部分都列出了相关文档的链接,方便查阅。

我们使用 React.js 前端框架,使用 Tailwind 编写 CSS,使用 TypeScript 编写脚本,并使用 ESLint 进行代码检查。最终,希望达到和使用 create-react-app 工具创建的项目类似的开发体验。

TL;DR

配置完毕后,整套工具链的示意图如下:

配置完毕后的工具链示意图

从创建一个 npm 项目开始

创建一个空目录(一般目录名就是项目名),进入其中执行 npm init,这个命令会交互式地让你填写该项目的元信息。

我们将这个项目命名为 study-chain(意为 study frontend toolchain):

mkdir study-chain
cd study-chain
npm init

确认信息之后,目录下会生成 package.json 文件,记录了项目的元信息:

// package.json
{
  "name": "study-chain",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "SkyWT",
  "license": "ISC"
}

接下来,我们以 moment 模块为例,这是一个用于转换日期格式的模块(这个模块其实已经废弃,不推荐新项目使用。我们只是将其作为示例,参见文档)。

在项目根目录下,使用 npm i 安装模块:

npm i moment

我们只使用这个模块的一个功能为例:将模块引入为 moment 之后,moment().format() 返回当前日期时间字符串。

Webpack

让我们先忘掉 React.js,从编写纯 HTML 和 JavaScript 开始。如何在这个项目里使用之前安装的 moment 模块呢?

考虑编写一个简单的 HTML 文件 index.html

<html>
  <body>
    <div id="app"></div>
    <script src="./index.js"></script>
  </body>
</html>

这个 HTML 引用了 index.js。这个 js 文件引入了 moment 模块,将 div 内的内容设置为当前时间:

// index.js
const moment = require("moment");

const app = document.getElementById("app");
app.innerText = moment().format();

当然,如果此时在浏览器中打开 HTML,这段 js 是无法运行的。因为 require 是 Node.js 的语法,浏览器并不支持。

但是我们知道这个模块就在本地,它的源文件就在 node_modules/moment 路径下。我们需要一个工具获取这个模块,整合进这段 js 里。这种工具就叫做 bundler。有了 bundler,即使在用于前端的 js 中,我们也能引入模块了。

Webpack 就是其中之一。

类似的工具有:Rollup、Parcel。

安装与使用

首先安装 webpack 和 webpack-cli。后者是配套的命令行工具。这两个工具都只是在开发阶段使用,所以使用 --save-dev 安装为开发环境依赖:

npm i webpack webpack-cli --save-dev

安装后,可以直接使用 npx webpack 命令:

npx webpack ./index.js --mode=development

这个命令处理 index.js 文件,解析其中引用的模块,将对应的 js 代码注入该文件。参数 --mode=development 指示生成开发环境下易于调试的文件版本。如果在生产环境,应使用 --mode=production

运行之后,会生成 dist/main.js(这是默认的输出文件,可配置),这就相当于浏览器版的源文件。于是,修改 HTML 中引用的 script 路径:

<html>
  <body>
    <div id="app"></div>
    <script src="./dist/main.js"></script>
  </body>
</html>

用浏览器打开,可以发现成功地调用了该模块,div 中显示了当前的日期时间。

使用 --watch 参数可以使 webpack 保持运行,持续监听源文件的修改:

npx webpack ./index.js --mode=development --watch

运行时,每当编辑 index.js 并保存,都会自动重新生成 dist/main.js 文件。可以在终端看到对应的输出。

除了 require 语法,webpack 也支持更常用的 import 语法。刚才的引入模块语句可以改成:

// index.js
import moment from "moment";
// ...

配置文件 webpack.config.js

使用 webpack 的配置文件,可以替代运行命令时传递的参数,让命令行的使用更简洁和灵活。(相关文档

在项目根目录创建名为 webpack.config.js 的文件,内容如下:

// webpack.config.js
const path = require("path");
module.exports = {
  mode: 'development',
  entry: './index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, "dist")
  }
};

创建了配置文件之后,使用命令行时,只需要使用如下命令:

npx webpack
npx webpack --watch

设置 npm scripts

为了方便起见,可以将以上 webpack 命令设置为 npm scripts

编辑 package.json 文件,添加 scripts:

// package.json
{
  // ...
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch"
  },
  // ...
}

保存之后,只需使用如下命令,就等同于运行设置的 webpack 命令:

npm run build
npm run watch

为了表述方便,下文将运行 npm run build 命令的这一操作简称为 build。

生成 HTML

现在,构建完成后,访问 index.html 就能看到我们的网站。然而可以发现,这个 HTML 中 main.js 需要我们手动引用。能否让 webpack 帮我们完成这件事情呢?

这就需要让 webpack 为我们在 dist 目录中生成 HTML 文件。这可以通过 html-webpack-plugin 这个插件实现。没错,webpack 不仅是一个打包工具,其还拥有着丰富的插件生态

运行以下命令安装 html-webpack-plugin(文档):

npm i html-webpack-plugin --save-dev

index.html 重命名为 template.html,内容如下:

<html>
  <body>
    <div id="app"></div>
  </body>
</html>

接下来修改 webpack.config.js,添加 html-webpack-plugin 插件的配置:

// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
// ...
module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      template: "template.html"
    })
  ]
};

再次使用 build 构建,会发现 dist 目录下生成了 index.html,这个 HTML 引用了生成的 main.js 脚本。打开就能看见其实现了我们要的应用逻辑。

使代码可以 import CSS 文件

现在,有了 webpack 的加持,我们的 js 代码已经可以导入 module 了。但是如果需要引入其他静态资源,比如 CSS 文件,还是无法直接完成。为了使代码能直接 import 其他类型的文件,webpack 中可以安装配置一种称为 loader 的模块

💡 Webpack 中的 loader 与 plugin:二者都是可以集成到 webpack 的模块,但是两个不同的概念。loader 一般用于处理特定类型的文件,而 plugin 可以提供更加广泛的功能。

比如,为了引入 CSS 文件,可以安装 style-loadercss-loader 两个模块(相关文档):

npm i style-loader css-loader --save-dev

接下来,修改 webpack 配置文件,添加一条规则:对于文件名以 .css 结尾的文件,使用这两个模块:

// webpack.config.js
// ...
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
 };

Webpack 会按照配置的顺序调用 loader。在该配置文件下,先调用 style-loader,再调用 css-loader。这两个 loader 分别的作用是:

  • style-loader:将一个 CSS 文件注入 DOM,放在 <style> 元素中。(文档
  • css-loader:解析 CSS 中的 @importurl() 等语句,将对应引用的文件配置好。(文档

现在,可以使用 import 语句导入 CSS 文件了。首先还是在根目录下编写 style.css 文件:

// style.css
.bg-gray {
  background-color: #aaa;
}

保存后,修改 index.js 文件:

// index.js
import moment from "moment";
import "./style.css";

const app = document.getElementById("app");
app.innerText = moment().format();

app.classList.add("bg-gray");

重新 build 后,打开 HTML 即可发现样式的变化。

PostCSS

顾名思义。PostCSS 能够对 CSS 文件进行「后处理」(post-processing)。

和之前提到的 style-loader 和 css-loader 一样,PostCSS 也可以作为 loader 集成到 webpack。

集成到 webpack

首先还是安装 postcss-loader,同时安装 PostCSS 的一个插件 autoprefixer

npm i postcss-loader autoprefixer --save-dev

webpack.config.js 中添加配置:

// webpack.config.js
// ...
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 1,
            },
          },
          "postcss-loader",
        ],
      },
    ],
  },
  // ...
}

⚠️ 注意:此处调用 css-loader 处添加了 options,将 importLoaders 设置为 1。这是考虑到 PostCSS 可能引入新的 @import 等语句,css-loader 要在其运行之后重新进行解析(相关文档)。如果确定 PostCSS 不会添加新的 @import 等语句,则此参数可不加。(可参考 GitHub 上的相关讨论

配置文件 postcss.config.js

接下来创建 PostCSS 的配置文件,项目根目录下的 postcss.config.js 文件(文档):

// postcss.config.js
/** @type {import('postcss-load-config').Config} */
module.exports = {
  plugins: [require("autoprefixer")],
};

在以上的配置文件中,我们加载了 PostCSS 的 autoprefixer 插件(文档)。由于浏览器支持的差异,部分浏览器中使用某些样式需要加上特定的前缀,比如 webkit 或者 moz,这叫做 vendor prefix。这个插件会自动添加这种前缀,确保样式的兼容性。这里使用此插件只是为了演示 PostCSS 插件的使用,因为接下来我们将配置使用 Tailwind 插件。

PostCSS 是 webpack 的插件,autoprefixer 又是 PostCSS 的插件,也就是 webpack 的插件的插件。接下来我们还可以安装 Tailwind 的插件,即 webpack 的插件的插件的插件。前端工具链就是如此。

Tailwind CSS

使用过 Tailwind 之后,在开发任何前端项目时,我的心理状态:

没有它我不能活!😭😭😭

是的,之后在开发任何前端项目的时候,我没有一次离开过 Tailwind。即使是写纯 HTML 也要从 CDN 引入静态文件。因为它彻底改变了我们编写样式的方式。

作为现代前端项目,Tailwind 当然是必备的工具。

集成到 PostCSS

首先还是安装 Tailwind:

npm install tailwindcss --save-dev

接下来,在 PostCSS 中添加 Tailwind 插件(官方指南):

// postcss.config.js
/** @type {import('postcss-load-config').Config} */
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
};

配置文件 tailwind.config.js

下一步,使用以下命令创建 Tailwind 的配置文件:

npx tailwindcss init

Tailwind 会生成自己的配置文件 tailwind.config.js文档):

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

配置文件中的 content 的值,是一个字符串数组,其中存放着需要处理的文件路径。Tailwind 会尝试检测所有匹配的文件中出现的 class 值,并添加对应的 CSS 定义。

为了匹配我们根目录下的模板 HTML 和 js 文件,删除路径中 src 部分。(或者也可以将所有源文件放到 src 子目录里——大多数项目都是这样做的。下一步在整理文件环节,我们也会这样做)。

接下来,在我们引用的主样式表(即 style.css)的开头,加上 @tailwind 指令

// style.css
@tailwind base;
@tailwind components;
@tailwind utilities;

.bg-gray {
    background-color: #aaaaaa;
}

大功告成。接下来可以尝试修改 HTML 模板并重新 build,就可以发现能使用 Tailwind 了!

<html>
  <body>
    <div class="text-4xl" id="app"></div>
  </body>
</html>

(当然,集成到 PostCSS 并不是使用 Tailwind 的唯一方式。官方的Get started 中提供了大量框架、工具的集成指南)

安装 Tailwind 插件

没错,Tailwind 也有插件生态,比如 tailwindcss-animated文档)和 typography文档),这两个插件我都比较常用。

Tailwind 插件配置起来并不难,这里不再展开了,可以查阅相关文档。

中场休息:整理目录结构

至此,CSS 相关的工具配置完了。在进行下一步之前,是时候整理一下我们项目的目录结构了。

如前文所述,为了让项目目录更简洁,我们将所有源文件移动到新建的 src 文件夹内。移动之后,项目目录结构如下:

node_modules/
  ...
dist/
  ...
src/
  template.html
  index.js
  style.css
package-lock.json
package.json
postcss.config.js
tailwind.config.js
webpack.config.js

为了使所有工具只处理 src 目录下的文件,需要修改部分配置文件。

修改 tailwind.config.js

// tailwind.config.js
module.exports = {
  content: ["./src/**/*.{html,js}"],
  // ...
};

修改 webpack.config.js

// webpack.config.js
module.exports = {
  // ...
  entry: "./src/index.js",
  // ...
};

这下,我们的项目目录就干净了很多。是时候进行下一步了!

Babel

JavaScript 是浏览器原生支持的唯一语言,但:1)不同浏览器对该语言的新特性支持有所不同;2)许多人不喜欢 JavaScript 弱类型的特性,TypeScript 应运而生。但浏览器本身不支持 TypeScript。

所以,需要这样一种工具:1)将 JavaScript 的新特性相关代码转换为使用旧特性的实现;2)将 TypeScript 翻译为 JavaScript。这个过程和 C++ 这类语言「编译」的过程有些相似,只是目标是 JavaScript 而非二进制。

这种工具就叫做 transpiler(可以翻译成「转译器」)。它的作用是将一段代码「翻译」成另一段代码,但目标代码仍然是高级语言(一般是 JavaScript)。这个「翻译」和传统编程语言中的「Compile」概念不同,称为「Transpile」。

Babel 就是其中之一。

(吐槽:既然都要 transpile 才能运行代码,不如直接 compile 成更低级的字节码,执行效率还会更高。Web Assembly 就这样诞生了。不过这里不介绍了)

集成到 webpack

Babel 可以和 PostCSS 一样作为 loader 集成在 webpack 里。安装 Babel:

npm i babel-loader @babel/core --save-dev

安装后,修改 webpack 配置文件,对 .js 文件使用 babel-loader(排除 node_modules 目录中的文件):

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
};

(同样,集成到 webpack 也非安装 Babel 的唯一方式。官方指南提供了很多种配置方式)

目前,重新 build 时,虽然会调用 babel-loader,但是 Babel 还什么事情都没做。这是因为我们没有为其指定任何规则。一般可以通过 preset 指定规则。

presets

Babel 中的 preset 这一概念,官方的定义是「可分享的一组插件和配置的集合」(文档)。

官方提供了四种 preset:

  • env:用于将较新的 ECMAScript 特性转译为兼容较旧环境的实现。
  • react:用于转译 React.js 的 JSX 语法。
  • typescript:用于转译 TypeScript。
  • flow:用于 flow 工具,这是一个静态类型检查器。

配置文件 babel.config.json

在项目根目录下创建配置文件 babel.config.json,其中可以添加 preset 指定规则。我们先添加一个 preser-env:

// babel.config.json
{
  "presets": ["@babel/preset-env"]
}

别忘了安装这个 preset:

npm i @babel/preset-env --save-dev

安装和配置完毕后,重新 build,就会使用 preset-env 指定的 transpile 规则。这套规则有什么用呢?

preset-env

ECMAScript 标准每一两年都推出新的版本,引入新的特性。而不同浏览器对其的实现难免会有所滞后。为了:1)能及时使用 ECMAScript 的新特性;2)确保我们的代码在所有浏览器环境中的表现一致,Babel 提供的 preset-env 可以将使用新特性的代码 transpile 为使用旧特性的实现。文档)(在 Babel 出现之前,许多应用引入一个静态的 js 脚本完成这一功能,这种脚本叫做「polyfill」)

比如,ES6 引入了箭头函数和 const 关键字:

const a = [1,2,3];
a.forEach((x) => console.log(x));

如果要兼容不支持 ES6 的环境(虽然所有现代浏览器都已经支持了 ES6),Babel 就要将箭头函数转换成普通函数,const 换成 var:

var a = [1, 2, 3];
a.forEach(function (x) {
  return console.log(x);
});

可以在官网的 Try it out 中尝试。

TypeScript

TypeScript 也是开发现代 Web 应用的必备。如果 standalone 地安装,可以使用 tsc 命令将一个 .ts 文件 transpile 成一个 .js 文件。然而,为了使这一过程在 build 时自动完成,还是要将其集成到 Babel。

集成到 Babel

如前所述,Babel 已经提供了 TypeScript 的 preset(文档),其中包含了转译 TypeScript 的插件。只需直接安装:

npm i @babel/preset-typescript --save-dev

webpack.config.js 中,要修改两个地方:1)将 entry 改为 index.ts;2)将 babel-loader 的 test 规则改为匹配 .ts 结尾的文件:

// webpack.config.js
module.exports = {
  // ...
  entry: "./src/index.ts",
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
};

babel.config.json 里,加入 preset-typescript

// babel.config.json
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript"
  ]
}

现在,可以将 src 中的 index.js 改写为 index.ts 了。由于这段代码很短,只需要改一个地方,即判断 app 是否为 null

import moment from "moment";
import "./style.css";

const app = document.getElementById("app");
if (app !== null) {
  app.innerText = moment().format();
  app.classList.add("bg-gray");
}

配置文件 tsconfig.json

TypeScript 也有配置文件。在项目根目录下创建 tsconfig.json 即可。

具体规则可参考官方文档。当我们配置好 React 后会再来修改 TypeScript 的规则配置。

React.js

使用 React 时我们会编写 JSX(或 TSX)语法的代码。JSX(或 TSX)全称 JavaScript(TypeScript)Extension,这是一种糅合了 HTML 和 JavaScript(TypeScript)语法的代码。当然,无论是浏览器还是 Node 都不支持这种代码,所以需要 Babel 为我们转译。其实,这样的代码中,类似 HTML 的那部分会被转译成 JavaScript 递归的函数调用的形式。

集成到 Babel

Babel 也提供了 React 的 preset(文档),包含了对应插件。只要安装:

npm i @babel/preset-react --save-dev

webpack.config.js 中设置匹配 .ts 或 .tsx 结尾的文件:

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
};

修改 Babel 配置文件 babel.config.json,添加 preset-react

// babel.config.json
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

接下来,为了让 TypeScript 解释 TSX 语法,要在 tsconfig.json 中加入如下配置:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "jsx": "react",
    "esModuleInterop": true,
    "noEmit": true,
    "allowImportingTsExtensions": true
  }
}

这段配置文件中:

  • strict 设为 true 表示开启严格类型检查,包括不允许隐式 any 类型等等。
  • jsx 设为 react,表示启用 JSX 支持。
  • esModuleInterop 设为 true 允许用 import 语法直接导入 CommonJS 模块(否则,必须使用 require 的语法)。
  • noEmit 表示不输出编译后的结果文件。由于在该配置中 TypeScript 是作为 Babel 的一个插件,转译后结果文件由 Babel 输出。
  • allowImportingTsExtensions 表示允许导入 .tsx 类型的文件。

以及,现在我们的脚本文件可以是 js、jsx、ts、tsx 格式了,要在 Tailwind 的配置文件中修改其检测的文件格式:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

最后,别忘了安装 React 本体,以及其对应的类型定义:

npm i react react-dom --save
npm i @types/react @types/react-dom --save-dev

编写 React 组件

现在,在 src 下创建 App.tsx 文件,我们可以在其中用 TSX 语法编写一个 React 组件了。

将之前写的显示时间的组件写进这里面:

// App.tsx
import React from "react";
import "./style.css";
import moment from "moment";

export default function App() {
  return (
    <div className="App">
      <h1 className="text-4xl">Hello, World!</h1>
      <p>{moment().format()}</p>
    </div>
  );
}

为了引用该组件,入口文件 index.ts 也需要用到 TSX 语法。因此,将其重命名为 index.tsx,修改为如下内容:

// index.tsx
import React from "react";
import { createRoot } from "react-dom/client";

import App from "./App.tsx";

const container = document.getElementById("app");
if (container !== null) {
  const root = createRoot(container);
  root.render(<App />);
}

同时,要在 webpack 配置中修改 entry:

// webpack.config.js
module.exports = {
  // ...
  entry: "./src/index.tsx",
  // ...
}

现在,build 之后,打开生成的 HTML,可以看到我们用 React 写的组件了。

ESLint

ESLint 是一个代码检查工具。对于团队项目,统一代码风格十分重要,而 ESLint 可以方便地做到这一点:如果没有满足指定的代码风格,则显示警告或错误(如果在 IDE 中集成的话),或者拒绝提交或部署(如果在提交部署流程中集成的话)。

为了使流程更加清晰,我们还是选择将 ESLint 作为一个插件集成到 webpack

集成到 webpack

安装 eslint-webpack-plugin相关文档):

npm i eslint-webpack-plugin --save-dev

修改 webpack 的配置,添加该插件:

// webpack.config.js
// ...
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
  // ...
  plugins: [
    // ...
    new ESLintPlugin({
      extensions: ["js", "jsx", "ts", "tsx"],
    }),
  ],
  // ...
};

配置文件中 new ESLintPlugin({}) 可以传入一个 options 对象,用于指定 ESLint 插件选项文档)。这里我们指定了要 lint 的文件拓展名。

配置文件 .eslintrc.js

可以使用 @eslint/config 创建配置文件(文档),这是一个友好的交互式命令:

npm init @eslint/config

在其中可以选择「项目使用了 React.js、TypeScript」,该命令会自动为我们安装配置对应的 ESLint 插件。

运行完成后,除了安装了一堆插件,项目根目录会产生配置文件 .eslintrc.js(或者其他文件格式,取决于你的选择)。

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: ["standard-with-typescript", "plugin:react/recommended"],
  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["react"],
};

现在,再次 build,ESLint 会按照我们设定的规则进行代码检查。

可以在配置文件中添加一些自己习惯的规则,比如使用双引号、行末加分号。并且,需要设置对于 *.config.js 这类配置文件的特殊检测规则。我的 .eslintrc.js 文件设置如下:

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: ["standard-with-typescript", "plugin:react/recommended"],
  overrides: [
    {
      env: {
        node: true
      },
      files: [".eslintrc.js", "*.config.js"],
      parserOptions: {
        sourceType: "script"
      },
      extends: ["plugin:@typescript-eslint/disable-type-checked"],
      rules: {
        "@typescript-eslint/no-var-requires": "off"
      }
    }
  ],
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module"
  },
  rules: {
    "@typescript-eslint/semi": ["error", "always"],
    "@typescript-eslint/quotes": ["error", "double"]
  },
  plugins: ["react"]
};

⚠️ 一个坑点:使用 TypeScript 时,由于使用 typescript-eslint 的解析器而非默认解析器(文档),添加规则要写 @typescript-eslint/quotes 而非 quotes,否则不会生效。例如:

// eslintrc.js
module.exports = {
  // ...
  rules: {
    "@typescript-eslint/quotes": ["error", "double"],
    "@typescript-eslint/semi": ["error", "always"],
  },
  // ...
};

💡 或许你同时在 IDE 中使用 Prettier 一类的代码格式化工具。其文档中 Prettier vs. Linters 介绍了这两种工具的区别;Integrating with Linters 介绍了其与 linter 的集成指南。简而言之,在 ESLint 配置中应用 eslint-config-prettier 规则集即可自动关闭所有与 Prettier 冲突的规则。不过我更推荐的是在 IDE 中安装 ESLint 插件,对于 js 类文件直接使用 ESLint 作为代码格式化工具,这样能够确保遵循 eslintrc 中的规则。

(ESLint 这部分配置起来还是挺麻烦的,尤其要集成到 VSCode,同时兼容 TypeScript,并考虑到其和 Prettier 的冲突。改天配置好了一个比较 fancy 的方案再单独写一篇)

Recap:站在巨人的肩膀上

至此,一套比较完整的前端项目 starter 终于配置完毕了。完整的项目可以在这个仓库查看。

回顾一下,我们首先使用了 webpack 作为打包工具;其 PostCSS 插件能够对 CSS 进行处理;Tailwind 则可以作为 PostCSS 插件集成。我们使用 Babel 这个 transpiler 处理各种脚本文件,其中 env preset 将 ECMAScript 较先进的特性转译为旧特性的实现,确保兼容性;TypeScriptReact JSX 两个 preset 则分别将它们各自的语法转译成 JavaScript。最后,我们使用 ESLint 作为代码质量检查工具,并配置其针对 TypeScript 和 JSX 的规则。

使用的工具链关系示意图如下:

配置完毕后的工具链示意图

相比从前,各种工具让开发的过程变得越来越优雅和美妙。然而每个工具背后,都有无数前人的辛勤付出,没有他们的这些努力,我们无法得到这样现代化的前端开发体验。

现代前端开发,就是站在巨人的肩膀上

一点思考 🤔

最后,还有一个我的疑问:相比其他领域,为什么 Web 前端开发的工具链会呈现如此复杂的形态呢?我体验过 iOS 开发,也了解过基于 Qt 等框架的客户端开发,我个人的感觉是没有一个领域的客户端开发像 Web 前端这样有如此庞大复杂的工具链:某个工具可以配置插件,插件又有插件,插件的插件又有插件……那么归根结底,Web 前端工具链这种复杂的形式,是历史发展的必然,是某种设计缺陷的后果,还是某种设计思想的体现?🤔

欢迎分享你的思考。

值得一读

Web development used to be a great entry point for people new to programming precisely because it was so easy to get up and running; nowadays it can be quite daunting, especially because the various tools tend to change rapidly.

—— Modern JavaScript Explained For Dinosaurs

值得一读的相关文章;


暂无评论


发表新的评论

所有评论都将经过博主审核。请勿填写无意义邮箱或发表无关评论、广告等,否则会被视为垃圾评论。

提交评论即表明你同意本网站使用 Cookie,并允许本站在后台记录你的邮箱、IP 地址等必要信息。这些信息不会被透露给其他用户。(提交一次评论后,本提示将不再展示)