×
Community Blog Build Standardized Applications with Webpack5 + React + TS from 0 to 1

Build Standardized Applications with Webpack5 + React + TS from 0 to 1

This article explains how to build a standardized frontend application based on webpack + react + typescript (starting from an empty directory).

1

By Liu Huangxun (Keyu)

Preface

This article mainly explains how to build a standardized frontend application based on webpack + react + typescript (starting from an empty directory).

  • Technology Stack: Webpack5 + React18 + TS
  • Engineering: eslint + prettier + husky + Git hooks
  • Supports image, less, sass, fonts, data resources (JSON, csv, tsv, etc.), Antd loading on demand, and themes
  • Supports hot update, resource compression, code separation (dynamic import, lazy loading, etc.), cache, and devServer

Background

We can use scaffolding tools in project development (such as create-react-app or iceworks), so why should we build a standardized project by ourselves?

Cause

When we use excellent scaffolding tools to develop projects, our development will be more convenient. They have comprehensive functions and powerful performance. However, in front of these worthy examples, we need to start from scratch to realize every detail and function. It is better to realize a demo by practice rather than only watching. Practices can help us understand the principles of project packaging and compilation, thereby improving our technical proficiency and expanding our knowledge. Webpack is not using all in one code to implement all aspects of functions in engineering. From the design concept and implementation principle of Webpack, we can come into contact with engineering knowledge: schema extension, plug-in, and caching mechanism. Learning Webpack also means learning the development trend of the frontend. For example, we can learn the concept of bundleless from Vite, which skips the traditional concept of packaging. Other advanced concepts are places we need to learn.

During the development, by using def, Aone, etc., to generate a mature frontend project template, it is not difficult to find that the configuration files (such as babel, wepback, prettier, and loader) in the project are missing, and it is difficult to modify the existing scaffold configuration. The scalability is weak. This leads to limited work that can be done in performance optimization, thus limiting development.

Project Structure

Contents

├── dist                                // The default build output directory.
├── .husky                              // pre-commit hook
├── webpack.config.js                   // Global configuration file and webpack configuration file.
├── test                                // Test Directory
└── src                                 // Source Code Directory
    ├── assets                          // Common Files (Such as images, css, and font)
    ├── components                      // Project Widgets
    ├── constants                       // Constant/Interface Address, etc.
    ├── routes                          // Routings
    ├── utils                           // Tool Library
    ├── pages                           // Page Module
        ├── Home                        // Home Module. We recommend that all widgets start with capitalized letters.
        ├── ...
    ├── App.tsx                         // react Top-level File
    ├── typing                          // TS Type File
├── .editorconfig                       // IDE Format Specifications
├── .eslintignore                       // eslint Ignore
├── .eslintrc                           // eslint Configuration File
├── .gitignore                          // git Ignore
├── .prettierrc                         // prettierc Configuration File
├── .babelrc                         // babel Configuration File
├── LICENSE.md                          // LICENSE
├── package.json                        // package
├── README.md                           // README
├── tsconfig.json                       //typescript Configuration File

Dependencies

