This article describes how to build the background project framework template in React from zero, which is convenient for the rapid development of specific projects. Including Webpack5.0 configuration and packaging optimization, React family bucket use (React + react-router + Axios + Mobx + Antd), ESLint and other project development specifications.

————2020-12-10 Update record ————–

Github source updated with v2.0.0:

  • The upgrade project depends on all library versions, including: React17, ANTD4, WebPack5…
  • Add the commitlint specification to git commit.
  • – Added husky + Lint-Passage + Prettier to commit validation
  • – Optimized esLint + prettier project code style
  • .

To see the pre-article code, switch to V1.0.0

————2020-12-10 Update record ————–

The technology stacks involved use the latest version of the syntax:

  • Build the project with Webpack5.0 (nocreate-react-app,umiSuch as scaffolding);
  • Use Babel7 configuration to convert ES6, React, Mobx syntax, etc.
  • React version V17.0.1, all project components are developed using functional Hooks features.
  • Use the React-Router5 tool to configure project routes.
  • Mobx5 + Hooks were used to realize project data state management.
  • Encapsulate the Axios library to interact with background HTTP requests;
  • The UI library uses the popular Ant-Design 4.0 component library;
  • Complete project implementation and module structure separation;

Screenshot of the project page:

preface

Generally React development can be created using the Create-React-app provided by Facebook. The Create-React-app is simple enough to use and is perfect for learning react. But strictly speaking, if you want to develop a larger app that requires a much more detailed and flexible configuration, creating a Create-React-app is not the way to go, and companies with a point of scale will consider building their own company-level scaffolding tools and framing templates. The foundation is built from zero refinement based on WebPack.

