脚手架帮你做了什么?手动搭建 React 后台项目

有时候过于依赖脚手架、各种库,会让我们的能力陷入一个瓶颈。本教程试图从零开始,记录一个项目的形成过程。

本教程涉及什么

涉及技术

  • React

  • Tailwind + SCSS

  • Git(Github)

预备知识

最好能有所了解。

基础工作

创建 Git 仓库

名称:bloggo-admin-frontend

链接:https://github.com/pluveto/bloggo-admin-frontend

创建项目目录

1$ cd ~/proj
2
3$ mkdir bloggo-admin-frontend
4
5$ cd bloggo-admin-frontend

初始化项目

 1
 2$ npm init
 3This utility will walk you through creating a package.json file.
 4It only covers the most common items, and tries to guess sensible defaults.
 5
 6See `npm help init` for definitive documentation on these fields
 7and exactly what they do.
 8
 9Use `npm install <pkg>` afterwards to install a package and
10save it as a dependency in the package.json file.
11
12Press ^C at any time to quit.
13package name: (bloggo-admin-frontend) 
14version: (1.0.0) 
15description: Frontend project for bloggo dashboard
16entry point: (index.js) 
17test command: 
18git repository: https://github.com/pluveto/bloggo-admin-frontend
19keywords: bloggo admin frontend tailwind react
20
21license: (ISC) MIT
22About to write to /root/proj/bloggo-admin-frontend/package.json:
23
24{
25  "name": "bloggo-admin-frontend",
26  "version": "1.0.0",
27  "description": "Frontend project for bloggo dashboard",
28  "main": "index.js",
29  "scripts": {
30    "test": "echo \"Error: no test specified\" && exit 1"
31  },
32  "repository": {
33    "type": "git",
34    "url": "git+https://github.com/pluveto/bloggo-admin-frontend.git"
35  },
36  "keywords": [
37    "bloggo",
38    "admin",
39    "frontend",
40    "tailwind",
41    "react"
42  ],
43  "author": "pluveto",
44  "license": "MIT",
45  "bugs": {
46    "url": "https://github.com/pluveto/bloggo-admin-frontend/issues"
47  },
48  "homepage": "https://github.com/pluveto/bloggo-admin-frontend#readme"
49}
50
51
52Is this OK? (yes) yes

安装依赖

react

核心依赖是 react

1$ yarn add react
2$ yarn add react-dom
3$ yarn add react-router-dom
  • react-dom:提供 DOM 元素操作

  • react-router-dom:提供 DOM 的路由绑定

sass

1$ yarn add node-sass --global

sass 是一个 css 预处理器,从而能让我们用 sass 或 scss 语法编写更工程化的样式代码。

webpack

1$ yarn add webpack webpack-cli --dev
  • webpack:用于提供打包功能

  • webpack-cli:提供打包 CLI 命令

devServer

用于提供热重载

1$ yarn add webpack-dev-server --dev

wepack loader

1$ yarn add babel-loader html-loader style-loader css-loader sass-loader --dev

webpack plugin

用于生成 HTML5 文件并引入 webpack 包裹。

1$ yarn add html-webpack-plugin --dev

babel

1$ yarn add @babel/core @babel/preset-react @babel/preset-env --dev
  • babel:提供 ES6 到 ES5 的编译

  • babel-preset-react:提供 react 相关的 babel 插件

  • babel-preset-env:用于配置需要支持的平台和版本(比如 IE>=11,Chrome >64)

配置 babel

.babelrc 进行配置如下:

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

从而支持 JSX 语法

配置 webpack

