问题提出
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
1yarn add -D @babel/core @babel/cli @babel/preset-env @babel/preset-react这一系列包的用处博文解释如下:
babel-coreis the main babel package — We need this for babel to do any transformations on our code.babel-cliallows you to compile files from the command line.preset-reactandpreset-envare both presets that transform specific flavors of code — in this case, theenvpreset allows us to transform ES6+ into more traditional javascript and thereactpreset does the same, but with JSX instead.
安装完成后,需要创建一个.babelrc的文件编辑配置,即需要编译 ES6+以及 JSX 的代码:
1{
2 "presets": ["@babel/env", "@babel/preset-react"]
3}Webpack
1yarn add -D webpack webpack-cli webpack-dev-server style-loader css-loader babel-loader现在我们的目录结构如下:
1MyDirectory
2|- dist
3|- public
4 |- index.html
5|- src
6 |- index.js
7|- webpack.config.js然后是写一个webpack.config.js的配置。根据是否是调试环境isDev来决定是否要对代码进行一个 minify。
1const path = require('path');
2const webpack = require('webpack');
3const HtmlWebpackPlugin = require('html-webpack-plugin');
4const TerserPlugin = require('terser-webpack-plugin');
5const MiniCssExtractPlugin = require('mini-css-extract-plugin');
6const isDev = process.env.NODE_ENV === 'development';
7
8module.exports = {
9 entry: './src/index.js',
10 mode: isDev ? 'development' : 'production',
11 devtool: isDev ? 'inline-source-map' : 'source-map',
12 module: {
13 rules: [
14 {
15 test: /\.(js|jsx)$/,
16 exclude: /node_modules/,
17 loader: 'babel-loader',
18 },
19 { test: /\.tsx?$/, loader: 'ts-loader' },
20 {
21 test: /\.less$/i,
22 use: [
23 isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
24 {
25 loader: 'css-loader',
26 options: {
27 modules: {
28 auto: true,
29 localIdentName: '[local]--[hash:base64:5]',
30 },
31 },
32 },
33 'less-loader',
34 ],
35 },
36 {
37 test: /\.css$/i,
38 use: [
39 isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
40 {
41 loader: 'css-loader',
42 options: {
43 modules: {
44 auto: true,
45 localIdentName: '[local]--[hash:base64:5]',
46 },
47 },
48 },
49 ],
50 },
51 {
52 test: /\.(png|jpg|jpeg|gif)$/,
53 type: 'asset',
54 },
55 {
56 test: /\.svg$/,
57 exclude: /node_modules/,
58 use: {
59 loader: 'svg-react-loader',
60 },
61 },
62 ],
63 },
64 resolve: {
65 extensions: ['*', '.ts', '.tsx', '.js', '.jsx'],
66 alias: {
67 '@': path.resolve(__dirname, 'src'),
68 },
69 },
70 output: {
71 path: path.resolve(__dirname, './dist'),
72 filename: 'static/[name].[chunkhash:8].js',
73 assetModuleFilename: isDev
74 ? 'static/[name].[hash][ext]'
75 : 'static/[hash][ext]',
76 clean: true,
77 },
78 devServer: {
79 port: 3000,
80 hot: true,
81 static: {
82 directory: path.join(__dirname, '/'),
83 },
84 historyApiFallback: true,
85 client: {
86 overlay: {
87 errors: true,
88 warnings: false,
89 },
90 },
91 },
92 plugins: [
93 new HtmlWebpackPlugin({
94 template: './public/index.html',
95 }),
96 new webpack.HotModuleReplacementPlugin(),
97 new MiniCssExtractPlugin(),
98 ],
99 optimization: {
100 minimize: !isDev,
101 minimizer: [new TerserPlugin()],
102 },
103};配置完后,需要手动设置一下package.json里的 script,指定如何运行调试和打包,例如:
1{
2 "scripts": {
3 "test": "echo \"Error: no test specified\" && exit 1",
4 "dev": "webpack serve",
5 "build": "webpack"
6 }
7}React
最后装上react和react-dom,一套最基础的工具链就完成了。
1yarn add react react-domTypescript
配置 typescript 需要首先有一个tsconfig.json文件,所以先初始化一个出来:
1tsc --init初始化后的 config 文件需要手动调整一下,例如需要 include 我们需要的 src 文件夹,exclude node_modules 避免混乱。顺便声明一个@方便引用组件。
1{
2 "compilerOptions": {
3 "target": "ES2020",
4 "jsx": "react-jsx",
5 "module": "ES6",
6 "moduleResolution": "node",
7 "paths": {
8 "@/*": ["./src/*"]
9 },
10 "sourceMap": true,
11 "allowSyntheticDefaultImports": true,
12 "esModuleInterop": true,
13 "forceConsistentCasingInFileNames": true,
14 "strict": true,
15 "alwaysStrict": true,
16 "skipLibCheck": true
17 },
18 "include": ["src", "./react-app-env.d.ts"],
19 "exclude": ["node_modules"]
20}除了 tsconfig,我们还需要一个声明 web 开发所需文件的类型声明,因此创建一个react-app-env.d.ts进行相关声明,和 webpack config 类似,需要什么就添加什么进去,例如:
1declare module '*.less';
2
3declare module '*.svg' {
4 export default function ReactComponent(
5 props: React.SVGProps<SVGSVGElement>
6 ): React.ReactElement;
7}Eslint && Prettier
添加 Eslint 和 Prettier 是可选的,但是如果是合作开发项目并且你的队友和你的代码风格不同并且完全不在乎 coc 方面的事情,有一个 eslint 来做代码规范对我这种强迫症而言就很重要(。
现在快速配置一个 eslint 很方便,只需要在yarn add -D eslint后进行一次 init 即可,可以自行选择想要的风格:
1palemoons@MacBookPro ~/myDir $ npx eslint --init
2You can also run this command directly using 'npm init @eslint/config'.
3✔ How would you like to use ESLint? · style
4✔ What type of modules does your project use? · esm
5✔ Which framework does your project use? · react
6✔ Does your project use TypeScript? · No / Yes
7✔ Where does your code run? · browser
8✔ How would you like to define a style for your project? · prompt
9✔ What format do you want your config file to be in? · JSON
10✔ What style of indentation do you use? · space
11✔ What quotes do you use for strings? · single
12✔ What line endings do you use? · unix
13✔ Do you require semicolons? · No / Yes
14The config that you've selected requires the following dependencies:
15
16eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
17✔ Would you like to install them now? · No / Yes
18✔ Which package manager do you want to use? · yarn
19Installing eslint-plugin-react@latest, @typescript-eslint/eslint-plugin@latest, @typescript-eslint/parser@latest
20yarn add v1.22.17
21...
22$ husky install
23husky - Git hooks installed
24✨ Done in 14.09s.
25Successfully created .eslintrc.json file in /myDir这样一个最简单的 eslint 配置就 OK 了。最后是添加 prettier 以及相关插件,避免它和 eslint 的冲突。
1yarn 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 "plugins": ["prettier"],
3 "rules": {
4 "prettier/prettier": "error"
5 },
6 "extends": ["plugin:prettier/recommended"]
7}这样,我们就把 eslint 有关 code formatting 的部分全部关掉,并把这部分交给 prettier 处理。
commitizen + husky
两者主要用来规范提交过程。前者通过交互式的方式帮助你自觉填写 commit message,后者则利用 git hook 检查提交信息,对于不合规范的提交行为进行阻止。这次尝试没有仔细研究,下次再玩。