"dependencies": {
    "antd": "^4.22.4", // You know it.
    "react": "^18.2.0", // You know it.
    "react-dom": "^18.2.0" // You know it.
  },
  "devDependencies": {
    // babels
    "@babel/core": "^7.18.10",
    "@babel/plugin-proposal-class-properties": "^7.18.6", // React Class Support
    "@babel/plugin-transform-runtime": "^7.18.10", // Extract the Babel injection code to prevent repeated loading and reduce the volume.
    "@babel/preset-env": "^7.18.10", // The provided preset allows us to use the latest JavaScript.
    "@babel/preset-react": "^7.18.6", // React Support
      
    //ts Type Check
    "@types/node": "^18.6.4",
    "@types/react": "^18.0.15",
    "@types/react-dom": "^18.0.6",
    // Those that start with @typesare the TypeScript type declaration of the corresponding package.
    "@typescript-eslint/eslint-plugin": "^5.33.0",
    "@typescript-eslint/parser": "^5.33.0",
      
    // webpack loader: Parses the corresponding file.
    "csv-loader": "^3.0.5",
    "sass-loader": "^13.0.2",
    "xml-loader": "^1.2.1",
    "ts-loader": "^9.3.1",
    "less-loader": "^11.0.0",
      
    // eslints
    "eslint": "^8.21.0",
    "eslint-config-ali": "^14.0.1", // ali Frontend Specification
    "eslint-config-prettier": "^8.5.0", // Disable all rules that are unnecessary or may conflict with [Prettier].
    "eslint-import-resolver-typescript": "^3.4.0", // The ts syntax is supported eslint-plugin-import.
    "eslint-plugin-import": "^2.26.0", //ES6 + import/export Syntax Support
    "eslint-plugin-prettier": "^4.2.1", //prettier Syntax Support
    "eslint-plugin-react": "^7.30.1", //react Syntax Support
    "eslint-plugin-react-hooks": "^4.6.0", //hooks Syntax Support
    "eslint-webpack-plugin": "^3.2.0", 
    
    // webpack plugin
    "fork-ts-checker-webpack-plugin": "^7.2.13", // Avoid detecting the ts type in the webpack.
    "html-webpack-plugin": "^5.5.0", // Simplify the creation of an HTML file and use it with a webpack bundle containing hash.
    "mini-css-extract-plugin": "^2.6.1", // css Splitting
    "optimize-css-assets-webpack-plugin": "^6.0.1", // css Compression
    "terser-webpack-plugin": "^5.3.3", // Use terser to compress js (terser is a tool for managing and compressing ES6 +).
    "webpack-bundle-analyzer": "^4.5.0", // Visual Analysis of webpack Packaging Volume
    "webpack-cli": "^4.10.0", // Provide scaffolding commands.
    "webpack": "^5.74.0", // Webpack Engine.
    "webpack-dev-server": "^4.9.3", // The live server in the development environment.
     
    // Tools
    "husky": "^8.0.1", // Automatically configure Git hooks.
    "less": "^4.1.3", // css Type
    "sass": "^1.54.3", // css Type
    "typescript": "^4.7.4", // ts
    "lint-staged": "^13.0.3", // Run the linter on the staging git file.
    
    // prettier Formatting
    "prettier": "^2.7.1",
    "pretty-quick": "^3.1.3", // Run the prettier on the changed file.
  }

Implementations

Project Initialization

Start with an empty directory and initialize the project:

mkdir demo
cd demo
git init
npm init

React and Babel Introduction

First, we install React and write Hello World! for a React project.

Install our main project dependencies:

tnpm i -S react react-dom

Since our browser does not support the latest ECMAScript syntax, we need Babel to escape it as ES5 or ES6.

Install our Babel to improve compatibility:

tnpm i -D @babel/core babel-preset-env babel-preset-react @babel/plugin-proposal-class-properties
  • @ babel/core: The core engine of babel transcoding.
  • babel-preset-env: Add support for ES5 and ES6
  • babel-preset-react: Add support for JSX
  • @ babel/plugin-proposal-class-properties: Support for classes in React

Webpack Introduction

tnpm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
  • webpack: The core dependency of the wepback plug-in
  • webpack-cli: Provides command line tools for plug-ins
  • webpack-dev-server: Help start the live server
  • html-webpack-plugin: Help create HTML templates

Babel Configuration

Add basic configurations to Babelrc:

