This is my first nuggets blog post, the beginning of nuggets writing. Hi, everybody. I’m a new member of the “Two Smelly cats” team of the Nuggets.

Without authorization, prohibit reprinting ~

The opening

This time, we will discuss how to build a modern front-end engineering from scratch and summarize the key points. There will be a lot of tools, libraries, frameworks, and a lot of writing to do. In the article, more attention is paid to how to set up and how to operate, so the detailed configuration of some tools may not be so detailed, but also need to explore.

Suitable for some front-end entry knowledge of the people, if it is a new friend, when do not know a certain is to do what, check the information, check until you understand his role (also spend not much time) ~

Again, if you don’t know, it’s very important to look it up

The technologies used in this paper are the latest version of the current period (January 2022). If the time is long, the configuration may be different.

This article may be quite long, but also please calm down and slowly read (don’t turn over the hero), the best practice again

Thank you again

(I set the size of the image of the nuggets editor, but it doesn’t seem to work.)

Catalogue and key technologies

First of all, the whole technology to be used roughly listed, but also for friends to understand the first look:

HTML、CSS、JS、Webpack、Babel、TS、Less、Postcss、React、Redux、Axios、Eslint、Husky、Git

That’s about it

Ok, let’s get down to business: Joy:

I. Principle of project operation

First of all, we need to understand the essence of a project or a project. At present, the essential operation logic of front-end engineering remains unchanged: reading HTML and CSS files for parsing and rendering, and loading JS for execution. Therefore, the purpose of the construction project is to come up with the same old three, put in the UserAgent to run.

At present, the popular project management mode is the package management mechanism of Node. At present, there are many kinds of management mechanisms: NPM, CNPM, YARN, PNPM, etc. We will choose the most familiar NPM.

Any project starts with a new folder. First we create a new folder and initialize it as the NPM project:

mkdir demo && cd demo
npm init
Copy the code

Initialization will require you to fill in some information. Here, we will select some project properties. Check the Node documentation if you don’t understand the specific field meanings. After initialization, the project is formed, remember that we are already a project at this point. After the mkdir demo folder is created, we are a project in which we can write HTML, CSS, JS files to run. The latter is initialized in the form of NPM packages just to keep pace with modern front ends.

The whole article

Just kidding

What do you do when you initialize it as a project? Recall that in a normal project, when we run NPM run Start, a local server is started to pull up the code we wrote, and the code we wrote is hosted on an HTML template. Among them, the following work was done:

  1. nodethroughnpmrunpackage.jsonThis command is usually a scaffold custom command or a self-configured packaging tool command.
  2. When the added packaging tool or scaffold receives the command, it reads the packaging tool configuration and generates the packaging artifacts.
  3. The packaged product will be displayed as an entry in our template file, and we can preview it in the browser.
  4. Add a local server, use a local domain name to pull up our template file, give you a feeling of home, take off 🛫.

Two, practical operation run first

In the new demo folder, create a new index.html file and write something random. Install a packaging tool and configure it according to the rules of the packaging tool. We choose the commonly used packaging tool Webpack, directly open the document guide Webpack (this document is the best update relative to the official website, the translation is also relatively ok, I suggest to see this.) :

npm install webpack webpack-cli --save-dev
Copy the code

After installing Webpack, if you are not familiar with it, you can follow the official document to go through. Webpack essentially deals with the various resources used in your project, packages and compresses the artifacts and hangs them on top of HTML templates. Since we are just starting the build project, here we manually mount the artifacts to the template file first.

This /dist/main.js is what we packaged, and here is the current project directory:

A few more important files:

Note 📢 : the following part of the code, may only be posted changes.

Webpack.config.js is the configuration of our packaging tool, here we add only the most basic configuration:

// webpack.config.js
// In the latest version of Webpack you can even have default configurations without writing them
const path = require('path');
module.exports = {  
  mode: 'development'.entry: path.join(__dirname, 'src'.'index'),
  output: {
    path: path.join(__dirname, 'dist'),}};Copy the code

Index.html is our entry file, and our logic is very simple: manually mount the js file, and then click a button to trigger the method in our attached JS file:

<! DOCTYPEhtml>
<html>
  <head>    
    <meta charset="utf-8">  
    <meta http-equiv="X-UA-Compatible" content="IE=edge">  
    <title>Start a project from scratch</title>   
    <meta name="description" content="">  
    <meta name="viewport" content="width=device-width, initial-scale=1">  
    <link rel="stylesheet" href=""> 
  </head>  
  <body>    
    <script src="./dist/main.js"></script>  
    <h1>A project from scratch</h1>  
    <button onclick="change()">Click on the</button>  
    <div id="app"></div> 
  </body>
</html>
Copy the code

SRC index.js, which is equivalent to our business file, has a single function:

function say(){
  console.log("say hello")}function change(){ 
  console.log("Call change")  
  const app = document.getElementById('app') 
  app.innerHTML = "Change the App copy"
}
Copy the code

Package. json, after installing webpack, we just need to add a package command under scripts to start webpack, and the package command for Webpack is webpack:

