问题提出

ReactJS 自己提供的 CRA(Create-React-App)工具链简单易用,一键就能创建一个 web 前端应用。我们需要的一系列依赖都被整合进了react-script进行管理。不过它的问题也比较明显,如果我们想要自定义 webpack 相关的配置时 cra 就显得不那么灵活了。
例如,cra 本身没有添加对 less 的解析,因此如果我们想在项目里使用 less,我们不得不执行npm run eject,弹出我们想编辑的 webpack config 来进行操作。而这个操作是单向操作,不可逆转。
对比一下 NextJS,人家都有Custom Webpack Config
总之,很闲的我在一个非常轻量的玩具 web 服务里尝试了一下手搓一条自己的工具链,熟悉一下构建流程以及 webpack 的配置。

技术选型

除了工具链要手搓,其它的技术栈暂时没有上新更新。依旧是 Typescript + less 这一套。

构建过程

一个完整的工具链至少需要一个包管理器、打包器和编译器,这里没啥理由总之就选用了 Yarn、Webpack 和 Babel。

Babel

1
yarn add -D @babel/core @babel/cli @babel/preset-env @babel/preset-react

这一系列包的用处博文解释如下:

babel-core is the main babel package — We need this for babel to do any transformations on our code. babel-cli allows you to compile files from the command line. preset-react and preset-env are both presets that transform specific flavors of code — in this case, the env preset allows us to transform ES6+ into more traditional javascript and the react preset does the same, but with JSX instead.

安装完成后,需要创建一个.babelrc的文件编辑配置,即需要编译 ES6+以及 JSX 的代码:

1
2
3
{
"presets": ["@babel/env", "@babel/preset-react"]
}

Webpack

1
yarn add -D webpack webpack-cli webpack-dev-server style-loader css-loader babel-loader

Webpack 通过各种各样的loaders将不同的文件打包,将它们转化为静态资源(static assets)。
Webpack
现在我们的目录结构如下:

1
2
3
4
5
6
7
8
MyDirectory
|- dist
|- public
|- index.html
|- src
|- index.js
|- webpack.config.js

然后是写一个webpack.config.js的配置。根据是否是调试环境isDev来决定是否要对代码进行一个 minify。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isDev = process.env.NODE_ENV === 'development';

module.exports = {
entry: './src/index.js',
mode: isDev ? 'development' : 'production',
devtool: isDev ? 'inline-source-map' : 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{ test: /\.tsx?$/, loader: 'ts-loader' },
{
test: /\.less$/i,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
auto: true,
localIdentName: '[local]--[hash:base64:5]',
},
},
},
'less-loader',
],
},
{
test: /\.css$/i,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
auto: true,
localIdentName: '[local]--[hash:base64:5]',
},
},
},
],
},
{
test: /\.(png|jpg|jpeg|gif)$/,
type: 'asset',
},
{
test: /\.svg$/,
exclude: /node_modules/,
use: {
loader: 'svg-react-loader',
},
},
],
},
resolve: {
extensions: ['*', '.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
output: {
path: path.resolve(__dirname, './dist'),
filename: 'static/[name].[chunkhash:8].js',
assetModuleFilename: isDev
? 'static/[name].[hash][ext]'
: 'static/[hash][ext]',
clean: true,
},
devServer: {
port: 3000,
hot: true,
static: {
directory: path.join(__dirname, '/'),
},
historyApiFallback: true,
client: {
overlay: {
errors: true,
warnings: false,
},
},
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new webpack.HotModuleReplacementPlugin(),
new MiniCssExtractPlugin(),
],
optimization: {
minimize: !isDev,
minimizer: [new TerserPlugin()],
},
};

配置完后,需要手动设置一下package.json里的 script,指定如何运行调试和打包,例如:

1
2
3
4
5
6
7
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack serve",
"build": "webpack"
}
}

React

最后装上reactreact-dom,一套最基础的工具链就完成了。

1
yarn add react react-dom

Typescript

配置 typescript 需要首先有一个tsconfig.json文件,所以先初始化一个出来:

1
tsc --init

初始化后的 config 文件需要手动调整一下,例如需要 include 我们需要的 src 文件夹,exclude node_modules 避免混乱。顺便声明一个@方便引用组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"compilerOptions": {
"target": "ES2020",
"jsx": "react-jsx",
"module": "ES6",
"moduleResolution": "node",
"paths": {
"@/*": ["./src/*"]
},
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"alwaysStrict": true,
"skipLibCheck": true
},
"include": ["src", "./react-app-env.d.ts"],
"exclude": ["node_modules"]
}

除了 tsconfig,我们还需要一个声明 web 开发所需文件的类型声明,因此创建一个react-app-env.d.ts进行相关声明,和 webpack config 类似,需要什么就添加什么进去,例如:

1
2
3
4
5
6
7
declare module '*.less';

declare module '*.svg' {
export default function ReactComponent(
props: React.SVGProps<SVGSVGElement>
): React.ReactElement;
}

Eslint && Prettier

添加 Eslint 和 Prettier 是可选的,但是如果是合作开发项目并且你的队友和你的代码风格不同并且完全不在乎 coc 方面的事情,有一个 eslint 来做代码规范对我这种强迫症而言就很重要(。
现在快速配置一个 eslint 很方便,只需要在yarn add -D eslint后进行一次 init 即可,可以自行选择想要的风格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
palemoons@MacBookPro ~/myDir $ npx eslint --init
You can also run this command directly using 'npm init @eslint/config'.
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · prompt
✔ What format do you want your config file to be in? · JSON
✔ What style of indentation do you use? · space
✔ What quotes do you use for strings? · single
✔ What line endings do you use? · unix
✔ Do you require semicolons? · No / Yes
The config that you've selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · yarn
Installing eslint-plugin-react@latest, @typescript-eslint/eslint-plugin@latest, @typescript-eslint/parser@latest
yarn add v1.22.17
...
$ husky install
husky - Git hooks installed
✨ Done in 14.09s.
Successfully created .eslintrc.json file in /myDir

这样一个最简单的 eslint 配置就 OK 了。最后是添加 prettier 以及相关插件,避免它和 eslint 的冲突。

1
yarn add -D eslint-config-prettier eslint-plugin-prettier prettier

这是两个插件的作用描述:

eslint-config-prettier - Turns off all rules that are unnecessary or might conflict with Prettier.
eslint-plugin-prettier - Runs Prettier as an ESLint rule

最后在之前配置好的.eslintrc文件里添加关于 prettier 的配置,注意要放在最后以覆盖之前的配置:

1
2
3
4
5
6
7
{
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
},
"extends": ["plugin:prettier/recommended"]
}

这样,我们就把 eslint 有关 code formatting 的部分全部关掉,并把这部分交给 prettier 处理。

commitizen + husky

两者主要用来规范提交过程。前者通过交互式的方式帮助你自觉填写 commit message,后者则利用 git hook 检查提交信息,对于不合规范的提交行为进行阻止。这次尝试没有仔细研究,下次再玩。

参考资料