{
  "presets": ["@babel/react", "@babel/env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Babel Plugin

Babel is a code converter. With it, we can use the most popular js writing method. The plugin is the core of implementing Babel's functions.

2

The configuration here is to support the writing of the class in React.

Babel Preset

Babel's Plugin is divided into the smallest possible granularity. Developers can introduce features as needed, such as ES6 to ES5. 20+ plug-ins are officially provided, which can improve performance and extensibility. However, it is difficult to introduce them one by one. Babel Preset was created for this reason. It can be regarded as a collection of related Plugins:

  • @ babel/react: Supports all transcoding requirements of React
  • @ babel/env: It only needs its internal configuration items to complete almost all the transcoding requirements of modern JS projects.

Basic Configuration of Webpack

Create a new webpack.config.js file:

//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
   entry: './src/index.js',
   output: {
      path: path.join(__dirname, '/dist'),
      filename: 'bundle.js'
   },
   devServer: {
      port: 8080
   },
   module: {
      rules: [
         {
            test: /\.jsx?$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
         },
         {
            test: /\.css$/,
            use: [ 'style-loader', 'css-loader' ]
        }
      ]
   },
   plugins:[
       new HtmlWebpackPlugin({
            template: path.join(__dirname,'/src/index.html')
       }) 
   ]
}
  • entry: The starting point for packaging
  • output: The address of the packaged file
  • devServer: Live Server Configuration
  • test: The file type of the loader
  • loader: The loader to be used

Basic Configuration of Package.json

"start": "webpack serve --mode development --open --hot",
"build": "webpack --mode production"
  • mode: process.env.NODE_ENV --> development. Enable meaningful names for modules and chunks
  • open: Tells the server to open the default browser after the service starts
  • hot: Enables hot updates

Write a React Demo

The following figure shows the current project structure:

3

js and html files are shown in the following figure:

/_index.js_

import React from "react";
import ReactDOM from "react-dom";

const App = () => {
  return (
<div>
      <h1>Hello!!</h1>
      <h2>Welcome to your First React App..!</h2>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
//_index.html_

<!DOCTYPE html>
<html lang = "en">
  <head>
<meta charset = "UTF-8">
<title>React Web</title>
  </head>
  <body>
    <div id = "root"></div>
    <script src = 'bundle.js'></script>
  </body>
</html>

Finally, as long as you start, the project will start on the 8080 port.

TypeScript Configuration

tnpm install -D typescript ts-loader @types/node @types/react @types/react-dom
  • typescript: The main engine of TypeScript
  • ts-loader: Escape. ts --> .js and package
  • @ types/node @types/react @types/react-dom: The definitions of node, react, and react dom types

Add tsconfig.json to the root directory to configure the ts compiler:

//_tsconfig.json_

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "Node"
  }
}

Finally, add support for ts in the webpack.

Add ts-loader

//_webpack.config.js_
...
{
  test: /\.tsx?$/,
  exclude: /node_modules/,
  loader: 'ts-loader'
}
...

Set the Resolve Attribute to Specify How the File Is Resolved

//_webpack.config.js_
...
resolve: 
{
   extensions: [ '.tsx', '.ts', '.js' ],
}
...

Rename Entry

//_webpack.config.js_
...
entry: "./src/index.tsx",
...

Finally, start the server to check if the ts configuration is correct:

4

The preceding configuration is equivalent to one execution:

npx create-react-app my-app --template typescript

In this process, it is troublesome to provide *.ts to TypeScript and the running results to Babel with the help of many loaders.

5

Can we simplify this process? Babel-loader provided in Babel 7 can compile ts perfectly. So, the answer is yes. This method simplifies the process.

6

module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: ['babel-loader']
      }
    ]
  },

In addition, there is only one more line as @ babel/preset-typescript in. babelrc. This configuration is simpler, has faster packaging speed, and has clearer logic.

Why do we use ts-loader in a project?

  • ts-loader internally calls the official compiler of TypeScript -- tsc. Therefore, ts-loader and tsc share tsconfig.json. They will provide complete error messages. The ts-loader is consistent with the syntax verification provided by vscode.
  • However, @ babel/preset-typescript sometimes fails to provide complete error messages and type prompts.

Manage Resources

Webpack can only understand JavaScript and JSON files. This is a capability available for webpacks out of the box. Loader enables webpack to process other types of files and convert them into valid modules.

The test attribute can identify which files will be converted in the loader. The use attribute can define which loader should be used during conversion.

CSS, Less, and Sass

Install the loader:

tnpm i -D less less-loader style-loader css-loader sass sass-loader

Webpack Configuration:

//_webpack.config.js_
...
rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
      },
      {
        test: /\.(less|css)$/,
        exclude: /\.module\.less$/,
        use: [
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: !!DEV,
            },
          },
          {
            loader: 'less-loader',
            options: {
              sourceMap: !!DEV,
            },
          },
        ],
      },
      {
        test: /\.(sass|scss)$/,
        use: [
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: !!DEV,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: !!DEV,
            },
          },
        ],
      },
 ...

Images and JSON Resources

We can use the built-in Assets Modules to easily add images and fonts to our system. For types, we can choose:

  • asset/resource sends a separate file and exports the URL
  • asset/inline exports the data URI of a resource
  • asset/source exports the source code of a resource
  • Asset automatically selects between exporting a data URI and sending a separate file
