Writing in the front

This article focuses on building a React enterprise project step by step from NPM Init.

  • Webpack configuration and optimization
  • Configure and use the React -router
  • Basic configuration and use of React-redux
  • Project specification and style configuration

First, the project address react-base-project

0. Basic files

  1. mkdir react-pro && cd react-pro
  2. npm init
  3. New gitignore.
.DS_Store
.vscode
node_modules/
dist/
npm-debug.log
yarn.lock
package-lock.json
Copy the code

1. Install dependencies

1. Webpack4 dependencies

npm i webpack webpack-cli webpack-dev-server --save-dev
Copy the code

2. babel7

npm i @babel/core @babel/preset-env @babel/preset-react babel-loader @babel/plugin-proposal-class-properties -D
Copy the code

Presets use the set of plug-ins that Babel needs to install (i.e. which syntaxes are supported to convert to ES5)

presets describe
babel Javascript syntax compiler
@babel/core Call Babel API transcoding core library
@babel/preset-env Compile the code according to the runtime environment
@babel/preset-react Compile react syntax
@babel/plugin-proposal-class-properties Support for class syntax plug-ins
babel-preset-stage-x(stage-0/1/2/3/4) There are five stages of the proposal, with 0 being just an idea and 4 being completed
Babel7 changes since release:
  1. Deprecated year preset

Replace all previous babel-prese-es20xxpreset, @babel/preset-env

  1. Rename: Scoped Packages (@babel/x)

Solution: naming difficulty; Whether it is occupied by others; Distinguish between official package names

  1. Rename: -proposal-

Any proposals will be marked with the -proposal-name as they are not yet in JavaScript official.

So @babel/plugin-transform-class-properties becomes @babel/plugin-proposal-class-properties, which will be named back when it enters Stage 4.

Babel compiles almost all the latest JavaScript syntax (arrow functions, const, object destructions, for example), but not for APIs. For example, new objects such as Promise, Set and Map, and static methods such as Object.assign and Object.entries. At this point, polyfill is needed to solve the problem.

In order to achieve the purpose of using these new apis, the community has two schools of implementation: babel-Polyfill and babel-Runtime + babel-plugin-transform-Runtime

  1. @babel/ Polyfill deals with APIs compatibility issues; By modifying global variables.

Such as [1, 2, 3]. Includes (1)