{  
  "name": "demo"."version": "1.0.0"."description": "A sample project"."main": "index.js"."scripts": {   
    "start": "webpack" 
   }, 
  "author": "awefeng"."license": "MIT"."devDependencies": {    
    "webpack": "^ 4.29.0"."webpack-cli": "^ 3.2.1." "}}Copy the code

Configure the environment and change the entrance

This step is relatively simple but still needs to be done.

Although Webpack supports passing –env directly, we pass environment variables in scripts for environmental considerations in subsequent projects. – Added a dependency package cross-env to offset the issue of running commands on different platforms (Windows, Linux, macOS, etc.) :

npm install --save-dev cross-env
Copy the code
// package.json
"scripts": {   
  "start": "cross-env ENV=dev webpack"."build:test": "cross-env ENV=test webpack"."build": "cross-env ENV=prod webpack" 
},
Copy the code

Webpack sets mode to the environment parameter passed in by the command, and Webpack automatically sets the mode value in the configuration file to process.env.node_env.

To set the webpack entry, note that we have changed the entry name to app:

entry: { 
  app: resolve(__dirname, '.. /src'.'app'// multi: XXXX The multiple entries are similar
},
Copy the code

4. Add CSS and image processing configurations

First we add CSS preprocessor, here choose less (preferably not sASS, domestic garbage network + SASS own implementation issues + SASS version compatibility issues…) :

npm install --save-dev style-loader css-loader less-loader less
Copy the code

Css-loader: handles @import and URL () in the same way that JS interprets import/require(). By default, it generates an array of strings to store the processed style strings and export them.

Style-loader: inserts CSS into the DOM. It handles the array of modules exported by CSS-Loader, and then inserts styles into the DOM via style tags or other forms.

Less-loader: a loader generated by less for WebPack. Less-loader uses the core library less.

The loader for Webpack is a chain pipelined call implemented from right to left, so we configure it like this:

module: {    
  rules: [{test: /.less$/i,   
      use: [        
        'style-loader'.'css-loader'.'less-loader',]}]}Copy the code

Here the simple webpack handles the configuration of less, we write a less file in the project, import it in index.js, and then use it in our function:

// src/index.js
import './index.less'

function change(){ 
  const app = document.getElementById('app')
  app.innerHTML = "Change the App copy" 
  app.classList.add("container")
}
change()

Copy the code
// index.less
.container{  width: 100px;  height: 100px;  border: 1pxsolid black; }Copy the code

At this point, we run the NPM run start command and wait for the package to complete. The browser opens THE HTML:

Optimize:

  1. addcss module
  2. willcssandjsSeparate (production environment)
  3. The compressioncss
  4. cssCarry out on-demand import
  5. Browser compatibilitycss

Here is a simple first to each of the scheme, solution to say, and then in the final paste configuration file, the relevant detailed configuration also need to go to the detailed study.

Add CSS Module

There are two options:

  1. allless,cssAll files are modular
  2. Partial files (e.g. only.module.(le/c)ssSuffix) is modular

We use the first option because we can globalize styles by adding :global to the CSS Module.

Keep CSS and JS separate

Webpack generates bundles that mix CSS and JS together. We need to add the mini-CSS-extract-plugin for WebPack to handle the packaged bundles and separate CSS and JS.

Mini-css-extract-plugin and style-loader cannot be used together. In dev mode, style-loader is more advantageous. So let’s judge the environment here. Use style-loader in dev mode; Use the Mini-CSS-extract-Plugin in PROD environment. CSS references to entry files need to be imported automatically using html-webpack-plugin. After adding htML-wbpack-plugin, you need to specify the plugin to generate new index.html from the corresponding template HTML.

The mini-CSs-extract-plugin supports on-demand loading of CSS without configuration. The clean-webpack-plugin is installed to clean up the bundles left over from the last package before each package.

npm install --save-dev mini-css-extract-plugin html-wbpack-plugin clean-webpack-plugin
Copy the code

Compress CSS

npm install css-minimizer-webpack-plugin --save-dev
Copy the code

CSS compatibility with Browser

Browserslistrc is a file that provides the browser baseline information that tools like PostCSS and Babel need to reference.

npm install --save-dev postcss-loader postcss postcss-preset-env
Copy the code

After less, CSS processing, we will talk about image processing, image processing we directly use webpack asset Module function (check the information).

The entire configuration

<! -- index.html --> 
<! doctypehtml>
<html>
  <head>
    <meta charset="utf-8"> 
    <title>Build a project from zero</title> 
    <meta name="viewport" content="width=device-width,initial-scale=1">
  </head>
  <body> 
    <! -- Add a node --> 
    <! -- There is no need to import JS manually because we use htMl-webpack-plugin --> 
    <div id="app"></div>
  </body>
</html>
Copy the code
// index.js
import styles from './index.less' // css module
import temp from './temp.css'   // Tests whether the CSS file is processed by postCSS
import tans from './asset/image/tans.jpeg' //asset module

function change(){ 
  const app = document.getElementById('app')
  app.innerHTML = "Change the App copy"
  app.classList.add(styles.container) 
  app.classList.add(temp.temp)
  
  const img = document.createElement('img') 
  img.src = tans
  app.parentElement.append(img)
}

change()
Copy the code
// index.less
.container{ 
  width: 100px;  
  height: 100px; 
  border: 1px solid black; 
  background-image: url("./asset/image/tans.jpeg");  
  background-size: cover; 
  user-select: none;
}
Copy the code
// webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const path = require('path')
const {ProgressPlugin} = require('webpack')
const config = require("./config/index")

/** * gets the enumeration * of the current environment@returns development test production 
 */
function getEnv() {  return config.ENV_ENUM[process.env.ENV]}

const isProd = getEnv() === config.ENV_ENUM.prod

/ / webpack plug-in
const plugins = [  
  / / the progress bar
  new ProgressPlugin(),  
  Clear dist before packing
  new CleanWebpackPlugin(), 
  // Automatically generate HTML from the template
  new HtmlWebpackPlugin({template: 'index.html'}), 
  // Extract CSS files and load CSS on demand
  new MiniCssExtractPlugin({   
    filename: "./css/[name].[contenthash].css",}),]module.exports = { 
  // Environment configuration
  mode: isProd ? config.ENV_ENUM.prod : config.ENV_ENUM.dev,  
  / / the entry
  entry: {   
    app: path.join(__dirname, 'src'.'index')},/ / output
  output: {  
    filename: "[name].[hash].js".path: path.join(__dirname, 'dist'),},module: { 
    rules: [    
      // less CSS file processing
      {     
        test: /.(le|c)ss$/i,     
        exclude: /node_modules/,   
        use: [        
          // Production environment to compress the local dev directly using style-loader
          isProd ?  MiniCssExtractPlugin.loader : 'style-loader',  
          {        
            loader: "css-loader"./** * Enable CSS module * Load the previous loader (postCSs-loader) to handle compatibility issues */
            options: {        
              importLoaders: 1.modules: true}}, {loader: 'postcss-loader'.options: {         
              postcssOptions: {    
                // Automatically adds different browser light and handles new features
                plugins: ['postcss-preset-env']}}},"less-loader"],}, {test: /.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i,   
        type: 'asset/resource'.generator: {        
          filename: "[file]" 
        } 
      },  
    ] 
  }, 
  plugins,  
  optimization: { 
    minimizer: [  
      / / compress CSS
      new CssMinimizerPlugin(),   
    ],
  },
}
Copy the code

5. Build the development environment

First, open source-map. There are too many options. Search online. Devtool is recommended for dev environment eval-cheap-module-source-map and production environment nosource-source-map.

module.exports = {
    devtool: isProd ? "nosources-source-map" : "eval-cheap-module-source-map",}Copy the code

Manual NPM run start every time, and then to refresh, very troublesome. So we added the development tool (local server), webpack is used more is web-dev-server:

npm install --save-dev webpack-dev-server
Copy the code
module.exports = {
  devServer: {
    static: resolve(__dirname, '.. /dist'),
    compress: true.hot: true.port: 8080}},Copy the code
Json: "cross-env env =dev webpack serve --open",Copy the code

Rerun NPM run start, the project will be pulled up automatically and the browser will refresh automatically. At the same time when we debugger, we see our source code (because we configured devtool).

6. Configure HMR

Webpack-dev-server is with module hot replacement and all we need to do is turn it on. Enabling Method Add hot: true to the devServer configuration. Create a new js file, write something in it, and reference it in index.js. The module referenced in index.js is updated and needs to be retrieved with module.hot.accpet, which requires manual hook writing.

// Add a student.js
export function studentSay(){ 
  console.log('students'."Said"."Don't want to take a test")}Copy the code
// index.js 
import {studentSay} from './student.js'

if (module.hot) {    
  module.hot.accept('./student.js'.function() {
    console.log('Accepting the updated student module! ');   
    studentSay() 
  }) 
}
Copy the code

After the NPM run start project, make any changes to student.js and you will see the hot update.

As for why we need to write hooks manually, we can refer to this article. Fortunately, we need to use the framework, framework implements loader to handle, so do not worry.

Seven, Tree Shaking

It’s like shaking the dead leaves off a tree. The idea is to make the bundle smaller by removing unnecessary code from the project. It works on static structure analysis, so it can only be used for ESM. For globally referenced files, be aware of any side effects, such as global CSS, global JS, some polyfill, IIFE files, etc., which have side effects. Webpack needs to remove these files from the Settings.

Webpack has Tree Shaking turned on by default. If your files have no side effects, Tree Shaking will work and remove unwanted code. However, if there is only one place in your file that WebPack detects as having a side effect, it will not tree shaking the file. He doesn’t remove code that doesn’t work, doesn’t follow logic or fact. So, if you know exactly which files have side effects and which don’t, you can set them manually. This is what it says:

Math-effect. js and math-no-effect.js, one for side effects and one for no side effects:

// math-effect.js
// console.log in case the printed sentence is a business requirement
// So this sentence caused a side effect in Webpack's view

console.log("aaa")
export function addNum(num1, num2){  return num1 + num2}
Copy the code
// math-no-effect.js
export const name = 'awe'
export function calcNameLen(){  return name.length }
Copy the code
// only references are made in index.js without making any calls
import "./math-effect"
import './math-no-effect'
Copy the code

If we run start app.xxxx.js in the browser, we will not remove any code from these two files, because tree shaking is not done in the development environment.

Run the NPM run build and check app.xxx.js in the dist of the generated production environment. You’ll see that only math-effe.js is typed in. This is when Tree shaking works for files with no side effects.

In real projects, where there are global CSS, global JS, Polyfill, IIFE, etc., Webpack provides a configuration that can be explicitly configured in package.json: sideEffects: \[‘xxx.js, *.css’] (which matches the configuration item has side effects), because we know that we wrote math-effect.js with no business side effects, we set it empty:

// package.json
{
  "sideEffects": [],}Copy the code

Repackage and look at the generated Dist and you’ll see that math-effect-js is no longer packaged either.

Note that this means that all files have no side effects, and you need to know that the rest of the project really has no side effects.

There is another configuration method is to configure in loader, this check information will know.

In general, there is no special processing in the project, because the first is global CSS, global JS, Polyfill, IIFE, etc. Files with side effects are not a large proportion, and the second is that Webpack will tree shaking those files with no side effects by default. In fact, this is about it, the official version of the documentation recommended to install a compression plug-in, should be considered performance optimization.

npm install terser-webpack-plugin --save-dev
Copy the code
// webpack.config.js
module.exports = {
  optimization: { 
    minimize: true.minimizer: [new TerserPlugin()]
  },
}
Copy the code

Webpack configuration file optimization

Because we put all the WebPack configuration in webpack.config.js, but there are some configurations that I don’t want in some environments (like I don’t want the development environment to compress my code), so we split the configuration for different environments into different files. Then use Webpack-Merge to merge the common configuration.

npm install --save-dev webpack-merge
Copy the code

Create webpack.prod.js and webpack.dev.js. The test environment just needs to do the proxy division in webpack.dev.js.

// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
const { ProgressPlugin} = require('webpack')
const config = require("./index")
const { merge} = require('webpack-merge')
const devConfig = require("./webpack.dev")
const prodConfig = require("./webpack.prod")

/** * gets the enumeration * of the current environment@returns development test production
 */
function getEnv() {  return config.ENV_ENUM[process.env.ENV] }
const isProd = getEnv() === config.ENV_ENUM.prod

const commonConfig = { 
  / / the entry
  entry: {  
    app: path.join(__dirname, '.. /src'.'index')},/ / output
  output: { 
    filename: "[name].[hash].js".path: path.join(__dirname, '.. /dist'),},module: {   
    rules: [{test: /.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i,  
        type: 'asset/resource'.generator: {       
          filename: "[file]"}}},],plugins: [  
    new ProgressPlugin(),  
    Clear dist before packing
    new CleanWebpackPlugin(),
    // Automatically generate HTML from the template
    new HtmlWebpackPlugin({ template: 'index.html'}})]module.exports = () = >{
  if(isProd){ 
    return merge(commonConfig, prodConfig)  
  } 
  return merge(commonConfig, devConfig)
}
Copy the code
// webpack.dev.js
const config = require('./index')
const { HotModuleReplacementPlugin } = require('webpack')
const path = require('path')
const { merge } = require('webpack-merge')
const isTestEnv = config.ENV_ENUM[process.env.ENV] === config.ENV_ENUM.test
const devConfig = {
  mode: config.ENV_ENUM.dev,
  devtool: 'eval-source-map'.module: {
    rules: [
      // less CSS file processing
      {
        test: /\.(le|c)ss$/i,
        exclude: /node_modules/,
        use: [
          'style-loader',
          {
            loader: 'css-loader'.options: {
              importLoaders: 1.modules: {
                localIdentName: '[path][name]__[local]'}}}, {loader: 'postcss-loader'.options: {
              postcssOptions: {
                plugins: ['postcss-preset-env']}}},'less-loader']]}},plugins: [new HotModuleReplacementPlugin()]
}
const getConfig = () = > {
  if (isTestEnv) return devConfig
  return merge(devConfig, {
    devServer: {
      static: path.join(__dirname, '.. /dist'),
      compress: true.hot: true.port: 8080}})}module.exports = getConfig()
Copy the code
// webpack.prod.js
const config = require('./index')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  mode: config.ENV_ENUM.prod,
  devtool: 'nosources-source-map'.module: {
    rules: [
      // less CSS file processing
      {
        test: /\.(le|c)ss$/i,
        exclude: /node_modules/,
        use: [
          // Production environment to compress the local dev directly using style-loader
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader'./** * Enable CSS module * Load the previous loader (postCSs-loader) to handle compatibility issues */
            options: {
              importLoaders: 1.modules: {
                localIdentName: '[hash:base64]'}}}, {loader: 'postcss-loader'.options: {
              postcssOptions: {
                // Automatically add different browser prefixes and handle new features
                plugins: ['postcss-preset-env']}}},'less-loader']]}},optimization: {
    minimize: true.minimizer: [
      / / compress CSS
      new CssMinimizerPlugin(),
      JS / / compression
      new TerserPlugin()
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: './css/[name].[contenthash].css'}})]Copy the code
// package.json command change"
scripts": {"start":"cross-env ENV=dev webpack serve --open --config ./config/webpack.config.js","build:test":"cross-env ENV=test webpack --config ./config/webpack.config.js","build":"cross-env ENV=prod webpack --config ./config/webpack.config.js"}Copy the code