//_webpack.config.js_
...
module: {
  rules: [{
    test: /\.png/,
    type: 'asset/resource'
  }]
},
...

We need to install a csv-loader, xml-loader, etc. for other types of resources:

//_webpack.config.js_
...
{
  test: /\.(csv|tsv)$/i,
  use: ['csv-loader'],
},
{
  test: /\.xml$/i,
  use: ['xml-loader'],
},
...

Set up a Development Environment

Currently, our application can run tsx files normally and debug and develop locally. Let's take a look at how to set up a development environment to make development easier:

//_webpack.config.js_
...
const { DEV, DEBUG } = process.env;
process.env.BABEL_ENV = DEV ? 'development' : 'production';
process.env.NODE_ENV = DEV ? 'development' : 'production';
...
mode: DEV ? 'development' : 'production',
devtool: DEV && 'source-map',
...

We can obtain environment variables from process.env to distinguish the development environment from the production environment.

When webpack packages code locally, we can use inline-source-map to map the compiled code back to the source code. When an error is reported, the error will be traced to the exact number of files and lines. It is better to turn off this setting dynamically to protect privacy in the production environment.

In the development environment, webpack-dev-server will provide a basic web server with real-time reloading capabilities.

Improve Packaging Configuration and Cache

We hope to delete the last packaging file every time we package. You can use the CleanWebpackPlugin:

//_webpack.config.js_
...
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  plugins: [
    new CleanWebpackPlugin(),
  ]
}
...

We hope the changed new version can discard the cache, and the unchanged version can hold the cache in our production environment. However, in the development environment, we do not want to have a cache but to get the latest resources every time. Therefore, you need to split the webpack config. Divide it into:

  • webpack.prod.js production environment packaging configuration
  • webpack.dev.js development environment packaging configuration

The main differences are the file name and sourceMap of the packaged file.

Production Environment

contenthash: The hash value is only changed if the content of the module changes.

output: {
    filename: 'js/[name].[contenthash:8].js', // contenthash: The hash value is changed only if the content of the module is changed.
},

Development Environment

output: {
  filename: 'js/[name].[hash:8].js',
}

Performance Improvements

Packaging Analysis Tools

webpack-bundle-analyzer can analyze the size of our packaged resources:

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
  DEBUG && new BundleAnalyzerPlugin(),
]

Set the startup item of package.json.

Resource Compression

OptimizeCSSAssetsPlugin is mainly used to optimize the output of css files, including discarding duplicate style definitions, cutting off redundant parameters in style rules, and removing unnecessary browser prefixes.

TerserPlugin is used to optimize js volume, including renaming variables and even deleting the entire inaccessible code block.

//_webpack.config.js_
...
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
...
optimization: {
  minimizer: [
    new TerserPlugin({
      parallel: false,
      terserOptions: {
        output: {
          comments: false,
        },
      },
    }),
    new OptimizeCSSAssetsPlugin({}),
  ],
    minimize: !DEV,
      splitChunks: {
        minSize: 500000,
          cacheGroups: {
            vendors: false,
          },
      },
},
...

Code Separation

Resource Separation

1) Multiple Entries

The built-in feature of webpack can separate the code into different bundles. Then, these files can be loaded on demand or in parallel. Code separation can obtain smaller bundles and control the precedence of resource loading. If used properly, the loading time will be affected.

//_webpack.config.js_
...
entry: {
  index: './src/index.js',
  another: './src/another-module.js',
},
output: {
  filename: '[name].bundle.js'
...

2) Tree Shaking

Webpack5 has integrated the Tree Shaking functions in the production environment, and unused code will be shaken out.

// _webpack.config.js_
module.exports = {
  // ...
  mode: 'production',
};

However, you need to manually configure in the development environment, but we do not recommend it.

// _webpack.config.js_
module.exports = {
  // ...
  mode: 'development',
  optimization: {
    usedExports: true,
  }
};

How does webpack avoid unused code?

It's simple: Webpack didn't see the code you used. Webpack tracks the import/export statements of the entire application. If Webpack sees that the imported thing is not used in the end, it will think the code is unreferenced or dead, and will perform tree-shaking. However, dead code is not always clear to tell. Here is an example:

// _test.js_
// This will be regarded as "live" code and will not do tree-shaking.
import { add } from './math'
console.log(add(5, 6))
// Imported but not valued to JavaScript objects, nor used in code.
// This will be treated as "dead" code and will be tree-shaked.
import { add, minus } from './math'
console.log('hello webpack')
// Import the entire library, but not value to JavaScript objects or use in the code.
// It is strange that this is regarded as "live" code since Webpack handles library import differently from local code import. 
import { add, minus} from './math' // Dead
import 'lodash' // live
console.log('hello webpack')

So we can use the following Shimming method for this three-party library:

Note: The Webpack cannot perform tree-shaking 100% securely. Some module imports will have an important impact on the application as long as they are introduced. A good example is a global style table or a JavaScript file that sets the global configuration. Webpack believes such files have side effects. Files with side effects should not do tree-shaking since this will destroy the entire application. A better method to tell Webpack that your code has side effects is to set sideEffects in package.json.

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}

3) Shimming Preset Dependencies

We can use the fine-grained shimming preset method to optimize the preceding lodash library that fails to be shaken. First, we introduce the ProvidePlugin to change the module dependency in the application to a global variable dependency. Let's remove the import statement of lodash, provide it through the plug-in, and extract the join method to use it globally.

// _src/index.tsx
console.log(join(['hello', 'webpack'], ' '))
// _webpack.config.js_
plugins: [
  new webpack.ProvidePlugin({
    //_: 'lodash'
    // If there is no comment, you need to quote console like this.log(_.join(['hello', 'webpack'], ' '))
    join: ['lodash', 'join'],
  })
]

Fine-Grained Shimming

Some legacy modules depend on the window object pointed to by this. We can use import-loaders, which are useful for third-party modules that depend on global variables (such as $or this) under the window object.

In the context of CommonJS, this becomes a problem, which means this points to module.exports. As such, you can override this point using the imports-loader.

// _webpack.config.js_
module: {
  rules: [
    {
      test: require.resolve('./src/index.js'),
      use: 'imports-loader?wrapper=window',
    },
  ]
},

4) Common Part Extraction

You can use splitChunk to extract the common part of the code and prevent duplication.

//_webpack.config.js_
...
minimize: !DEV,
  splitChunks: {
    minSize: 500000,
      cacheGroups: {
        vendors: false,
      },
  },
...
  • minSize: The smallest size to form a new code block
  • cacheGroups: Set the chunks of the cache here

5) Separation on Demand

In the React project, the code can be separated on demand using the following method. Webpack takes import() as a split-point and the introduced module as a separate chunk. import() takes the module name as a parameter and returns a Promise object, which is import(name) -> Promise.

//_index.tsx_
...
const WdAndDxEntry = lazy(() => import(/* webpackChunkName: "wd-and-dx" */ '../../old-code/component/wd-and-dx/entry'));
const WdAndDxFallback = () => ()
const SSRCompatibleSuspense = (props: Parameters< typeof Suspense>['0']) => {
  const isMounted = useMounted();
  
  if (isMounted) {
    return < Suspense {...props} />;
  }
  return < >{props.fallback}< />;
} 
...
return (
  < SSRCompatibleSuspense fallback={< WdAndDxFallback />}>
    < WdAndDxEntry
      className=""
      data={data}
      style={{
        height: 150,
      }}
      />
  < /SSRCompatibleSuspense>
); 

6) Separate Three-Party Database

Configure the dependOn option so modules can be shared among multiple chunks.

//_webpack.config.js_
...
module.exports = {
  entry: {
    index: {
      import: './src/index.js',
      dependOn: 'shared',
  },
  another: {
    import: './src/another-module.js',
    dependOn: 'shared',
  },
  shared: 'lodash',
  }
}
...

CSS Separation

MiniCssExtractPlugin extracts CSS into a separate file. It creates a CSS file for each JS file that contains CSS:

//_webpack.config.js_
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
...
{
  test: /\.(sass|scss)$/,
  use: [
    {
      loader: MiniCssExtractPlugin.loader,
    },
    {
      loader: 'css-loader',
      options: {
        importLoaders: 2,
        sourceMap: !!DEV,
      },
    },
    {
      loader: 'sass-loader',
      options: {
        sourceMap: !!DEV,
      },
    },
  ],
},
...
DEBUG && new BundleAnalyzerPlugin(),
  new MiniCssExtractPlugin({
  filename: '[name].css',
  chunkFilename: '[name].css',
}),
...