webpack.config.js 配置如下:

 1const path = require("path");
 2const webpack = require("webpack");
 3const HTMLWebpackPlugin = require("html-webpack-plugin");
 4
 5module.exports = {
 6    mode: "development",
 7    entry: "./index.js",
 8    output: {
 9        filename: "bundle.js",
10        path: path.resolve("dist"),
11        publicPath: "/",
12    },
13    module: {
14        rules: [
15            {
16                test: /\.(js|jsx)$/,
17                exclude: /node_modules/,
18                use: "babel-loader"
19            },
20            {
21                test: /\.html$/,
22                use: "html-loader"
23            },{
24                test: /\.css$/,
25                use: ["style-loader", "css-loader"],
26            },
27            {
28                test: /\.scss$/,
29                use: [
30                    "style-loader",
31                    "css-loader",
32                    "sass-loader"
33                ],
34            },
35        ],
36    },
37    plugins: [
38        new HTMLWebpackPlugin({
39            template: "index.html"
40        }),
41    ]
42}

HTML 首页

index.html

 1<!DOCTYPE html>
 2<html lang="zh">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Hello, React!</title>
 8</head>
 9<body>
10    <div id="root"></div>
11</body>
12</html>

入口文件

index.js

 1import React from "react";
 2import ReactDOM from "react-dom";
 3import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
 4
 5import App from "./App.js";
 6import "./styles.scss";
 7
 8const appRouting = (
 9  <Router>
10    <Switch>
11      <Route exact path="/" component={App} />
12    </Switch>
13  </Router>
14);
15const root = document.querySelector("#root")
16ReactDOM.render(appRouting, root);

App.js

1import React from "react";
2
3const App = () => {
4   return <div className="hello">Hello, World!</div>;
5};
6
7export default App;

styles.scss

1.hello {
2    text-align: center;
3}

检查

现在的项目依赖是这样的:

 1  "dependencies": {
 2    "react": "^17.0.2",
 3    "react-dom": "^17.0.2"
 4  },
 5  "devDependencies": {
 6    "@babel/core": "^7.14.8",
 7    "@babel/preset-env": "^7.14.8",
 8    "@babel/preset-react": "^7.14.5",
 9    "babel-loader": "^8.2.2",
10    "css-loader": "^6.2.0",
11    "html-loader": "^2.1.2",
12    "html-webpack-plugin": "^5.3.2",
13    "react-router-dom": "^5.2.0",
14    "sass-loader": "^12.1.0",
15    "style-loader": "^3.2.1",
16    "webpack": "^5.47.1",
17    "webpack-cli": "^4.7.2",
18    "webpack-dev-server": "^3.11.2"
19  }

增加常用脚本

package.json 增加:

1  "scripts": {
2    "test": "echo \"Error: no test specified\" && exit 1",
3+   "start": "webpack serve"
4  },

运行

1$ yarn start

效果如下:

image-20210730135201479

推送到 Github

配置忽略规则

.gitignore

 1# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2
 3# dependencies
 4/node_modules
 5/.pnp
 6.pnp.js
 7
 8# testing
 9/coverage
10
11# production
12/build
13
14# misc
15.DS_Store
16.eslintcache
17/.env
18/.env.local
19/.env.development.local
20/.env.test.local
21/.env.production.local
22
23npm-debug.log*
24yarn-debug.log*
25yarn-error.log*
26yarn.lock

推送代码

 1$ git init
 2$ git remote add origin https://github.com/pluveto/bloggo-admin-frontend.git
 3$ git add .
 4$ git status
 5On branch master
 6
 7No commits yet
 8
 9Changes to be committed:
10  (use "git rm --cached <file>..." to unstage)
11
12        new file:   .babelrc
13        new file:   .gitignore
14        new file:   App.js
15        new file:   index.html
16        new file:   index.js
17        new file:   package.json
18        new file:   styles.scss
19        new file:   webpack.config.js
20$ git commit -m "feat: basic project"
21$ git push --set-upstream origin master

现在,基本的项目有了。但是项目结构是完全平行的:

image-20210730135931073

因此我们需要设计一下项目结构。

新建分支

我们新建一个基于 master 的分支:

1$ git branch v2

切换到新分支:

1$ git checkout v2

重设结构

我比较喜欢相同功能的放在一起,而不是分层,因为那样不好快速跳转到对应的文件。

我们增加一个 如下目录:

  • src/:存放项目特有的代码

  • public/:存放静态文件

  • helpers/:存放工具组件