Configure bundle analysis

After packaging, we usually need to know which resources occupy a lot of resources. Here we configure an analysis command to analyze the packages produced in the production environment. We use the plug-in Webpack-bundle-Analyzer to complete this work.

Add a webpack.analyzer.js configuration under config, which is a plug-in configuration on top of the prod configuration:

// Scripts adds a command to start the Analyzer
// Don't add this plugin to webpack.config because it will run a devServer to display your bundle statistics
"scripts": {   
  "analyzer": "cross-env ENV=prod webpack --config ./config/webpack.analyzer.js"  
},
Copy the code
//webpack.analyzer.js
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
const { merge} = require('webpack-merge')
const getConfig = require("./webpack.config")

const analyzerConfig = { 
  plugins: [   
    new BundleAnalyzerPlugin() 
  ]
}

// Note that webpack.config is an exported function after we optimize it
module.exports = merge(getConfig(), analyzerConfig)
Copy the code

At this point, our NPM Run Analyzer launches an interface to analyze the packaged bundles. See this address for more information about the plugin.

Configure code separation

The product generated by webpack is called bundle, and the code we wrote is called Module. In the process from Module to bundle, Webpack will generate an intermediate product called chunk. The whole packaging process is from Module to chunk to bundle. Chunk is a chunk, like a building block. Somewhere in the code the logic uses a chunk. Just load that part. Breaking up the code into smaller chunks and referring to each chunk as needed is where code separation comes in. Code separation allows us to get smaller and thinner bundles that can then be loaded on demand or in parallel.