var includes = require('xxx/includes'); // By modifying the Array prototype object; One problem with this is that it can cause conflicts if third-party libraries also modify the prototype methodCopy the code
npm install --save core-js@3 @babel/polyfill
Copy the code
"presets": [ [ "@babel/preset-env", { "modules": false, + "useBuiltIns": "Usage ", // to set entry, import @babel/polyfill manually at the top of the code; usage will analyze which items are used to automatically import corresponding polyfill + "corejs": 3}]],Copy the code

As you can see from the configuration package above, there is some same auxiliary code in each module package:

@babel/ Runtime solves the problem of code reuse

  1. @babel/plugin-transform-runtime

The @babel/ plugin-transform-Runtime plugin is used to automatically add appropriate polyfills to resolve compatibility issues when new types such as Promise and Map are found in source code. It also solved the problem of polluting global variables and reusing auxiliary code using @babel/polyfill. Two dependency packages @babel/ Runtime-corejs3 are needed: load the necessary new API; Babel/Runtime: Provide a helper program.

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime @babel/runtime-corejs3
Copy the code
"plugins": [
	[
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ]
 ]
Copy the code

3. React and development environment hot updates

npm i react react-dom --save
Copy the code

React -dom: v0.14+ removed from react core library; Responsible for browser and DOM operations. There is also a sibling library, React-Native, for writing native apps.

React-dom includes the following methods:

presets describe
render Render the React component into the DOM
hydrate Server render to avoid white screen
unmountComponentAtNode Remove the loaded React component from the DOM
findDOMNode Access the native browser DOM
createPortal Render the React child element into the specified DOM

React: The react core library; React.PropTypes react. PropTypes react. Children

4. Generate HTML files and update the development environment

npm i html-webpack-plugin -D
npm i react-hot-loader -S
Copy the code

2. Basic engineering files and configurations

  • Create a config directory in the root directory and create the webpack.config.js base configuration file
const paths = require('./paths'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = function(webpackEnv){ const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; let entry = [paths.appIndex]; return { mode:isEnvProduction ? 'production' : isEnvDevelopment && 'development', entry:entry, output:{ path:paths.appDist, publicPath:'/', filename:`static/js/[name]${isEnvProduction ? '.[contenthash:8]':''}.js` }, module:{ rules:[ { test: /\.jsx?$/, loader: 'babel-loader' } ] }, plugins:[ new HtmlWebpackPlugin({ filename: 'index.html', template: paths.appHtml, favicon: 'the favicon. Ico'}), isEnvDevelopment && new webpack. HotModuleReplacementPlugin () / / open the HRM], devServer: {publicPath: '/', host: '0.0.0.0', disableHostCheck: true, compress: true, port: 9001, historyApiFallback: true, open: true, hot:true, } } }Copy the code
  • The configuration file used to launch the development environment
const configFactory = require('./webpack.config');
const config = configFactory('development');

module.exports = config;
Copy the code
  • To centrally manage paths, create the paths.js file
const path = require('path'); const fs = require('fs'); const appDirectory = fs.realpathSync(process.cwd()); const resolveApp = relativePath => path.resolve(appDirectory, relativePath); Module.exports = {appIndex:resolveApp(' SRC '), // appSrc:resolveApp(' SRC '), ResolveApp ('dist'), resolveApp('index.html'),Copy the code
  • Create a SRC directory under the root directory and create the project entry file index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Copy the code
  • Create the project root component app.js
import { hot } from 'react-hot-loader/root';
import React, { Component } from 'react';

class App extends Component {
  render() {
    return <h1>hello-react</h1>;
  }
}
export default hot(App);
Copy the code
  • Create.babelrc in the root directory
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "modules": false
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": ["react-hot-loader/babel","@babel/plugin-proposal-class-properties"]
}
Copy the code

By default, webpack-dev-server will enable the livereload function (also known as hot update) to monitor for changes in files and automatically refresh the page. However, the React component needs to be modified without refreshing the page. The react-hot-loader is also required (for details, see the official documents). This is commonly known as hot Module Replacement.

Json file scripts add the script command webpack-dev-server –config config/start.js and run it; The browser will open the web page http://0.0.0.0:9001/ the first stage directory structure is as follows:

. ├ ─ ─ the README. Md ├ ─ ─ the config │ ├ ─ ─ build. Js │ ├ ─ ─ paths. Js │ ├ ─ ─ start. Js │ └ ─ ─ webpack. Config. Js ├ ─ ─ the favicon. Ico ├ ─ ─ Index.html ├ ─ ─ package - lock. Json ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ App. Js │ └ ─ ─ index. The js └ ─ ─ yarn. The lockCopy the code

2. Configure resource files

The actual development of the project also requires a style file, this project takes SCSS as an example to configure

2.1 The first is the configuration of the style file

  1. Installing dependency packagesnpm i style-loader css-loader postcss-loader sass-loader node-sass autoprefixer -D
presets describe
style-loader Insert the CSS file into the HTML
css-loader Compiling CSS files
sass-loader Compile the SCSS file
postcss-loader A tool to transform CSS using javascript plug-ins
autoprefixer Resolve the CSS and add Vendor Prefixes based on the user’s usage scenario
2. Add a Loader to WebPack
` ` `
{
test: /.(sc c)ss$/,
use: [
  isEnvDevelopment && 'style-loader',
  'css-loader', 'postcss-loader', 'sass-loader'
].filter(Boolean)
Copy the code

}

3. Create the postcss.config.js fileCopy the code

const autoprefixer = require(‘autoprefixer’); module.exports = { plugins: [autoprefixer] };

4. Package. json Adds a list of compatible browsersCopy the code

“browserslist”: [“iOS >= 8″,”> 1%”,”Android > 4″,”last 5 versions”]

Import global SCSS file 'import './assets/styles/app. SCSS '; '6. Restart the project and you can see that the style has been inserted into the head! [](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/7/171ef7bfb07c13e5~tplv-t2oaga2asx-image.image) # # 3. Static file handling ` ` ` NPM I file - loader url - loader - D ` ` ` | presets description | | | : -- -- -- -- -- -- -- -- -- - | : - | | file - loader | Solve the relative path problem of referencing local resources (pictures, fonts, audio and video, etc.) in the project; Here I tested, the absolute path will not occur path problem | | url - loader | turn dataURI processing | for resource filesCopy the code

{ test: /.(gif|png|jpe? g|svg)(? . *)? /, use: [ { loader: ‘url-loader’, options: { limit: 10000, name: ‘img/[name].[ext]?[hash]’ } } ] }, { test: /\.(woff2? |eot|ttf|otf)(\? . *)? /, loader: ‘url-loader’, options: { limit: 10000, name: ‘fonts/[name].[hash:7].[ext]’ } }

{react-router-dom}} {react-router-dom} {react-router-dom}} In react-Router4.0.0 + version; A react-router-dom wrapper is available for use in browsers. React-router-native is used to develop react-native applications.Copy the code

npm install react-router-dom –save

React Router (BrowserRouter, HashRouter, etc.) For web applications based on React Router, the root component should be a Router component. React-router-dom provides two routing modes; '<BrowserRouter>' : dynamically display the component '<HashRouter>' using the HISTORY API provided by HTML5 (pushState, replaceState, and popState events) : By listening for window.location.hash changes, the most intuitive experience for the dynamic presentation component is that BrowserRouter does not append # to the browser URL, which is of course preferred for elegance of address, but for static servers, the alternative HashRouter is used. Route matching is done by matching the path attribute of the <Route> component with the pathname of the current location. When a <Route> component matches, it is displayed; Otherwise render null. If a <Route> has no path attribute, its component content will always be renderedCopy the code

// When location = {pathName: ‘/about’} // path matches successfully, render component // path does not match, render null // This component has no path attribute, its corresponding component will always render

We can place '<Route>' components anywhere in the component tree. But it is more common to write several '<Route>' together. The '<Switch>' component can be used to 'wrap' multiple '<Route>' together. It is not mandatory to use a '<Switch>' component when multiple components are used together, but it is very convenient to use a '<Switch>' component. '<Switch>' iterates through all the '<Route>' subcomponents below it and renders only the '<Route>' that matches the first path. Here we introduce the new route component (SRC /routes/index.js) into the root component.Copy the code

Link navigation component

//to: string <Link to="/about? tab=name" /> //to: object <Link to={{ pathname: "/courses", search: "? Sort =name", hash: "#the-hash", state: {fromDashboard: true} // Pass the next page additional state argument}} />Copy the code

Use history to control route redirection

Different React versions use different methods. The following describes how to use different React versions

  1. React-Router 5.1.0+ (using hooks and React >16.8)

You can use useHistory hook to use program-time navigation in function components

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

function HomeButton() {
  let history = useHistory();
  // use history.push('/some/path') here
};
Copy the code
  1. The React – the Router 4.0.0 +

In 4.0+, use the history object in props in the component

class Example extends React.Component {
   // use `this.props.history.push('/some/path')` here
};
Copy the code
  1. The React – the Router 3.0.0 +

In 3.0+, the router in props is used in the component.

class Example extends React.Component {
   // use `this.props.router.push('/some/path')` here
};
Copy the code

5. Status management

npm install redux react-redux --save
Copy the code

Redux is a “predictable state container” that references flux’s design,

Redux’s Three principles

  1. Single data source

An application has only one data source. The advantage is that the state of the entire application is stored in one object, so that the state of the entire application can be removed at any time for persistence. Of course, if a complex project can also use the utility function combineReducers provided by Redux to split data management.

  1. The state is read-only

React does not display a defined store. Reducer is used to return the current state of the application. This does not change the previous state, but returns a new state. The createStore method provided by React generates a Reducer store. Finally, you can change the state using the Store. disputch method. 3. State modification is all completed by pure functions, which makes the state modification in Reducer simple and pure

Redux core API

At the heart of Redux is a Store generated by the createStore(Reducers [,initalState]) method provided by Redux. Reducers mandatory parameters are used to respond to actions generated by user operations. Reducer is essentially a function whose function signature is Reducer (previousState,action)=>newState. The reducer’s job is to calculate the new state based on previousState and Action. In practical applications, reducer needs a non-null judgment when it deals with previousState. It is clear that the Reducer did not have any previousState when it was first executed, and new state was returned when the Reducer responsibilities were executed, so a defined initalState needs to be returned in this special case.

With the React binding

Redux reacts-redux reacts-redux This is an architectural trend towards front-end frameworks or libraries that are as platform independent as possible. React-redux provides a component and an API. The React component accepts a store as props, which is the top component of the entire Redux application. One is Connect (), which provides the ability to retrieve store data from any component of the React application.

The project uses REdux

  1. The import file is added to the Provider component
import { Provider } from 'react-redux';
import store from './redux/index';
ReactDOM.render(<Provider store={store}><App /></Provider>, rootEl);
Copy the code
  1. Create store file
import reducers from './reducers/index'
export default createStore(reducers);
Copy the code
  1. Create the Reducers file
export default (state=[],action)=>{ switch (action.type){ case 'RECEIVE_PRODUCTS': return action.products; default: return state; }}Copy the code
  1. Dispatches in the container component trigger reducers to change state
import { connect } from 'react-redux' const ProductsContainer = ({products,getAllProducts}) => ( <button </button>) const mapStateToProps = (state) => ({products:state.products}) const mapDispatchToProps = (dispatch, ownProps)=> ({ getAllProducts:() => { dispatch({ type: 'RECEIVE_PRODUCTS', [1,2,3]}) export default connect(mapStateToProps, mapDispatchToProps)(ProductsContainer)Copy the code

6. Environment global variables

There are often global variables that differ between the test and production environments in a project; The most typical API interface domain name part, jump address domain name part; We can set DefinePlugin in the Webpack plugin:

New webpack.DefinePlugin({'process.env': env //env get local static file})Copy the code

The webpack Build package is designed to inject NODE_ENV into nodes, so process.env.node_env is the same, because NODE_ENV is injected into nodes by webpack build packages.

Cross-env is used to manually inject a marker parameter NODE_ENV_MARK into the node environment. The package code is as follows:

"scripts": {
    "dev": "cross-env NODE_ENV_MARK=dev webpack-dev-server --config config/start.js",
    "build:test": "cross-env NODE_ENV_MARK=test node config/build.js",
    "build:prod": "cross-env NODE_ENV_MARK=production node config/build.js"
  }
Copy the code

NODE_ENV_MARK gets the file from webpack.config.js:

const env = require(`.. /env/${process.env.NODE_ENV_MARK}.env`);Copy the code

Add dev env directory. The env. Js/test env. Js/production env. Js. The file content can be edited based on the actual situation

module.exports = {
  NODE_ENV: '"production"',
  prefix: '"//api.abc.com"'
};
Copy the code

This allows the process.env.prefix variable to be used in the browser environment.

This project configuration is basically over, the following is some optimization of the project.

Project optimization

The following are optimized configurations based on webpack4

Package speed optimization

  • Clean -webpack-plugin – Deletes the last package before each package

Add the following configuration to webpack.config.js:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
    !isEnvDevelopment && new CleanWebpackPlugin()
]
Copy the code
  • Reduce the file search scope
{ test: /\.jsx? $/, loader: 'babel-loader', include: paths.appSrc, exclude: /node_modules/ }Copy the code
  • Cache the loader execution result
loader: 'babel-loader? cacheDirectory=true'Copy the code
  • noParse

Modules that use noParse will not be parsed by Loaders. When determining that the library has no third-party dependencies, you can use this configuration to optimize performance

noParse: /lodash/
Copy the code
  • Happypack multithreaded packaging
const os = require("os");
const HappyPack = require("happypack");
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
plugins:[
    new HappyPack({
        id: "happyBabel",
        loaders: ["babel-loader?cacheDirectory=true"],
        threadPool: happyThreadPool,
        verbose: true
     })
]
Copy the code
  • sourcemap

To facilitate debugging problems on the line, Sourcemap is a mapping file for packaged code and source code.

    devtool: isEnvDevelopment ? 'cheap-module-eval-source-map' : 'source-map',
Copy the code

Output volume optimization

  • webpack-bundle-analyzer

First of all, you can use this plug-in to analyze the size of each tripartite library module that is packaged and referenced, and make optimization plans in turn.

1. Package. json add script "analyze": "cross-env NODE_ENV_REPORT=true npm run build:prod" 2.webpackage.config.js const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin plugins:[ process.env.NODE_ENV_REPORT && new BundleAnalyzerPlugin() ] 3. The browser will automatically open http://127.0.0.1:8888Copy the code

The analysis report is as follows:

  • The mini – CSS – extract – the plugin CSS
const MiniCssExtractPlugin=require('mini-css-extract-plugin'); plugins:[ isEnvProduction && new MiniCssExtractPlugin({ filename:'static/css/[name].[contenthash:10].css' }) ] / / loader modify {test: / \ | c (sc) ss $/, use: [ - isEnvDevelopment && 'style-loader', + MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader' ].filter(Boolean) }Copy the code
  • Optimize the CSS – assets – webpack – the plugin CSS
  • Js uglifyjs webpack – plugin compression
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

optimization: {
  minimizer: [
    new UglifyJsPlugin({
    cache:true,
    parallel:true,
    sourceMap:true
  }),
  new OptimizeCSSAssetsPlugin()
],
Copy the code

Unpack the project

Loadable

Lazily load the page route before unpacking the project

- import Home from '.. /pages/home/Home'; + import Loadable from 'react-loadable'; + const loading = () => { return ( <div> loading... </div> ) } + const Home = Loadable({loader: () => import(/* webpackChunkName: "Home" */ '.. /pages/home/Home'),loading:loading});Copy the code

The import() function, which is es6 syntax, is a dynamically imported way to return a Promise

splitChunks

The unpacking of the project mainly includes the following aspects:

  • The react | react – dom | react – redux | react – the router – dom | redux, the basic framework of the project
  • The Lodash project uses a large tripartite library
  • Common components and methods in a project
SplitChunks :{cacheGroups:{DLL :{// chunks:'all', test: /[\\/]node_modules[\\/](react|react-dom|react-redux|react-router-dom|redux)[\\/]/, name: 'DLL ', priority:100, // True, / / the cache for this group to create block, tell webpack ignore minSize minChunks, maxAsyncRequests, reuseExistingChunk maxInitialRequests options: Lodash: {// big chunks:'all', test: /[\\/]node_modules[\\/](lodash)[\\/]/, name: 'lodash', priority: 90, enforce: true, reuseExistingChunk: Name: 'Commons ', minChunks: 2, // math.ceil (pages. Length /3), when you have multiple pages, get pages. Length, at least 1/3 of the pages are introduced into the common packet 'all', reuseExistingChunk: true } }, chunks: 'all', name: true, }Copy the code

The figure below is the result after unpacking

Then use speed-measure-webpack-plugin to analyze the packaging speed of each link of Webpack

  • speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); const smp = new SpeedMeasurePlugin(); Wrap = webpackConfig ({//webpack config object});Copy the code

After packaging, the picture is as follows:

Development experience optimization

  • resolve.extensions

When modules are imported without file suffixes WebPack automatically resolves the file suffixes identified based on this configuration

import Cart from '.. /components/ cart.jsx '// import Cart from '.. / / configuration/components/Cart 'resolve: {extensions: [' js',' JSX ', 'json']}Copy the code
  • resolve.alias

Easy to import files from other directories into your project

import product from '.. /.. /.. // Import product from '@redux/reducers/products' alias:{'@redux': path.appredux}Copy the code
  • Copying static Files
New CopyWebpackPlugin({patterns:[{from: paths.appstatic, to:'static/',}]})Copy the code

Project specification and style configuration

Eslint: code style and syntax rule checker

  1. The installationnpm install eslint --save-dev
  2. Generated. Eslintrc. Jseslint --init(Select according to the project)

React version not specified in eslint-plugin-react Settings React version not specified in eslint-plugin-react Settings

"settings":{
    "react": {
        "version": "detect", // React version. "detect" automatically picks the version you have installed.
    }
}
Copy the code
  1. Ignore files

Create. Eslintignore in the root directory, and the configured directories or files will not be verified in ESLint format

**/dist/**
**/node_modules/**
**/config/**
Copy the code

If you’re adding ESLint to a new project, Extends recommends using Airbnb. This will constrain you to write more elegant code, which will eventually become your coding style

npm i eslint-plugin-react eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y -D
extends: [
    'airbnb'
]
Copy the code

Extends recommends using “esLint :recommended” and” Plugin: React /recommended” if an old project is enrolled in ESLint, extends recommends using “esLint :recommended” and” Plugin: React /recommended”.

npm i eslint-plugin-react -D
extends: [
    "eslint:recommended",
    "plugin:react/recommended"
]
Copy the code

Airbnb is used in my project; Run at this timeeslint srcYou’ll find a lot of errors like thisMost of the errors are coding style errors

You can useeslint src --fix; Code style issues can be fixed automatically, in my understanding automatic fixes will not add lines or move code. After running, there are still errors like this, and the rest need to be fixed manuallyIf vscode(1.46.1) editor can add the following configuration, enable save automatic formatting

"Eslint. validate": ["javascript", "javascriptreact"], "editor.codeActionsOnSave": {// New syntax" source.fixall.eslint ": true }Copy the code

Remember the resolve. Alias configuration above?Eslint reported an error that path could not be found; Install the plug-in and add the following configuration

npm install eslint-plugin-import eslint-import-resolver-alias --save-dev
settings: {
    'import/resolver': {
      alias: {
        map: [
          ['@redux', paths.appRedux]
          ['@pages', paths.appPages]
          ['@util', paths.util]
        ],
      },
    },
}
Copy the code

You’ll find that CTRL/Command + the left mouse button doesn’t recognize the path and the development experience isn’t very good.

Create jsconfig.json in the root directory

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@redux/*": ["src/redux/*"],
      "@pages/*":["src/pages/*"],
      "@util/*":["src/util/*"]
    }
  }
}
Copy the code

Stylelint specification SASS file

  1. The installation
npm i -D stylelint stylelint-config-standard stylelint-scss
Copy the code
  1. configuration

New stylelintrc.js in the root directory

Module. exports = {extends: 'stylelint-config-standard', // This is the way processors: [], plugins: processors: ['stylelint-scss'], ignoreFiles: ['node_modules/**/*.scss'], rules: { 'rule-empty-line-before': 'never-multi-line', }, };Copy the code

Prettier

A tool for formatting code

Why does Prettier need to be introduced when ESLint was already used? As I understand it, ESLint’s job is to test code for compliance with rules, while prettier formats code to avoid such errors; Of course Prettier cannot format problems with code quality and syntax classes.

  1. The installation
npm i eslint prettier eslint-config-prettier eslint-plugin-prettier -D
Copy the code
presets describe
eslint-plugin-prettier Extend rules in ESLint Rules
eslint-config-prettier Make ESLint compatible with Prettier by turning off parts where Prettier and ESLint conflict
2. Eslint modifies configurations
` ` `
extends: [
'airbnb',
+ 'plugin:prettier/recommended'
Copy the code

]

3. Run the package.json commandCopy the code

“scripts”: { “format”: “prettier –write “src/**/*.{js,jsx}”” }

NPM run format: NPM run format: NPM run format: NPM run format: NPM run format: NPM run format: NPM run format Also need to use automated tools. The installationCopy the code

npm i -D husky

2. Add the configuration file to package.jsonCopy the code

“scripts”: { “lint”: “eslint src –ext .jsx && stylelint “./src/**/*.scss””

}, “husky”: { “hooks”: { “pre-commit”: “npm run lint” } }

Performing the COMMIT commit detects the error and prevents the code from being committed online, which can result in an online error. ! [](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/6/1732318e7380d617~tplv-t2oaga2asx-image.image) Code error repair can be submitted. What's the problem with the article welcome comments area to discuss ~ # # # https://github.com/futurewan/react-base-project.git project addressCopy the code