App.jsindex.jsstyles.scss 移动到 src/,并更新相关依赖。

登录页面

我们首先做登录页面

src/pages/login/LoginPage.jsx

 1import React from "react";
 2
 3export default class LoginPage extends React.Component {
 4    render() {
 5        return (
 6            <div >
 7                Login Page
 8            </div>
 9        )
10    }
11}

增加路由

现在我们要登录页到路由表中。

允许 BrowserRouter

为了避免得到下面的响应:

Cannot GET /login

我们需要做如下设置

webpack.config.js

1    output: {
2        filename: "bundle.js",
3        path: path.resolve("dist"),
4        publicPath: "/", // HERE
5    },
6    devServer: {
7        historyApiFallback: true // HERE
8    },

首页

App.js 改成 HomePage.js,放到 src/pages/home:

 1import React from "react";
 2import { Link, Route, Switch } from "react-router-dom";
 3import LoginPage from "./LoginPage";
 4
 5const Home = () => {
 6   return (
 7      <div>
 8 <p>Bloggo Admin</p>
 9         <Link to="/login">登录</Link>
10      </div>
11   )
12};
13
14export default Home;

路由表

编辑 index.js

 1import React from "react";
 2import ReactDOM from "react-dom";
 3import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
 4
 5import HomePage from "./pages/login/HomePage.js";
 6import LoginPage from "./pages/login/LoginPage.js";
 7import "./styles.scss";
 8
 9const appRouting = (
10  <Router>
11    <div className="sans-serif">
12      <Route exact path="/" component={HomePage} />
13      <Route path="/login" component={LoginPage} />
14    </div>
15  </Router>
16);
17const root = document.querySelector("#root")
18ReactDOM.render(appRouting, root);

运行,并访问 `http://localhost:8080

image-20210730153324547

image-20210730153335521

两个页面均正常显示。

设计样式

增加 Tailwind 依赖

1$ yarn add tailwindcss

// TODO: 集成 PostCSS,以及按需导出

添加 Tailwind 到项目

index.js 增加:

1import "tailwindcss/tailwind.css"

这时并不会生效,你如果定位到那个文件,会看到:

tailwindcss/tailwind.css:

1@tailwind base;
2
3@tailwind components;
4
5@tailwind utilities;

它用了自己的指令,这些指令由 PostCSS 提供支持

集成 PostCSS

1$ yarn add postcss@latest autoprefixer@latest

postcss.config.js:

1module.exports = {
2  plugins: {
3    tailwindcss: {},
4    autoprefixer: {},
5  }
6}

tailwind.config.js

 1module.exports = {
 2    // 配置清除 (purge) 选项以删除任何未使用类,这样生成的文件尺寸最小
 3    purge: [
 4        './src/**/*.html',
 5        './src/**/*.js',
 6    ],
 7    darkMode: false, // or 'media' or 'class'
 8    theme: {
 9        extend: {},
10    },
11    variants: {},
12    plugins: [],
13}

安装 PostCSS 的 Webpack 插件:

1yarn add postcss-loader --dev

webpack.config.js

 1// ...
 2module.exports = {
 3    mode: "development",
 4    entry: "./src/index.js",
 5    output: {
 6        filename: "bundle.js",
 7        path: path.resolve("dist"),
 8        publicPath: "/",
 9    },
10    devServer: {
11        historyApiFallback: true
12    },
13    module: {
14        rules: [
15        	// ...
16        	{
17                test: /\.css$/,
18                use: [
19                    "style-loader",
20                    "css-loader",
21+                   "postcss-loader"
22                ],
23            },
24            {
25                test: /\.scss$/,
26                use: [
27                    "style-loader",
28                    "css-loader",
29                    "sass-loader",
30+                   "postcss-loader"
31                ],
32// ...

现在,Tailwind 可以正常使用了。

拥抱 jsx:

webpack.config.js

1// ...
2    resolve: {
3      extensions: ['', '.js', '.jsx'],
4    }
5}