πŸ’‘ preface

After learning Webpack 5 recently, I wanted to build my own project to practice, so I built a scaffolding for React based on Webpack 5.

The scaffolding is configured with related modules, integrated with common functions, easy to build your React new projects, out of the box!

Warehouse Address: “Github”

πŸ”Œ Module/function

  • The frameworkReact
  • routingreact-router-dom
  • Typescript
  • State management libraryredux
  • Style preprocessingless,sass
  • Code detectioneslint
  • git commitPre-specification inspectioncommitlint
  • Time librarydayjs
  • UI libraryantdAnd configure theStyles are introduced as needed,Custom themes
  • react hookslibraryahooks

πŸ’Ύ Directory Structure

The overall directory structure of the project is shown below, with some simple components and pages that can be changed to test usability.

β”‚ β”œ.gitignore // gitignore file list β”‚ β”œ.gitignore // gitignore file list β”‚ Json // typescript configuration β”‚ yarn. Lock β”‚ β”œβ”€public β”‚ index. HTML β”‚ template β”‚ β”œβ”€public β”‚ yarn β”œβ”€ β”œβ”€ exercises // β”œβ”€ exercises // β”‚ β”œβ”€ exercises // β”‚ β”œβ”€ exercises // β”‚ β”œβ”€ exercises // β”‚ β”œβ”€ exercises // β”‚ β”œβ”€ exercises-themeβ”‚ β”‚ env.js β”‚ β”‚ env.js β”‚ β”‚ β”œ ─config β”‚ webpack.htm // β”‚ β”œ ─ SRC β”‚ app.scss β”‚ app.tsx β”‚ β”œ ─ garbage, β”” β”‚ β”œ ─ SCSS β”‚ app.scss β”‚ app.tsx β”‚ Index. The TSX / / entry documents β”‚ β”œ ─ components / / component β”‚ β”” ─ ErrorBoundary / / error boundary β”‚ index. The TSX β”‚ β”œ ─ pages / / page wrote quite a few pages (testing) β”‚ β”œ ─ Admin β”‚ β”‚ Index. The TSX β”‚ β”‚ β”‚ β”” ─ Home β”‚ index. The TSX β”‚ β”œ ─ redux / / story related β”‚ β”‚ actions. The ts β”‚ β”‚ constant. The ts β”‚ β”‚ interface. The ts β”‚ β”‚ store. Ts β”‚ β”‚ β”‚ β”‚ β”” ─ reducers β”‚ count. Ts index. The ts β”‚ β”” ─ asset types / / module statement. Which s style. Which sCopy the code

βœ‚οΈ Main configuration file

package.json

Look at what’s under scripts. Git husky is also configured to automatically detect commit specifications before committing.

{
  "name": "my-react"."version": "1.0.0"."main": "index.js"."license": "MIT"."scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --config ./scripts/config/webpack.dev.js"."build": "cross-env NODE_ENV=production webpack --config ./scripts/config/webpack.prod.js"
  },
  "dependencies": {
  	// ...
  },
  "browserslist": [
    "0.2%" >."not dead"."ie >= 9"."not op_mini all"]."husky": {
    "hooks": {
      "commit-msg": "commitlint --config .commitlintrc.js -e"}}}Copy the code

env.js

Export environment variables.

const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
  isDevelopment,
  isProduction,
};
Copy the code

constant.js

Export the root path, HOST, and POST.

const path = require('path');

const ROOT_PATH = path.resolve(__dirname, '.. / ');

const SERVER_HOST = 'localhost';
const SERVER_PORT = 8080;

module.exports = {
  ROOT_PATH,
  SERVER_HOST,
  SERVER_PORT,
};
Copy the code

webpack.common.js

const path = require('path');
const WebpackBar = require('webpackbar');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');

const { ROOT_PATH } = require('.. /constant');
const { isDevelopment, isProduction } = require('.. /env');
const { myAntd } = require('.. /antd-theme');

const getCssLoaders = () = > {
  const cssLoaders = [
    Use the style - loader / / development mode, production pattern MiniCssExtractPlugin. Loader
    isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
    {
      loader: 'css-loader'.options: {
        modules: {
          // Modularize class names to prevent duplication
          localIdentName: '[local]--[hash:base64:5]',},sourceMap: isDevelopment,
      },
    },
  ];

  // Loader configuration with CSS prefix
  const postcssLoader = {
    loader: 'postcss-loader'.options: {
      postcssOptions: {
        plugins: [
          isProduction && [
            'postcss-preset-env',
            {
              autoprefixer: {
                grid: true,},},],],},},};// The CSS prefix is required only in production mode
  isProduction && cssLoaders.push(postcssLoader);

  return cssLoaders;
};