Ant Financial Umi(a pluggable enterprise React application framework) can also be used for enterprise React development. It can use the related whole family buckets for continuous and fast development. The advantage is that it is produced by Dafang and has experienced the test of many large projects, so the stability and maintainability are greatly guaranteed. However, it has increased a lot of learning costs, and the granularity of the project is not high and relatively limited. Umi, [Ant Design Pro](https://pro.ant.design/) and many other valuable references can be obtained in the construction of a full set of corporate project architecture.

This project builds React application template from zero, which is convenient for me to quickly build practical applications. Second, the focus is to comb the latest knowledge of each technology stack. I hope it helps the people who see it.

Project description

This project is a background project framework template in React, which is convenient for rapid development of specific projects. Including Webpack4.0 configuration and packaging optimization, React family bucket (React + react-router + Axios + Mobx + Antd), ESLint and other project development specifications.

Git address: github.com/now1then/re… ; Article links – language finch: www.yuque.com/nowthen/lon… ; Online demo address:

Directory structure:

│ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises Webpack. Prod. Js / / webpack production environment configuration ├ ─ ─ dist / / packaging output directory ├ ─ ─ public / / project public directory ├ ─ ─ the SRC/directory/SRC development │ ├ ─ ─ assets/resources/static │ ├ ─ ─ │ ├── modules │ ├── pages │ ├── pages │ ├── Routers // Layout components │ ├─ ├── Modules │ ├─ pages │ ├── Routers // Layout components │ ├─ ├── Modules │ ├─ pages │ ├── Routers // Layout components │ ├─ Exercises │ ├── utils │ ├─ index. HTML │ ├─ utils │ ├─ index. HTML │ ├─ utils │ ├─ index │ ├─.pdf /.pdf │ ├─.pdf /.pdf │ ├─.pdf . Gitignore / / git ignore configuration ├ ─ ─ the postcssrc. Js/configuration/postcss ├ ─ ─ package. The json / / rely on package configuration └ ─ ─ the README. Md / / projectCopy the code

The project build

In this document, the Yarn management installation package is used. If Yarn is not installed, replace it with the Npm command.

Initialize the project

Initialization package. Json

yarn init
Copy the code

Install webpack

yarn add -D webpack webpack-cli webpack-merge
Copy the code

The Webpack version used in the project is ^5.10.0. The Webpack4.0 package build has a lot of default optimizations, and many configuration items do not need to be configured or changed. For example, to accelerate packaging for development mode, merge chunks; Code compression for production mode, reduce packaging volume, etc.

// Partial default configuration
optimization: {
    removeAvailableModules: true.// Delete the resolved chunk (default true)
    removeEmptyChunks: true.// Delete empty chunks (default true)
    mergeDuplicateChunks: true // Merge duplicate chunks (default true)
  }
  
 // Default configuration for production environment
  optimization: {
    sideEffects:true.// Work with tree shaking
    splitChunks: {... },/ / unpacking
    namedModules: false.// namedChunks:false Specifies that the chunk name is not enabled
    minimize: true.// Code compression
  }
Copy the code

It is necessary to distinguish webPack configurations by development/production environment to speed up the packaging of the development environment, and sometimes if the development environment is too slow to package, you can check whether the configuration is wrong (for example, the development environment turned on code compression).

Cooperation in projectwebpack-mergeSplit configuration according to development/production environment:

Webpack4.0 has been released for a long time, and I believe that the project has basically migrated to 4.0, so I won’t go into much detail here.

Configuring Html Templates

Installation:

yarn add -D html-webpack-plugin
Copy the code

Configuration:

const srcDir = path.join(__dirname, ".. /src");
plugins: [
  new HtmlWebpackPlugin({
    template: `${srcDir}/index.html`})]Copy the code

Configure local services and hot updates

Installation:

yarn add -D webpack-dev-server clean-webpack-plugin
Copy the code

The development environment uses Webpack-dev-server to build a local Web server and enable module hot update (HMR). To facilitate development and debugging, forward proxy requests (in this case with AXIOS encapsulating the forward interface to an easy-Mock online platform)

Configuration:

mode: "development".// Development mode
devServer: { // Local service configuration
  port: 9000.hot: true.open: false.historyApiFallback: true.compress: true.proxy: { / / agent
    "/testapi": {
      target:
      "https://www.easy-mock.com/mock/5dff0acd5b188e66c6e07329/react-template".changeOrigin: true.secure: false.pathRewrite: { "^/testapi": ""}}}},plugins: [
  new webpack.NamedModulesPlugin(),
  new webpack.HotModuleReplacementPlugin()
],
Copy the code

Configure the Babel

Installation:

yarn add -D babel-loader @babel/core @babel/plugin-transform-runtime 
	@babel/preset-env @babel/preset-react  babel-plugin-import
	@babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
Copy the code

The Babel configuration in Webpack is an important part. It is related to the normal operation of ES6 grammar, React JSX, Mobx and other grammars after packaging. Among them:

  • @babel/preset-reactConvert React JSX syntax;
  • @babel/plugin-proposal-class-propertiesTransform Class syntax;
  • @babel/plugin-proposal-decoratorsConverting more advanced syntax such as Mobx;
  • babel-plugin-importImplement React component loading on demand;

It’s important to note how Babel7.0 differs from Babel6.0.

Configuration:

module: {
  rules: [{test: /\.(js|jsx)$/,
      include: [srcDir],
      use: ["babel-loader? cacheDirectory=true"]},]}Copy the code

.babelrcConfiguration file
{
  "presets": [
    "@babel/preset-env"."@babel/preset-react"]."plugins": [
    "@babel/transform-runtime"["@babel/plugin-proposal-decorators",
      {
        "legacy": true}], ["@babel/plugin-proposal-class-properties", { "loose": true }],
    [
      "import",
      {
        "libraryName": "antd"."libraryDirectory": "es"."style": "css" // 'style: true' loads the less file}}]]Copy the code

Handle resources such as Less styles and images

Installation:

yarn add -D less less-loader style-loader css-loader url-loader 
	mini-css-extract-plugin postcss-loader autoprefixer
Copy the code

Among them:

  • Less-loader, style-loader, and CSS-LoaderLoad less and CSS files.
  • Postcss - loader, autoprefixerHandle CSS style browser prefix compatibility;
  • url-loaderProcessing of images, font files and other resources;
  • mini-css-extract-pluginSeparate CSS into separate files;

Configuration:

const MiniCssExtractPlugin = require("mini-css-extract-plugin"); .module: {
  rules: [{test: /\.less$/,
      use: [
        devMode ? "style-loader" : MiniCssExtractPlugin.loader,
        "css-loader"."postcss-loader"."less-loader"] {},test: /\.css$/,
      use: [
        devMode ? "style-loader" : MiniCssExtractPlugin.loader,
        "css-loader"."postcss-loader"] {},test: /\.(png|jpe? g|gif|svg)(\? . *)? $/,
      use: ["url-loader"].include: [srcDir]
    },
    {
      test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/,
      use: ["url-loader"].include: [srcDir]
    },
    {
      test: /\.(woff2? |eot|ttf|otf)(\? . *)? $/,
      use: ["url-loader"].include: [srcDir]
    }
  ]
},
plugins: [
  new MiniCssExtractPlugin({
    filename: "[name].[contenthash:8].css".chunkFilename: "chunk/[id].[contenthash:8].css"}),].Copy the code

Configure the postcss.postcssrc. js file

// .postcssrc.js
module.exports = {
  plugins: {
    autoprefixer: {}}};// Configure compatible browsers in package.json
"browserslist": [
  "1%" >."last 2 versions"."not ie <= 10"
]
Copy the code

Multithreaded packaging with Happypack

Installation:

yarn add -D happypack
Copy the code

Configuration:

const os = require("os");
const HappyPack = require("happypack");
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module: {
  rules: [{test: /\.(js|jsx)$/,
      include: [srcDir],
      exclude: /(node_modules|bower_components)/,
      use: ["happypack/loader? id=happybabel"]},]},plugins: [
  // Start the happypack thread pool
  new HappyPack({
    id: "happybabel".loaders: ["babel-loader? cacheDirectory=true"].threadPool: happyThreadPool,
    cache: true.verbose: true}),]Copy the code

The production environment splits modules

The modules are split according to the actual project situation and asynchronously loaded to prevent a single file from being too large.

  optimization: {
    runtimeChunk: {
      name: "manifest"
    },
    splitChunks: {
      chunks: "all".// By default, this parameter applies only to asynchronous modules. 'all' applies to all modules, and 'initial' applies to synchronous modules
      cacheGroups: {
        dll: {
          test: /[\\/]node_modules[\\/](react|react-dom|react-dom-router|babel-polyfill|mobx|mobx-react|mobx-react-dom|antd|@ant-design) /,
          minChunks: 1.priority: 2.name: "dll"
        },
        codeMirror: {
          test: /[\\/]node_modules[\\/](react-codemirror|codemirror)/,
          minChunks: 1.priority: 2.name: "codemirror"
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          minChunks: 1.priority: 1.name: "vendors"}}}}Copy the code

Other configuration

Introduction of ESLint and Prettier to standardize team project code development and unify code styles.

yarn add -D prettier babel-eslint eslint eslint-loader eslint-config-airbnb 
eslint-config-prettier eslint-plugin-babel eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
Copy the code

(Details to be added)

For details, see the project code under **/build directory **.

npm scripts

Package. The json file

{..."scripts": {
    "start": "webpack-dev-server --color --inline --progress --config build/webpack.dev.js".//
    "build": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js"."build:report": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js"."build:watch": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js"},... }Copy the code

Command line run:

// Run the development environment; Yarn start // Compress the production environment; Yarn build // Analyze package file size graphically; Yarn build:report // This section describes how to check the error information in the production environmentsourceThe map); yarn build:watchCopy the code

Build :report; build:watch; build:report;

// Easy to check the production environment after the package file error message (file source map)
if (process.env.npm_lifecycle_event == "build:watch") {
  config = merge(config, {
    devtool: "cheap-source-map"
  });
}
// Analyze the size of the package file graphically
if (process.env.npm_lifecycle_event === "build:report") {
  const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
    .BundleAnalyzerPlugin;
  config.plugins.push(new BundleAnalyzerPlugin());
}
Copy the code

Project Code Architecture

Install the dependencies for actual development:

yarn add react react-dom react-router-dom mobx mobx-react mobx-react-router 
	axios antd moment
Copy the code

The first decision we have to make before we write the actual code is, how do we build the directory structure? Where do you want to put these components?

The directory structure

According to personal habits and experience, the project catalog is built as shown in the figure below:

│ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises │ ├─ exercises Webpack. Prod. Js / / webpack production environment configuration ├ ─ ─ dist / / packaging output directory ├ ─ ─ public / / project public directory ├ ─ ─ the SRC/directory/SRC development │ ├ ─ ─ assets/resources/static │ ├ ─ ─ │ ├── modules │ ├── pages │ ├── pages │ ├── Routers // Layout components │ ├─ ├── Modules │ ├─ pages │ ├── Routers // Layout components │ ├─ ├── Modules │ ├─ pages │ ├── Routers // Layout components │ ├─ Exercises │ ├── utils │ ├─ index. HTML │ ├─ utils │ ├─ index. HTML │ ├─ utils │ ├─ index │ ├─.pdf /.pdf │ ├─.pdf /.pdf │ ├─.pdf . Gitignore / / git ignore configuration ├ ─ ─ the postcssrc. Js/configuration/postcss ├ ─ ─ package. The json / / rely on package configuration └ ─ ─ the README. Md / / projectCopy the code

Page module directory structure, such as FormDemo page structure:

│ ├── form.js // Form.js // Form.js // Form.js // Form.js // Form.js // Form.js // Form.js // Form.js // Form.js // Form.js // Form.js Store.js │ ├ ─ style.pdfCopy the code

Functional Hooks

Hooks are new in React 16.8. It lets you use state and other React features without having to write a class.

Because the current React version has been updated to 16.12, Hooks should fully become mainstream in React usage. Hooks will be fully embraced in this project, and classes will generally not be used to implement components. ** The following is a partial implementation (you can ignore the use of mobx) :

import React, { useState, useEffect, useContext } from 'react';
import { observer } from 'mobx-react';
import { Button } from 'antd';
import Store from './store';

import './style.less';

const HomePage = () = > {
  // useContext subscribs to mobx data
  const pageStore = useContext(Store);
  // useState State Indicates the status
  const [num, setNum] = useState(0);
  // useEffect Side effect
  useEffect(() = >{ pageStore.qryTableDate(); } []);return (
    <div className="page-home page-content">
      <h2>{pageStore.pageTitle}</h2>
      <div>
        <span>Num value: {num}</span>
        <Button type="primary" size="small" style={{ marginLeft: 10 }}
          onClick={()= > setNum(num + 1)}
        >+1</Button>
      </div>
    </div>
  );
};

export default observer(HomePage); 
Copy the code

Router Route Configuration

The project is a single page application, and the routing configuration is generally divided into compact dynamic routing and centralized configuration routing. In the React world, the mature react-Router tool manages page routing directly. When we talk about react-router, we’re basically talking about versions after version 4 of the React-router. The current version has been updated to 5.1.x. Currently, React-Router supports dynamic routing. It uses the React component to implement routing completely. In the rendering process, routing rules are set dynamically, and corresponding page components are loaded by matching the matching rules.

In this project, centralized configuration routes are adopted (for the convenience of route authentication, obtaining menu route configuration from the server interface, etc.), and the side menu bar is conveniently set. Of course, for simplicity, the project reads the configuration of the local static menu and does not import the route authentication.

Configuring static Routessrc/routes/config.js:

import React, { lazy } from "react";
import BasicLayout from "@/layouts/BasicLayout";
import BlankLayout from "@/layouts/BlankLayout";

const config = [
  {
    path: "/".component: BlankLayout, // Blank page layout
    childRoutes: [ // Submenu routing
      { 
        path: "/login".// Route path
        name: "Login page".// The name of the menu is not displayed in the menu bar.
        icon: "setting".// The menu icon
        component: lazy(() = > import("@/pages/Login")) // Load the routing component lazily
      },
      // if you don't have a menu navigation bar or a BasicLayout, put it before the BasicLayout.
      {
        path: "/".component: BasicLayout, // The basic layout framework
        childRoutes: [{path: "/welcome".name: "Welcome Page".icon: "smile".component: lazy(() = > import("@/pages/Welcome"))}, {.../ * * /}, 
          { path: "/".exact: true.redirect: "/welcome" },
          { path: "*".exact: true.redirect: "/exception/404"}]}]}];export default config;
Copy the code

This is part of the static route configuration. Note:

will be wrapped with

, which will match the first one that is matched. “/login” and other pages that do not have a menu navigation bar or a BasicLayout should be placed before the BasicLayout.

using<Suspense>andReact.lazy()Implement lazy page component loading.

Routing Component Renderingsrc/routes/AppRouter.js:

import React, { lazy, Suspense } from "react";
import LoadingPage from "@/components/LoadingPage";
import {
  HashRouter as Router,
  Route,
  Switch,
  Redirect
} from "react-router-dom";
import config from "./config";

const renderRoutes = routes= > {
  if (!Array.isArray(routes)) {
    return null;
  }

  return (
    <Switch>
      {routes.map((route, index) => {
        if (route.redirect) {
          return (
            <Redirect
              key={route.path || index}
              exact={route.exact}
              strict={route.strict}
              from={route.path}
              to={route.redirect}
            />
          );
        }

        return (
          <Route
            key={route.path || index}
            path={route.path}
            exact={route.exact}
            strict={route.strict}
            render={()= > {
              const renderChildRoutes = renderRoutes(route.childRoutes);
              if (route.component) {
                return (
                  <Suspense fallback={<LoadingPage />} ><route.component route={route}>
                      {renderChildRoutes}
                    </route.component>
                  </Suspense>
                );
              }
              return renderChildRoutes;
            }}
          />
        );
      })}
    </Switch>
  );
};

const AppRouter = () = > {
  return <Router>{renderRoutes(config)}</Router>;
};

export default AppRouter;
Copy the code

Route hooks syntax

React-router-dom also supports hooks syntax. To get routing information or route hops, you can use the new hooks function:

  • [useHistory](https://reacttraining.com/react-router/core/api/Hooks/usehistory): Obtain historical routes, roll back, and hop operations.
  • UseLocation: displays the current route information.
  • [useParams](https://reacttraining.com/react-router/core/api/Hooks/useparams): Reads the params parameter information attached to the route.
  • [useRouteMatch](https://reacttraining.com/react-router/core/api/Hooks/useroutematch): matches the current route.

Any subcomponent wrapped in the hook function can obtain routing information from these hook functions.

Code demo:

import { useHistory } from "react-router-dom";

function HomeButton() {
  const history = useHistory();

  function onClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={onClick}>Jump the Home page</button>
  );
}
Copy the code

Manage data status in conjunction with MOBX

Whether or not to use state management tools in the project depends on the actual project conditions. This project uses Mobx which I am familiar with. Mobx is a powerful and easy state management tool.

In order to use concise and convenient management, in the organization, divided into global public data state and page data state. Public data status is stored in the/SRC /stores directory; The page data is stored in the corresponding page directory.

In the implementation, mobx + useContext Hook feature is used to realize the state management of functional components. Use React’s createdContext to build a context containing Mobx. Use useContext Hook to subscribe to Mobx data changes in functional components.

Page level store.js code:

import { createContext } from "react";
import { observable, action, computed } from "mobx";
import request from "@/services/newRequest";

class HomeStore {
  @observable tableData = [];
  @observable pageTitle = "Home page";
  @observable loading = false;

  @action.bound setData(data = {}) {
    Object.entries(data).forEach(item= > {
      this[item[0]] = item[1];
    });
  }

  // Table data
  @action.bound
  async qryTableDate(page = 1, size = 10) {
    this.loading = true;
    const res = await request({
      url: "/list".method: "post".data: { page, size }
    });

    if (res.success) {
      const resData = res.data || {};
      console.log(resData);
    }
    this.loading = false; }}export default createContext(new HomeStore());
Copy the code

Page component code:

import React, { useContext } from "react";
import { observer } from "mobx-react";
import Store from "./store";

import "./style.less";

const HomePage = () = > {
  const pageStore = useContext(Store);

  return (
    <div className="page-home page-content">The home page<h2>{pageStore.pageTitle}</h2>
    </div>
  );
};

export default observer(HomePage);
Copy the code

The above is part of the demo code, the specific business implementation can view the project code.

Axios Http request encapsulation

Axios request encapsulation, specific code/SRC/services/newRequest js thinking see himself before another article (ignore the external components) : “a long way – Axios encapsulation”

UI components and page layout

The UI components use the excellent Ant Design component library, noting the use of the Babel-plugin-import configuration for the on-demand loading of the components.

The internal page layout of this project usesAntdOn the classic layout:

The page layout needs to be properly split up into modules, and the left menu navigation bar is rendered according to the static menu. See the project for the actual complete code. The following is the BasicLayout component:

import React from "react";
import { Layout } from "antd";
import SiderMenu from ".. /SiderMenu";
import MainHeader from ".. /MainHeader";
import MainFooter from ".. /MainFooter";

import "./style.less";

const BasicLayout = ({ route, children }) = > {
  return (
    <Layout className="main-layout">{/* Left menu navigation */}<SiderMenu routes={route.childRoutes} /> 
      <Layout className="main-layout-right">{/* Top display layout */}<MainHeader></MainHeader>
        <Layout.Content className="main-layout-content">{/* Actual page layout */} {children} {/*<MainFooter></MainFooter>* /}</Layout.Content>
      </Layout>
    </Layout>
  );
};

export default BasicLayout;
Copy the code

The BasicLayout, which does not need to be placed on top of the login page, needs to be handled separately (the menu configuration precedes the BasicLayout configuration).

Items to be improved:

  • perfectESLint+ prettierStandardize the team code style;
  • Importing TypeScript and configuration;
  • Extracting common modules and components according to actual scenarios;

The last

Git address: github.com/now1then/re… ; Article links – language finch: www.yuque.com/nowthen/lon… ; Online demo address:

————2020-12-10 Update record ————–

Github source updated with v2.0.0:

  • The upgrade project depends on all library versions, including: React17, ANTD4, WebPack5…
  • Add the commitlint specification to git commit.
  • – Added husky + Lint-Passage + Prettier to commit validation
  • – Optimized esLint + prettier project code style
  • .

To see the pre-article code, switch to V1.0.0

————2020-12-10 Update record ————–

Writing is not easy, feel good or helpful shoes, welcome attention, more star; (-) -)