There are three ways to separate code: dynamic import, entry starting point, prevent duplication, and so on. Webpack dynamic introduction requires anti-human written code that is not very readable; The separation of entry points only increases the number of entrances, but cannot prevent the problem of repeated introduction. So we can extract the common dependencies as a bundle in a way that prevents duplication.

There are two ways to prevent duplication: one is the entry dependency method. Specify the dependent item under each entry and configure the specific content of the dependent item, as shown in this address. SplitChunksPlugin (SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin)

Here we have a very simple configuration on the line, to make good enough, need to go into the study.

// webpack.prod.js 
module.exports = {
  optimization: { 
    splitChunks: {  
      chunks: 'all',,}}}Copy the code

11. Modify it as TS project

TypeScript has two main functions. First, it provides type extension and static checking (a strongly typed language front-end really needs to be written, such as Java) by installing TypeScript packages in development dependencies. The other is to provide compilation capability (TSC), which compiles the higher version syntax into the lower version syntax to be compatible with different browsers or other runtime environments. So the second ability is a rival to Babel.

Babel has a richer ecology and a more nuanced compilation than TSC, so check it out (a quick article).

So we’ll just use TS static checking and type extension, and for compilation we’ll stick with Babel, which we’ll configure later.

npm i --save-dev typescript @types/node
Copy the code

Since we want to change the webpack file configuration to TS as well, we also need to install the types package associated with Webpack. And we need to run TS directly (webpack.config. TS, etc.), so we need to install TS-Node.

npm install --save-dev ts-node @types/webpack @types/webpack-dev-server
Copy the code

Add tsconfig. Json

The first step is to add tsconfig.json to the project so that it is already a TS project by definition. Then go to modify the file we wrote originally (only a few more important notes, not TS advice or first go to see).

{
  "compilerOptions": {
    "noEmit": true.// Do static typing without compiling content
    "module": "esnext"."target": "es5"."lib": [
      "dom"."esnext"]."baseUrl": "."."sourceMap": true.// Source-map must be enabled in TS
    "allowJs": true.// Writing js files is also allowed
    "checkJs": true.// Allow checking of js files
    "noImplicitAny": false."skipLibCheck": true."esModuleInterop": true."allowSyntheticDefaultImports": true."strict": true."forceConsistentCasingInFileNames": true."noFallthroughCasesInSwitch": true."moduleResolution": "node"."resolveJsonModule": true."incremental": true."isolatedModules": true
  },
  "ts-node": { // Ts-Node overloading of modules will be covered later
    "compilerOptions": {
      "module": "CommonJS"}},// Scope of action
  "include": [
    "src/**/*"."config/**/*",].// Exclude what
  "exclude": [
    "node_modules"."build"."dist"."scripts"]},Copy the code

Webpack adds processing ts

The second step is to transform our files, including some business configuration files, environment configuration files, Webpack configuration files, etc.

Two things to note:

After our project became A TS project, some custom variables such as process.env.env could not be defined by checking, and resource files such as.less,.css and.png could not be considered as modules by definition. SRC /react-app-env.d.ts. SRC /react-app-env.d.ts.

Since we changed the webpack configuration file to.ts, we need to run it with TS-Node. Ts-node is simply an extension of TS that runs on node, and the Node environment is the CommonJS specification. In tsconfig.json the module we specify is esNext, so we need to override the ts-Node configuration (see tsconfig.json above). If we do not do this step, Import {XXX} from ‘XXX’ : import {XXX} from ‘XXX’ : import {XXX} from ‘XXX’

I also tweaked the WebPack configuration here.

// src/typing.d.ts
// There are some problems with the typing.d.ts code in the picture
// The following is the correction
declare module '*.less'
declare module '*.css'
declare module '*.png'
declare module '*.svg'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare namespace NodeJS {
  interface ProcessEnv {     
    ENV: "dev" | "test" | "prod"; }}Copy the code
// config/index.ts
// Environment enumeration
export const ENV_LIST = {
  DEV: "dev".TEST: "test".PROD: "prod"
}
Copy the code
// config/webpack.analyzer.ts
import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'
import { merge } from 'webpack-merge'
import {Configuration} from 'webpack'
import prodConfig from './webpack.prod'

const analyzerConfig: Configuration = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

export default merge<Configuration>(prodConfig,  analyzerConfig)
Copy the code
// config/webpack.common.ts
import {CleanWebpackPlugin} from 'clean-webpack-plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import { join} from 'path'
import {Configuration, ProgressPlugin} from 'webpack'

const commonConfig: Configuration = {
  / / the entry
  entry: {
    app: join(__dirname, '.. /src'.'index')},/ / output
  output: {
    filename: "[name].[chunkhash].js".path: join(__dirname, '.. /dist'),},module: {
    rules: [{test: /\.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i.type: 'asset/resource'.generator: {
          filename: "[file]"}}},],plugins: [
    new ProgressPlugin(),
    Clear dist before packing
    new CleanWebpackPlugin(),
    // Automatically generate HTML from the template
    new HtmlWebpackPlugin({ template: 'index.html'}})]export default commonConfig
Copy the code
// webpack.dev.ts
import { HotModuleReplacementPlugin, Configuration } from 'webpack'
import { join } from 'path'
import { merge } from 'webpack-merge'
import commonConfig from './webpack.common'
// in case you run into any typescript error when configuring `devServer`
import 'webpack-dev-server'