Increase The Speed

When the volume of the project increases, the compilation time increases. The time consumed most is the duration of ts type detection. ts-loader provides a transpileOnly option, which is false by default. We can set it to true so the project will not be type-checked, and the declaration file will not be output during the compile time of the project.

//_webpack.config.js_
...
module: {
  rules: [
    {
      test: /\.tsx?$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            transpileOnly: true
          }
        }
      ]
    }
  ]
}
...

You can look at the comparison before and after switching the option.

Before the check is enabled:

$ webpack --mode=production --config ./build/webpack.config.js
Hash: 36308e3786425ccd2e9d
Version: webpack 4.41.0
Time: 2482ms
Built at: 12/20/2019 4:52:43 PM
     Asset       Size  Chunks             Chunk Names
    app.js  932 bytes       0  [emitted]  main
index.html  338 bytes          [emitted]
Entrypoint main = app.js
[0] ./src/index.ts 14 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 489 bytes {0} [built]
    [2](webpack)/buildin/global.js 472 bytes {0} [built]
    [3](webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module
✨  Done in 4.88s.

After the check is closed:

$ webpack --mode=production --config ./build/webpack.config.js
Hash: e5a133a9510259e1f027
Version: webpack 4.41.0
Time: 726ms
Built at: 12/20/2019 4:54:20 PM
Asset       Size  Chunks             Chunk Names
app.js  932 bytes       0  [emitted]  main
index.html  338 bytes          [emitted]
Entrypoint main = app.js
[0] ./src/index.ts 14 bytes {0} [built]
Child html-webpack-plugin for "index.html":
1 asset
Entrypoint undefined = index.html
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 489 bytes {0} [built]
[2](webpack)/buildin/global.js 472 bytes {0} [built]
[3](webpack)/buildin/module.js 497 bytes {0} [built]
+ 1 hidden module
✨  Done in 2.40s.

From 4.88s --> 2.4s, the type check is missing:

An officially recommended solution is to use fork-ts-checker-webpack-plugin, which runs the type inspector on a separate process. This plug-in uses TypeScript instead of webpack module resolution. With TypeScript module resolution, we don't have to wait for webpack compilation. It can speed up compilation.

//_webpack.config.js_
...
module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ]
...

Unify Editor Specifications with editorconfig

Create .editorconfig in the root directory. Make sure it does not conflict with the existing lint rule:

// __.editorconfig__
# 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
indent_size = 4

Antd Configuration

Configure on-demand loading in babel:

{
  "presets": ["@babel/react", "@babel/env"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    [
      "import",
      {
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": true // or 'css'
      },
      "antd"
    ]
  ]
}

Custom themes in webpack:

module: {
    rules: [
      // Process. css.
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // Process. less.
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // less-loader
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                // Replace the antd variable and remove the @ symbol.
                // https://ant.design/docs/react/customize-theme-cn
                modifyVars: {
                  'primary-color': '#1DA57A',
                },
                javascriptEnabled: true, // Support js
              },
            },
          },
        ],
      },
    ]
  }

Note: Styles must be loaded with less format. A common problem is that multiple styles are introduced and less styles are overwritten by CSS styles.

ESlint Configuration

The main functions of ESlint include code format and code quality verification. You can configure pre-commit to regulate code submission.

tnpm install -D eslint eslint-webpack-plugin @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react
  • eslint: The main engine of eslint.
  • eslint-webpack-plugin: webpack loader
  • @ typescript-eslint/parser: Help ESlint lint ts code
  • @ typescript-eslint/eslint-plugin: A plug-in that contains TS extension rules.
  • eslint-plugin-react: Plug-ins that contain React extension rules.

ESlint Configuration File

// _eslintrc_
module.exports =  {
  parser:  '@typescript-eslint/parser',  // ESlint Parser
  extends:  [
    'plugin:react/recommended',  // Select a recommended rule from the @ eslint-plugin-react.
    'plugin:@typescript-eslint/recommended',  // Select a recommended rule from @ typescript-eslint/eslint-plugin.
  ],
  parserOptions:  {
    ecmaVersion:  2018,  // Help transform the most advanced ECMAScript features.
    sourceType:  'module',  // Allow the usage of imports.
    ecmaFeatures:  {
      jsx:  true,  // JSX-compatible
    },
  },
  rules:  {
  },
  settings:  {
    react:  {
      version:  'detect',  // Tell eslint-plugin-react to automatically detect the latest version of react.
    },
  },
};

