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
- mkdir react-pro && cd react-pro
- npm init
- 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: |
- Deprecated year preset
Replace all previous babel-prese-es20xxpreset, @babel/preset-env
- Rename: Scoped Packages (@babel/x)
Solution: naming difficulty; Whether it is occupied by others; Distinguish between official package names
- 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
- @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
- @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
- Installing dependency packages
npm 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
- 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
- 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
- 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
- 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.
- 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
- 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
- Create store file
import reducers from './reducers/index'
export default createStore(reducers);
Copy the code
- Create the Reducers file
export default (state=[],action)=>{ switch (action.type){ case 'RECEIVE_PRODUCTS': return action.products; default: return state; }}Copy the code
- 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
- The installation
npm install eslint --save-dev
- Generated. Eslintrc. Js
eslint --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
- 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 src
You’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
- The installation
npm i -D stylelint stylelint-config-standard stylelint-scss
Copy the code
- 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.
- 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