//https://github.com/DefinitelyTyped/DefinitelyTyped/issues/27570
const devConfig: Configuration & { devServer: { [key: string] :any}} = {mode: 'development'.devtool: 'eval-source-map'.devServer: {
    static: join(__dirname, '.. /dist'),
    compress: true.hot: true.port: 8080
  },
  module: {
    rules: [
      // less CSS file processing
      {
        test: /\.(le|c)ss$/i,
        exclude: /node_modules/,
        use: [
          'style-loader',
          {
            loader: 'css-loader'.options: {
              importLoaders: 1.modules: {
                localIdentName: '[path][name]__[local]'}}}, {loader: 'postcss-loader'.options: {
              postcssOptions: {
                plugins: ['postcss-preset-env']}}},'less-loader']]}},plugins: [new HotModuleReplacementPlugin()]
}

export default merge<Configuration>(commonConfig, devConfig)
Copy the code
// webpack.prod.ts
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
import TerserPlugin from 'terser-webpack-plugin'
import { Configuration } from 'webpack'
import commonConfig from './webpack.common'
import { merge } from 'webpack-merge'

const prodConfig: Configuration = {
  mode: 'production'.devtool: 'nosources-source-map'.module: {
    rules: [
      // less CSS file processing
      {
        test: /\.(le|c)ss$/i,
        exclude: /node_modules/,
        use: [
          // Production environment to compress the local dev directly using style-loader
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader'./** * Enable CSS module * Load the previous loader (postCSs-loader) to handle compatibility issues */
            options: {
              importLoaders: 1.modules: {
                localIdentName: '[hash:base64]'}}}, {loader: 'postcss-loader'.options: {
              postcssOptions: {
                // Automatically add different browser prefixes and handle new features
                plugins: ['postcss-preset-env']}}},'less-loader']]}},optimization: {
    splitChunks: {
      chunks: 'all'
    },
    minimize: true.minimizer: [
      / / compress CSS
      new CssMinimizerPlugin(),
      JS / / compression
      new TerserPlugin()
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: './css/[name].[contenthash].css'}})]export default merge<Configuration>(commonConfig, prodConfig)
Copy the code
"scripts": { 
  "start": "cross-env ENV=dev webpack serve --open --config ./config/webpack.dev.ts"."start:prod": "cross-env ENV=prod webpack serve --open --config ./config/webpack.dev.ts"."build:test": "cross-env ENV=test webpack --config ./config/webpack.prod.ts"."build": "cross-env ENV=prod webpack --config ./config/webpack.prod.ts"."analyzer": "cross-env ENV=prod webpack --config ./config/webpack.analyzer.ts"  
},
Copy the code

The other.js files should also be changed to.ts. Add some type of ts to these files, not.ts files with a.js suffix to continue to run (this will be used for verification later, this step is required). Some configuration files, such as those in the root directory that end in.js, cannot be changed.

When we run NPM run start, we find that the project will not run:

Item not foundsrcUnder theindexBecause thewebpackNot supported by defaultTypeScriptHe doesn’t know.ts. In the officialModules and module resolutionThere are also instructions.

So at this time we need to do two steps first:

The first step is to add the parsed extension so that WebPack can find it when looking for the path of the module we reference, so we add the configuration to webpack.common.ts:

resolve: {  extensions: [ '.tsx'.'.ts'.'.jsx'.'.js'.'.less'.'.css' ],},
Copy the code

If we add a. Ts suffix instead of omiting the suffix, we will get an error:

This is an interesting question, Google searchTS 2691You will find the official openinghereThis one is not fixed, which means let’s leave out the suffix. All right, let’s do it

After the project arrived at this point, NPM run start found that it was still running, because Webpack knew.ts, but did not know how to deal with it. The previous step just added a parsing rule, hence our second step.

The second step is that we need to let Webpack know how to handle.ts. Reviewing the principle of Webpack, we need to add loader to enable Webpack to process resources it does not know, so we need to add loader to process.ts. As mentioned earlier, there are two choices: TSC and Babel. We chose Babel. In the next section, we will configure babel-loader.

Add Babel

At the beginning, Babel was just dealing with JS, not with.TS, so it was a tough phase, mixing Babel and TS, and adding various configurations. TSC is used to convert TS to JS, and Babel is used to process the lower version of JS. Babel7 solved this problem by simply erecting all ts and compiling as JS. Babel does compilation itself, so the idea is that you can just do your own type definition, check it statically, and when it comes to compiling code, I don’t care about your type or anything, just get rid of it and compile it in JS.

npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-typescript
Copy the code

Add babel.config.json to the project root directory

// babel.config.json
// Preset -typescript is an official set of recommendations for TS
// Preset -env uses.browserslistrc to generate lower-version code for the target environment
{  "presets": ["@babel/preset-typescript"."@babel/preset-env"]}
Copy the code

This time we NPM run start, the project is running again, of course you can install some Babel plugin, continue to optimize.

Add React

Install the React dependency package. React distinguishes the core from the rendering environment. The core package is React (react-core), and the rendering environment package is react-dom and react-native. So we use react-dom.

npm i react react-dom
npm i -D @types/react @types/react-dom
Copy the code

Then we changed the project to app. TSX and global.less in the root directory. We removed the redundant files left by the previous validation function, added the views view folder and wrote a welcome.

I want to emphasize that.tsxExtended syntax andvueThe difference between that template, please understand thattsxIt’s extension syntax, syntax. It’s going to be natural if we figure it out.

//app.tsx
import ReactDOM from 'react-dom'
import React from 'react'
import './global.less'
import Welcome from './views/welcome'

ReactDOM.render(
  <React.StrictMode>
    <Welcome />
  </React.StrictMode>.document.getElementById('app'))Copy the code
// src/welcome/index.tsx import React, {FC} from 'react' import styles from './index.less' // index.less const Welcome: FC = () => {return <div className={styles.welcome}> Welcome home </div>} export default welcomeCopy the code

Configure tsconfig.json and babel.config.json to support React:

//tsconfig.json adds JSX conversions to compilerOptions
{  
  "compilerOptions": { 
    "jsx": "react-jsx",}}Copy the code
// Add react Babel set NPM install --save-dev @babel/preset-reactCopy the code

/ / configuration Babel. Config. Json
{ 
  "presets": ["@babel/preset-react"."@babel/preset-typescript"."@babel/preset-env"]}Copy the code

Modify webPack configuration to support TSX and JSX, modify entry to app.tsx:

// Change the entry to app
entry: {
  app: join(__dirname, '.. /src'.'app')},// Add the.tsx.jsx extension
resolve: {
  extensions: [ '.ts'.'.tsx' , '.js'.'.jsx'.'.less'.'.css'],},module: {
  rules: [{// Add extension TSX JSX extension to add presets
      test: /\.(tsx? |jsx?) $/,
      exclude: /node_modules/,
      use: {
        loader: "babel-loader".options: {
          presets: ['@babel/preset-react'.'@babel/preset-env'.'@babel/preset-typescript']}}},]},Copy the code

Add UI library

The UI library can be selected according to their own, we take the use of more ANTD for example, open their official website guidance document.