const getAntdLessLoaders = () = > [
  isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
  {
    loader: 'css-loader'.options: {
      sourceMap: isDevelopment,
    },
  },
  {
    loader: 'less-loader'.options: {
      sourceMap: isDevelopment,
      lessOptions: {
        // antd custom theme
        modifyVars: myAntd,
        javascriptEnabled: true,},},},];module.exports = {
  entry: {
    index: path.resolve(ROOT_PATH, './src/index'),},plugins: [
    / / HTML templates
    new HtmlWebpackPlugin({
      template: path.resolve(ROOT_PATH, './public/index.html'),
      filename: 'index.html'.inject: 'body',}).// Package to display progress bar
    new WebpackBar(),
    // Webpack packaging does not have type checking, forcing TS type checking
    new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: path.resolve(ROOT_PATH, './tsconfig.json'),}}),// Copy resources that do not need to be dynamically imported
    new CopyWebpackPlugin({
      patterns: [{context: 'public'.from: 'assets/*'.to: path.resolve(ROOT_PATH, './build'),
          toType: 'dir'.globOptions: {
            dot: true.gitignore: true.ignore: ['**/index.html'].// ** indicates any directory}},]}),// Automatically deletes the last packaged product
    new CleanWebpackPlugin(),
    // Replace moment.js with day.js in antd
    new AntdDayjsWebpackPlugin(),
  ],

  module: {
    rules: [{test: /\.css$/,
        exclude: /node_modules/,
        use: getCssLoaders(),
      },
      {
        test: /\.less$/,
        exclude: /node_modules/,
        use: [
          ...getCssLoaders(),
          {
            loader: 'less-loader'.options: {
              sourceMap: isDevelopment,
            },
          },
        ],
      },
      {
        test: /\.less$/,
        exclude: /src/,
        use: getAntdLessLoaders(),
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
          ...getCssLoaders(),
          {
            loader: 'sass-loader'.options: {
              sourceMap: isDevelopment,
            },
          },
        ],
      },
      {
        test: /\.(tsx? |js)$/.// ts\tsx\js
        loader: 'babel-loader'.options: { cacheDirectory: true }, // Cache public files
        exclude: /node_modules/}, {test: [/\.bmp$/./\.gif$/./\.jpe?g$/./\.png$/].// Automatically select export as a separate file or URL
        type: 'asset'.parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024,},},}, {test: /\.(eot|svg|ttf|woff|woff2?) $/.// Split it into separate files and export the URL
        type: 'asset/resource',}]},// Configure the alias for the path
  resolve: {
    alias: {
      The '@': path.resolve(ROOT_PATH, './src'),},// If there are no suffixes, check the array to see if the corresponding suffix file exists
    extensions: ['.tsx'.'.ts'.'.js'.'.json'],},/ / cache
  cache: {
    // File system-based persistent caching
    type: 'filesystem'.buildDependencies: {
      // The cache is invalid when the configuration file is changed
      config: [__filename],
    },
  },
};
Copy the code

webpack.dev.js

const path = require('path');
const { merge } = require('webpack-merge');
const webpack = require('webpack');

const common = require('./webpack.common');
const { ROOT_PATH, SERVER_HOST, SERVER_PORT } = require('.. /constant');

module.exports = merge(common, {
  target: 'web'.// Resolve hot update failures
  mode: 'development'.devtool: 'eval-cheap-module-source-map'.output: {
    path: path.resolve(ROOT_PATH, './build'),
    filename: 'js/[name].js',},devServer: {
    host: SERVER_HOST,
    port: SERVER_PORT,
    compress: true./ / gzip compression
    open: true.// Automatically opens the default browser
    hot: true.// Enable the service hot replace configuration
    client: {
      logging: 'warn'.// Logs of warn and above will be printed
      overlay: true.// Displays full screen overlay in the browser when compilation errors or warnings occur
    },
    // Resolve redirect 404 problem
    historyApiFallback: true,},plugins: [
    // Introduce hot substitution
    new webpack.HotModuleReplacementPlugin(),
  ],

  optimization: {
    minimize: false.minimizer: [].// Code split
    splitChunks: {
      chunks: 'all'.minSize: 0,}}});Copy the code