Prettier Configuration

Although ESLint can verify the code format, Prettier is better at it. They are usually used together in projects. The general solution is to ban the rules that conflict with Prettier in ESLint to avoid conflicts between the two, using Prettier for formatting and ESLint for code verification.

Prettier Configuration File:

{
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "embeddedLanguageFormatting": "auto",
  "htmlWhitespaceSensitivity": "css",
  "insertPragma": false,
  "jsxBracketSameLine": true,
  "jsxSingleQuote": false,
  "printWidth": 100,
  "proseWrap": "preserve",
  "quoteProps": "as-needed",
  "requirePragma": false,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "useTabs": true,
  "vueIndentScriptAndStyle": false
}

Code Submission Specifications

Prettier only ensures that when formatting the code through the editor (vs code), it is formatted into the required format. (It can be automatically formatted when the code is saved by configuring onSave). It cannot guarantee that everyone will take the initiative.

Therefore, automatic formatting is important, and automatic formatting is the most appropriate timing for pre-commit. The code can be formatted before the commit through the git hook. (If it has been committed, the staging will be converted to commit, and the commit record will be generated, which will only be undone after rollback.)

tnpm i -D pretty-quick prettier husky
  • pretty-quick: Coordinated with git-hooks for code detection and fixes
  • husky: You can use the git-hooks through configuration to avoid manual modification.

package.json Settings:

"pretty": "./node_modules/.bin/pretty-quick --staged"
...
"husky": {
    "hooks": {
      "pre-commit": "tnpm run pretty"
    }
  },

Complete Configuration of Webpack

Finally, post the complete configuration. Since Aone releases the automatic update version number, there is no need to split the config file to set the cache according to the environment. The configuration has been simplified as much as possible, and splitting will increase the maintenance cost.

//_webpack.config.js_
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const ESLintPlugin = require('eslint-webpack-plugin');

const { DEV, DEBUG } = process.env;

process.env.BABEL_ENV = DEV ? 'development' : 'production';
process.env.NODE_ENV = DEV ? 'development' : 'production';

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: 'bundle.js',
    clean: true,
  },
  devServer: {
    port: 8080,
  },
  mode: DEV ? 'development' : 'production',
  devtool: DEV && 'source-map',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // Process . less
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // less-loader
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                // Replace the variable of antd and remove the @ symbol.
                // https://ant.design/docs/react/customize-theme-cn
                modifyVars: {
                  'primary-color': '#1DA57A',
                  'border-color-base': '#d9d9d9', // Border Color
                  'text-color': '#d9d9d9'
                },
                javascriptEnabled: true, // Support js
              },
            },
          },
        ],
      },
      {
        test: /\.(sass|scss)$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: !!DEV,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: !!DEV,
            },
          },
        ],
      },
      {
        test: /\.png/,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: false,
        terserOptions: {
          output: {
            comments: false,
          },
        },
      }),
      new OptimizeCSSAssetsPlugin({}),
    ],
    minimize: !DEV,
    splitChunks: {
      minSize: 500000,
      cacheGroups: {
        vendors: false,
      },
    },
  },
  resolve: {
    modules: ['node_modules'],
    extensions: ['.json', '.js', '.jsx', '.ts', '.tsx', '.less', 'scss'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '/src/index.html'),
      filename: 'app.html',
      inject: 'body',
    }),
    DEBUG && new BundleAnalyzerPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[name].css',
    }),
    new ESLintPlugin(),
    new ForkTsCheckerWebpackPlugin(),
  ].filter(Boolean),
};

Summary

This article mainly records the construction journey from project initialization to a standardized frontend project in the development process. It involves related code specifications, development environment construction, and production environment optimization. It aims to create a modern Webpack5.x + React18.x + Typescript + Antd4.x template that can be quickly used for future business scenarios at zero cost.

0 0 0
Share on

HaydenLiu

1 posts | 0 followers

You may also like

Comments

HaydenLiu

1 posts | 0 followers

Related Products