Antd says it is a direct reference, but we actively ignore node_modules when configuring webpack to handle less and CSS files, so we need to handle them separately in Webpack. Secondly, the style files of antD component library are compiled and not applicable to the CSS Module configured by us, so the CSS Module should be removed when configured separately:

// Modify the less-loader configuration in webpack.dev.ts and webpack.prod.ts
{
  loader: "less-loader".options: {
    lessOptions: {
      javascriptEnabled: true}}}Copy the code
// Style files in node_modules are handled separately in wepack.mon.ts
{
  test: /\.(le|c)ss$/i,
  include: /node_modules/,
  use: [
    'style-loader',
    {
      loader: "css-loader".options: {
        importLoaders: 1,}}, {loader: "less-loader".options: {
        lessOptions: {
          javascriptEnabled: true}}}]},Copy the code
// Introduce antD style files in app.tsx
import 'antd/dist/antd.less';
Copy the code

15. Add state management

We’ll use Redux as an example, but if you have other state management libraries that you like, you can add others.

Add redux, React-redux, @reduxJS/Toolkit: Redux is a state management library for React, vUE, native JS and other frameworks. React – Redux is a middleware developed to facilitate redux use in React. And because redux is a little bit cumbersome, I developed @reduxJS/Toolkit (something like DVA, called little DVA) to simplify it, and that’s it.

As mentioned earlier, if the package is written in TS, it will ship with the type file. If not, download the package corresponding to @types.

npm i redux react-redux @reduxjs/toolkit
Copy the code

Ts, rootReducer. Ts, and our business module state. Here is an example of the user module (note that this requires some basic elements of react-Redux and @reduxjs/ Toolkit. If it says “laborious,” go and look it up.)

// state/index.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';

const store = configureStore({
  reducer: rootReducer,
});

export type AppDispatch = typeof store.dispatch;
export const dispatch = store.dispatch;
export default store;
Copy the code
// state/rootReducer.ts
import { combineReducers } from '@reduxjs/toolkit';
import userReducer from './user';

const rootReducer = combineReducers({
  user: userReducer,
});

export type RootState = ReturnType<typeof rootReducer>;

declare module 'react-redux' {
  // The state type of the corresponding service module can be read directly in the service
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface DefaultRootState extends RootState {}
}

export default rootReducer;
Copy the code
// state/user.ts
import type { PayloadAction } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'

export interface UserStateProps {
  userId: string
  name: string
  phone: string
}
// Initialize the value
const initState = (): UserStateProps= > {
  return {
    userId: ' '.name: ' '.phone: ' '}}const userSlice = createSlice({
  name: 'user'.initialState: initState(),
  reducers: {
    init(state) {
      const initS = initState()

      Object.keys(initS).forEach((key) = > {
        state[key] = initS[key]
      })
    },
    setUserInfo(state, action: PayloadAction<Partial<UserStateProps>>) {
      const inputState = action.payload

      Object.keys(inputState).forEach((key) = > {
        state[key] = inputState[key]
      })
    }
  }
})

export const { init, setUserInfo } = userSlice.actions

export default userSlice.reducer
Copy the code

TSX: App.tsx: app.tsx:

// src/app.tsx
import ReactDOM from 'react-dom'
import React from 'react'
import store from './store'
import './global.less'
import 'antd/dist/antd.less'
import Welcome from './views/welcome'
import { Provider } from 'react-redux'

ReactDOM.render(
  <React.StrictMode>/ / mount store<Provider store={store}>
      <Welcome />
    </Provider>
  </React.StrictMode>.document.getElementById('app'))Copy the code

On the business component let’s test:

import { Button, message } from 'antd'
import React, { FC, Fragment } from 'react'
import styles from './index.less'
import { useDispatch, useSelector } from 'react-redux'
import { setUserInfo } from '.. /.. /store/user'

const Welcome: FC = () = > {
  const dispath = useDispatch()
  const { name } = useSelector((state) = > state.user)

  return (
    <Fragment>
      <Button
        type='primary'
        danger
        onClick={()= >{dispath(setUserInfo({name: 'awefeng'})) message.success(' login succeeded ')}} > Login to aweFeng</Button>
      <div className={styles.welcome}>Welcome: {name}</div>
    </Fragment>)}export default Welcome
Copy the code

You can see the setup read problem, and reduxdev can also see the process.

Add a Router

React-router-core and react-router-dom are both installed in the browser, and the react-router-core and react-router-dom are installed in the browser. But I’m not going to talk about it today, because the routing section is quite large and involves configuration writing, permission control, ICONS, layout menus, lazy loading, etc. Therefore, we will write a series of router designs for the project separately. Then we will use the React-Router, so we will first bury a pit here.

React-router is also easy to use. Check out their website.

Add request processing file

To add a unified request file to handle requests, it usually selects a certain library and writes some processing logic in combination with the business. Here we use Axios to write a simple example:

npm install axios
Copy the code

Create the request folder in the SRC directory and create index.ts. I’ll just paste the writing steps here and give you a simple example file.

  1. First, we write a general hollow shelf. When we request, we fill in the URL, request method, query parameters, data, other configuration, etc.
  2. Then we need to complete the configuration of the incoming AXIos, at which point we also need to add the type.
  3. Handle the return of axois according to the API specification documentation for front-end alignment.
  4. Continue to refine the parameters passed in from config to refine the request (including authentication, headers, timeout, interception, cancellation, concurrency, etc.).
  5. Finally, pull out different configuration files based on different environments (development, testing, pre-release, online).
  6. The optimization point is that multiple AXIOS instances can be pulled out for different scenarios to call.
// src/request/index.ts
// A simple example is not necessarily accurate
import axios from 'axios';
import type { Method, AxiosRequestConfig, AxiosResponse, } from 'axios';
import { notification } from 'antd';

const fetch = <R>(
  url: string,
  {
    method,
    baseUrl = ' ',
    params = undefined,
    data = undefined,
    then = undefined. others }: {method: Method; baseUrl? :string; params? :Object; data? :any; then: Function, [key: string] :any },
): Promise<R> => {

  /** * 1. Whether there is a custom header or other configuration passed in from config * improve from here */
  let headers = {
    'Content-Type': 'application/json'./ / TODO authentication?
  };

  if (others.headers) {
    headers = Object.assign(headers, others.headers);
  }
  // 2. Define the requested Config
  const requestConfig: AxiosRequestConfig = {
    method,
    url,
    timeout: 10000, data, headers, ... others, };// 3. Baseurl is determined based on the environment and input
  // Optimize points to extract different AXIos instances
  if (baseUrl) {
    requestConfig.baseURL = baseUrl;
  } else {
    // The development environment is replaced by devServer proxy
    // Other environments read from the configuration
    if (process.env.ENV !== ENV.DEV) {
      requestConfig.baseURL = config.baseUrl;
    }
  }

  // If you pass in a subsequent method, use the subsequent method
  if (then) {
    return axios(requestConfig).then(then);
  }

  return axios(requestConfig)
    .then((res) = > {
      return new Promise((resolve, reject) = > {
        // Process according to the API specification document
        const { code } = res.data;
        if (code === 0 ) {
          resolve(res.data as R);
        } else{ reject(res); }}); }) .catch(async (res: AxiosResponse) => {
      // Unified error request handling
      // TODO login expired API specification document error code error etc
      const { status, data } = res;
      if (status === 401) {
        await new Promise<void> ((resolve) = > {
          notification.error({ message: data? .msg ??'Authentication expired, please log in again! ' });
          setTimeout(() = > {
            resolve();
          }, 2000);
        }).then(logOut);
      } else {
        // TODO other cases
        // Throws a business error message
        throw new Error(data? .msg ??'Request error ~ server is distracted'); }}); };export default fetch;