webpack.prod.js

const path = require('path');
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const common = require('./webpack.common');
const { ROOT_PATH } = require('.. /constant');

module.exports = merge(common, {
  target: 'browserslist'.mode: 'production'.devtool: false.output: {
    path: path.resolve(ROOT_PATH, './build'),
    filename: 'js/[name].[contenthash:8].js'./ / resources
    assetModuleFilename: 'assets/[name].[contenthash:8].[ext]',},plugins: [
    / / production mode using the MiniCssExtractPlugin loader, you need to use MiniCssExtractPlugin
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'.chunkFilename: 'css/[name].[contenthash:8].chunk.css',}).// Check the package size and enable a local server
    new BundleAnalyzerPlugin(),
  ],

  // Store optimally packaged configurations
  optimization: {
    minimize: true.minimizer: [
      new CssMinimizerPlugin(),
      / / JS compressed
      new TerserPlugin({
        extractComments: false.// Remove all comments
        terserOptions: {
          compress: { pure_funcs: ['console.log']},// Remove all console.log functions}}),].// Code split
    splitChunks: {
      chunks: 'all'.minSize: 0,}}});Copy the code

πŸ“ Problems encountered

BrowserRouter development environment 404 issue

Add an entry to webpack.dev.js that will return the index.html file for any request, solving the problem of route hops for single-page applications.

devServer: {
  // ...
  historyApiFallback: true,}Copy the code

Failed to install Node-sass. Procedure

Install node-gyp globally:

npm install -g node-gyp
Copy the code

Go to the root directory of the project and install yarn again.

Antd styles are loaded on demand

Install babel-plugin-import and add the following item to the plugins in the.babelrc file:

{
  "plugins": [["import", {
      "libraryName": "antd"."libraryDirectory": "es"."style": true  // 'style: true' will load less files}}]]Copy the code

Normal use, no need to introduce style:

import React from 'react';
import { Button } from 'antd';
import { useTitle } from 'ahooks';

const Admin: React.FC = () = > {
  useTitle('Admin');
  return <Button type='primary'>button</Button>;
};

export default Admin;
Copy the code

CSS – Module conflicts with ANTD style

When the CSS – Loader is configured with modular import, the following output is displayed:

// ...
{
  loader: 'css-loader'.options: {
    modules: {
      // Modularize class names to prevent duplication
      localIdentName: '[local]--[hash:base64:5]',},sourceMap: isDevelopment,
  },
}
// ...
Copy the code

Found that the style of ANTD is not displayed. The reason is that modularity also applies to files in node_modules. The styles introduced in ANTD are also modularized, but the imported components are still normal class names, so they don’t show up.

The solution is to separate the business code written by myself from the code configuration of the third party library. Since antD has configured “style”: true when loading the configuration on demand, it needs to configure less separately, and only enable module in the business code:

module.exports = {
  // ...
  
  module: {
    rules: [{test: /\.less$/,
        exclude: /node_modules/.// Exclude third-party library code
        use: [
          ...getCssLoaders(), // The configuration is normal
          {
            loader: 'less-loader'.options: {
              sourceMap: isDevelopment,
            },
          },
        ],
      },
      {
        test: /\.less$/,
        exclude: /src/.// Exclude business code
        use: getAntdLessLoaders(), // Do not enable module
      },
      // ...],},// ...
};
Copy the code

Antd custom themes

Deal with less. Note to exclude business code, do not enable module:

// antD custom theme configuration

const myAntd = {
  'primary-color': '#1DA57A'.'link-color': '#1DA57A'.'border-radius-base': '8px'};module.exports = {
  myAntd,
};
Copy the code
const { myAntd } = require('.. /antd-theme');

/ /...
const getAntdLessLoaders = () = > [
  isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
  {
    loader: 'css-loader'.options: {
      sourceMap: isDevelopment,
    },
  },
  {
    loader: 'less-loader'.options: {
      sourceMap: isDevelopment,
      lessOptions: {
        // antd custom theme
        modifyVars: myAntd,
        javascriptEnabled: true,},},},];/ /...

{
  test: /\.less$/,
  exclude: /src/,
  use: getAntdLessLoaders(),
}
    
/ /...
Copy the code

This article records what I have learned, if there is anything wrong, welcome criticism