Copy the code

Then we create the API folder under SRC for the request. Write a request example:

// src/api/user.ts
import fetch from '.. /request/index'

// Get user information
export function getUserInfo(
  userId: string
) :Promise<{ data: { userId: string; name: string; blabala: any } }> {
  return fetch('/user/info', { method: 'GET'.params: { userId } })
}
Copy the code

18. Unify NPM source and version

It is very simple to unify the NPM source. Add. NPMRC under the project directory. Considering the reasons of runner packaging and wall in CI/CD, it is suggested to use Taobao mirror:

.npmrcregistry=https://registry.npm.taobao.org/
@company:registry=https://registry.npm.company.com/
Copy the code

Node unity is essential. Node updates, no matter how big or small, may change. Some dependencies may require node versions, so it is necessary to keep node and NPM versions unified in order to ensure that the project is consistent across everyone (or even the server). Package-lock. json (NPM) : package-lock.json (NPM) : package-lock.json (NPM) : package-lock.json (NPM) : package-lock.json As for how to unify, you can script constraints, you can enforce them (or you can just crash, whatever it is, run), and NVM is managed.

Path alias

Json: tsconfig.json: tsconfig.json: tsconfig.json:

// config/webpack.common.ts
resolve: {
  alias: {
    "@": resolve(__dirname, '.. /src'),
    "#": resolve(__dirname, '.. /config')},extensions: [ '.ts'.'.tsx' , '.js'.'.jsx'.'.less'.'.css'],},// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@ / *": [
        "./src/*"]."# / *": [
        "./config/*"]}}}Copy the code

At this point we can use it in our code:

// For example
import { setUserInfo } from "@/store/user";
import {ENV_LIST} from "#/index"
Copy the code

Editorconfig and gitignore

The editorConfig configuration file is a coding constraint on the IDE and requires IDE support (the supported ides and those that require plug-ins are detailed on the official website). For example, if I’m using VS Code, I need to install the EditorConfig for VS Code plug-in in order to use this configuration.

// In the root directory. Editorconfig
# see http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab
Copy the code

Git ignore files are files that need to be ignored during the configuration project submission. The Git official recommends different configurations for different development environments

# see https://github.com/github/gitignore/blob/main/Node.gitignore
/dist
.DS_Store
*.lock

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Dependency directories
node_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
Copy the code

Add esLint, prettier, and stylelint

Eslint is primarily used to regulate code quality, requiring us to install our own packages

npm i --save-dev eslint
Copy the code

Write an initial configuration and place it in the root directory

// .eslintrc.js
module.exports = {
  // Set the current directory to the eslint root directory
  root: true.// Set esLint's env
  env: {
    es6: true.browser: true.node: true
  },
  parserOptions: {
    ecmaVersion: 6.sourceType: "module".jsx: true
  },
  extends: ["eslint:recommended"].rules: {
    // Do not write}};Copy the code

I am using VS Code as the IDE and need to install the plugin ESLint and check whether the ESLint Server starts successfully and the configuration file fails:

Eslint will be able to detect this by creating a new test js file in the root directory and writing a bit of code. The initial file is ignored by default, as is node_modules, but I didn’t find the official description, so check it out if you’re interested.

Then continue to modify the configuration above to support React and TS, give two references:

Eslint-TS

Eslint-React

npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react
Copy the code

Add some custom rules to rules. The modified rules are as follows:

module.exports = {
  // Set the current directory to the eslint root directory
  root: true.// Set esLint's env
  env: {
    es2021: true.browser: true.node: true
  },
  parserOptions: {
    ecmaVersion: 'latest'.sourceType: 'module'.ecmaFeatures: {
      jsx: true}},parser: '@typescript-eslint/parser'.plugins: ['@typescript-eslint'.'react'].extends: [
    'eslint:recommended'.'plugin:react/recommended'.// The react-jsX mode is used in the project
    'plugin:react/jsx-runtime'.'plugin:@typescript-eslint/recommended'].settings: {
    react: {
      pragma: 'React'.version: 'detect'}},rules: {
    // See the official website for details
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off'.'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'.'no-inner-declarations': 'warn'.'block-scoped-var': 'error'.'default-case': 'error'.'no-caller': 'error'.'no-eval': 'error'.'array-element-newline': ['warn', { multiline: true.minItems: 4}].'comma-dangle': ['error'.'never'].'max-len': [
      'error',
      {
        code: 100.tabWidth: 2.comments: 80}].semi: ['error'.'never'].'padding-line-between-statements': [
      'error'.// Add the necessary blank line style to the code
      {
        blankLine: 'always'.prev: ['const'.'let'.'var'.'block'.'block-like'].next: The '*'
      },
      {
        blankLine: 'always'.prev: ['import'].next: ['const'.'let'.'var'.'block'.'block-like'.'expression'] {},blankLine: 'never'.prev: ['import'].next: ['import'] {},blankLine: 'never'.prev: ['const'].next: ['const'}]}}Copy the code

Configure ignore files:

# .eslintignore
#Three will do for now 
#Node_modules esLint can also be ignored without writing node_modules
node_modules
/dist
/src/asset
Copy the code

Prettier, prettier, prettier, prettier, prettier, prettier, prettier, prettier

Eslint-Prettier

npm i -D eslint-plugin-prettier prettier eslint-config-prettier
Copy the code

Lint supports prettier:

// Paste only the changes
module.exports = {
  // The plugin added prettier
  plugins: ["@typescript-eslint"."react"."prettier"].// Use the new recommended configuration mode
  / / the use of "plugin: prettier/it"
  extends: [
    "eslint:recommended"."plugin:react/recommended"."plugin:react/jsx-runtime"."plugin:@typescript-eslint/recommended"."plugin:prettier/recommended"]},Copy the code

Create.prettier. Js to configure prettier, see the official documentation

// .prettier.js
module.exports = {
  printWidth: 80.// The number of characters in a line. If more than 80 characters are added to the line, the default value is 80
  tabWidth: 2.// A TAB represents several Spaces. The default is 80
  useTabs: false.// Whether to use TAB for indentation. The default value is false, indicating that Spaces are used for indentation
  singleQuote: true.// Whether to use single quotation marks for strings. The default is false. Use double quotation marks
  jsxSingleQuote: true.// Whether to use single quotes in JSX
  semi: false.// Whether to use semicolons on line bits. Default is true
  trailingComma: 'none'.Tail / / whether or not to use commas, there are three optional value "< none | es5 | all >"
  bracketSpacing: true.{foo: bar} {foo: bar}
  parser: 'typescript' // Parsing engine for code
}
Copy the code

Ignore files are also configured:

# .prettierignore

package.json
/dist
.DS_Store
.eslintignore
*.png
.editorconfig
.gitignore
.prettierignore
Copy the code

I’ll leave it up to you to add styleint.

22. Formatting

Configuring formatting Commands

After the above work, the project code specification is available, but it’s just a hint that you’ll need to run your own commands to make changes, so we’ll add a command in package.json to fix the problem esLint detected:

"scripts": {  
  "lint": "eslint --fix --ext .js,.jsx,.ts,.tsx ."
 },
Copy the code

The pitfall here is that esLint has requirements for node versions, so if you run this command incorrectly, check to see if the node version is correct, which is one of the reasons we said to control the version above.

After running the command, you will find that some areas may still not be fixed, which is when you point to the error to see why. There’s a case where prettier’s rule conflicts with our own ESLint rule in Rules.

For example, prettier is in conflict with this rule
'array-element-newline': ['warn', { multiline: true.minItems: 4 }]
Copy the code

The reason for this is that prettier does not prettier before something she writes, and prettier does not take precedence over what she writes. The official solution for prettier is to ignore it yourself Ah yes yes yes, you can look this up, I remember someone mentioned “issue” on Github. So prettier is just a reference when it comes to actual customization for herself or her team.

IDE automatic formatting

This requires different configurations for different ides. Some ides may support it by default and do not require configuration and will format it if you detect that you have an ESLint configuration. Some ides may still require you to write a workspace configuration file. I take VS Code as an example, VS Code needs to be configured, and there are two ways to configure the workspace: the first way is to create. Vscode under the root directory, add settings.json and write it manually. The second option is menu -code- Preferences – Settings – Workspace, check Settings.

// .vscode/settings.json
{
  "eslint.alwaysShowStatus": true."editor.codeActionsOnSave": {
    "source.fixAll": true}}Copy the code

At this point, when we write the code and save it, it will be automatically formatted.

Another optimization point is that there is no need to passively format all the files each time you commit; instead, you only need to format the files that have been changed (lint-staged). This section can be done independently.

Git constraint

If a team member’s IDE requires manual configuration or is not supported, or the team member is not configured either. At this point, his code may be pushed to a remote repository without being formatted. To avoid this, we need to run our commands to format the code before git commits, and it is best to be able to do the canonical commit information. With these two requirements in mind, we called on “Husky”, a Git hook library that can be used for lint code, spec commit information, run tests, and more before committing. Husky uses a configuration file script after version 7.0, and we use the official installation instructions:

npx husky-init
Copy the code

To modify the contents of the pre-commit hook, we need to reformat and recommit the hook in the pre-commit script.

//. Husky /pre-commit simple example is for reference only#! /bin/sh
. "$(dirname "$0")/_/husky.sh"

echo '1. Perform eslint'
npm run lint
echo '2. Perform prettier'
npx prettier --write
echo 'Execute done, add file again'
git add -A
Copy the code
// package.json
{
  "scripts": {
    "lint": "npm run lint:js && npm run lint:style"."lint:js":"eslint --fix --ext .js,.jsx,.ts,.tsx ."."lint:style": "Echo 'can't write, use the styleling command to format less CSS",}}Copy the code

At this point we go through the submission process:

After the formatting of the first job, we need to detect the submission information for the second job to prevent scribbling and maintain consistency. Again, follow the official example to add onecommit-msgHook:

npx husky add .husky/commit-msg
Copy the code

Then we’ll edit the commit-msg script to check if the committed information meets our requirements:

// /husky/commit-msg
#! /bin/sh
. "$(dirname "$0")/_/husky.sh"
I am not familiar with the bash script
So run the constraint script we wrote with Node
node ./commit-msg.js The $1
Copy the code
// root directory commit-msg.js
X * NPM I -d [email protected] * Remember to ignore this file under ESlint ** I copied umiJS fabric detection * He copied other people's */, too

 const chalk = require("chalk")
 const msgPath = process.argv[2]
 
 const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
 
 const commitRE =
   /^(((\ud83c[\udf00-\udfff])|(\ud83d[\udc00-\ude4f\ude80-\udeff])|[\u2600-\u2B55]) )? (revert: )? (feat | fix | computer | | UI refactor | ⚡ perf | workflow | build | CI | typos | chore | tests | types | the wip | release | dep | locale) :. / {1, 50}
 
 if(! commitRE.test(msg)) {console.error(
     `  ${chalk.bgRed.white(' ERROR ')} ${chalk.red('Submit log not conforming to specification')}\n\n${chalk.red(
       'The legal submission log format is: \n\n' (emoji optional))},
     ${chalk.green('💥 feat: Added a great feature)}
     ${chalk.green('🐛 fix: fixed some bugs')}
     ${chalk.green('📝 docs: Updated the document')}
     ${chalk.green('🌷 UI: Modified the style)}
     ${chalk.green('🏰 chore: Made some changes to scaffolding')}
     ${chalk.green('🌐 locale: makes a small contribution to internationalization')}
     ${chalk.red(For more commit prefixes see commit-msg.js\n)}`
   )
   process.exit(1)}Copy the code

conclusion

Here we build almost, mellow mellow can be used, so far the whole from zero construction project is done, the whole series of content may be in and out, there are mistakes, there are not configured, omissions, please forgive, point out, thank you.

Complete project catalogue:

Summarize the general steps and ideas:

  1. Create a new folder and initialize it as an NPM project
  2. Introduce the packaging tool and write a very basic configuration file
  3. Configure the environment and the corresponding packaging script commands
  4. Packaging profiles for different environments are distinguished
  5. Added resource file handling to the package configuration file
  6. Setting up a Development Environment (devServer)
  7. Configure HMR, Tree Shaking, and Code Splitting
  8. Added packaged Product Analysis (Webpack-bundle-Analyzer)
  9. The project was changed to TS project
  10. Add babel-loader to handle TS
  11. Add React and modify the configuration to support TSX and JSX
  12. Add UI library
  13. Adding Status Management
  14. Add the routing
  15. Add request handling
  16. Add project constraint files
  17. Repackage the configuration file, optimize the extraction, and remove the configuration file
  18. Add automatic build/deployment
  19. Add a project description file (what the project does, what technologies are used, how to start and play, submit reviews, workflow, deployment instructions, etc.)

These configurations are not difficult at all, you just need to know what each technology does and find the appropriate configurations. Building a project from scratch, except for the routing part, should be done. I put a demo on Github for your reference:

Github.com/awefeng/fe-…

Welcome star(more ⭐️, gentlemen)

Welcome to follow my official account: Mi Zai and Tangyuan

I am “have two smelly cats”, one has two cats, and we learn front end friend ~