preface

When using the React library, many students use the create-react-app official scaffolding to generate react projects. The disadvantage of this is that our project configuration is not flexible enough. Although we can use eject command to expose some configuration, it is not written by us, so it is very difficult to change the configuration. However, if we build the project ourselves, it will be easy for us to change the configuration, and the process will give us a lot of knowledge and a basic understanding of how to use WebPack

This article requires some basic knowledge of Webpack, if you are not very good at it, please go to the webpack official website first

Consistent version number is very important!!

[email protected]

[email protected]

[email protected]

Directory structure

MyReact/
|-- config/
|   |-- webpack.base.js
|   |-- webpack.dev.js
|   |-- webpack.pro.js
|-- dist/
|-- images/
|-- pages/
|   |-- Home
|-- public/
|   |-- index.html
|-- App.js
|-- index.js
|-- index.less
|-- package.json
|-- postcss.config.js 
|-- yarn.lock
|-- README
Copy the code

1. Generate a package management file

Perform NPM init

The package management file is used to manage node_modules. Here are some common configurations

{
  "name": "project".// If the project wants to publish NPM, this is the name of the NPM package. If not set, it cannot be published and downloaded
  "version": "1.0.0".// Package version number. The release version must be different
  "description": "".// Description of the packet
  "main": "dist/main.js".// Package entry file
  "scripts": {
    // NPM script commands can encapsulate some functions and perform quick operations such as NPM run test
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [].// Package keyword information
  "author": ""./ / the author
  "license": "ISC".// Open source protocol name
  "files": [
    // The file or folder to publish
    "dist"."package.json"]."dependencies": {
    // Production dependencies, which refer to packages that are also needed after they go live, are packaged into dist
  },
  "devDependencies": {
    // Development dependencies, which are only packages used during development, are not packaged into dist
  },
  "browserslist": {
    // To be compatible with the browser, it is mainly used by the following tools:
    //Autoprefixer
    //Babel
    //post-preset-env
    //eslint-plugin-compat
    //stylelint-unsupported-browser-features
    //postcss-normalize
    "production": [].// Production environment support
    "development": [] // Development environment support}}Copy the code

2. Download required dependencies

Yarn or NPM is recommended (NPM can cut the mirror source if it is slow)

npm install react react-dom -S

npm install webpack webpack-cli webpack-dev-server -D

After downloading the dependencies, create index.html in the public folder. This HTML file will be used as our template

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
Copy the code

Then create the entry files index.js and app.js

//index.js
import ReactDom from 'react-dom';
import App from './App';

// Render component
ReactDom.render(<App/>.document.querySelector('#root'));
Copy the code
//App.js
import React from 'react';

const App = () = > {
    return <div>
        <h1>App</h1>
    </div>
}

export default App;
Copy the code

3. Configure the WebPack development file

Because we write React using JSX syntax, the browser doesn’t know this syntax, so we convert these files. Here we use Babel plugins, which we use when compiling files, so we download them to our development dependency

npm install babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin -D

Create webpack.dev.js in the config folder

//config/webpack.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const resolve = p= > path.resolve(__dirname, p);


module.exports = {
    / / mode
    mode: "development".// the entry tells WebPack where to start and it will find all dependencies and process them
    entry: resolve('.. /index.js'),
    / / export
    output: {
        // The directory name of the final output
        path: resolve('dist'),
        // The final output file name
        filename: "main.js"
    },
    module: {
        rules: [{// Matches files of type JS or JSX
                test: /\.jsx? $/.// exclude files under node_modules, because files under node_module don't need to be handled by us, others have already handled them
                exclude: /node_modules/.// The loader to use
                use: {
                    loader: 'babel-loader'.options: {
                        presets: ['@babel/preset-env'.'@babel/preset-react']}}}]},// The plug-in used
    plugins: [
        // Generate an HTML file
        new HtmlWebpackPlugin({
            // Directory to use HTML templates
            template: resolve('.. /public/index.html'),
            // Automatically import packaged files, default true
            inject: true})].// Development server configuration
    devServer: {
        static: {
            / / directory
            directory: resolve('dist')},/ / compression
        compress: true./ / hot update
        hot: true.// Whether to open the browser automatically
        open: true./ / the port number
        port: 8888}}Copy the code

Configure it in the scripts of package.json

 "scripts": {
    "dev": "webpack-dev-server --config=config/webpack.dev.js"
  }
Copy the code

Then run NPM run start

Configure the path alias and ignore the suffix name

 resolve: {
        extensions: ['.js'.'.json'].alias: {
            "@": path.resolve(__dirname, 'pages/')}}Copy the code

We continued to change the configuration to support CSS files and also downloaded them to the development dependencies

npm i style-loader css-loader postcss-loader postcss-preset-env -D

Create postcss.config.js in the root directory

//postcss.config.js
module.exports = {
    plugins: [
        // Automatically prefixes compatible styles with browsers
        require('postcss-preset-env')]}Copy the code

Added configuration in package.json

{
  "browserslist": {
    "production": [
      "0.2%" >."not dead"."not op_mini all"]."development": [
      "last 1 chrome version"."last 1 firefox version"."last 1 safari version"]}}Copy the code

Then create index.css in the root directory and import it in index.js

/* * index.css */
html.body{
  height: 100%;
  background: #f5f5f5;
}
h1{
    color: red;
  }
Copy the code

Added new configuration to rules in webpack.dev.js

    {
        test: /\.css$/,
        exclude: /node_modules/./* * The order in which the loader is executed is important */
         //style-loader generates the style tag and inserts it into the HTML
         //css-loader Loader that processes CSS files
         // PostCSs-loader is also used to process CSS files, and should be used in conjunction with a third-party library, such as postCSs-env autofixer
        use:['style-loader'.'css-loader'.'postcss-loader']}Copy the code

Then we import our CSS file and see that the style works.

Of course, our projects usually do not use CSS files directly, but only use CSS preprocessors such as LESS or SCSS, so let’s configure less preprocessors to support less files

npm i less less-loader -D

Change the WebPack Rules configuration so that the LESS and CSS files can be processed simultaneously

 {
     test: /\.(less|css)$/,
     exclude: /node_modules/,
     use: [
            'style-loader'.'css-loader'.'postcss-loader'.'less-loader']}Copy the code

Then change the index. CSS to index.less and write some nested syntax. Find that it also works.

If you need to use other preprocessing principles are the same, you can configure different configurations according to different requirements

Then configure static resources, Webpack5 does not need to use file-loader or URl-loader to process pictures, it has internal support for static resources processing, and can configure limit to output small pictures into Base64 format to reduce the number of requests

{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    type: 'asset/resource'.generator: {
        filename: 'images/[hash][ext][query]'}}Copy the code

And then we took a picture and we introduced it and it worked

3. Configure the WebPack packaging environment

In fact, the configuration of production environment and development environment is almost the same, except for some file optimization and construction speed optimization, such as compression of CSS files, compression of HTML files, enabling multi-process packaging and use of cache, etc

New webpack. Pro. Js

// config/webpack.pro.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require("clean-webpack-plugin"); // The plugin used to clean up the directory of the last packed file
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // Package analysis tools
const MiniCssExtractPlugin = require('mini-css-extract-plugin');// Extract CSS into separate files
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // Compress the CSS file
const resolve = p= > path.resolve(__dirname, p);

module.exports = {
    / / mode
    mode: "production".// the entry tells WebPack where to start and it will find all dependencies and process them
    entry: resolve('.. /index.js'),
    / / export
    output: {
        // The directory name of the final output
        path: resolve('.. /dist'),
        // The final output file name
        filename: "./js/[name].js"
    },
    module: {
        rules: [{// One type of file matches only one Loader to improve performance
                oneOf: [{// Matches files of type JS or JSX
                        test: /\.jsx? $/.// exclude files under node_modules, because files under node_module don't need to be handled by us, the author already handled them
                        exclude: /node_modules/.// The loader to use
                        use: {
                            loader: 'babel-loader'.options: {
                                presets: ['@babel/preset-env'.'@babel/preset-react'}}}, {test: /\.(less|css)$/,
                        exclude: /node_modules/,
                        use: [
                            MiniCssExtractPlugin.loader,
                            'css-loader'.'postcss-loader'.'less-loader'] {},test: /\.(png|svg|jpg|jpeg|gif)$/i,
                        type: 'asset/resource'.generator: {
                            filename: 'images/[hash][ext][query]'}}]}]},// Can be used to improve performance, code segmentation,
    optimization: {
        // Tell WebPack to compress bundles using TerserPlugin or other plugins defined in Optimization.minimizer.
        minimize: true.// Split the module
        splitChunks: {
            chunks: 'all'
        },
        // Allows you to override the default minimizer by providing one or more custom TerserPlugin instances.
        minimizer: [
            new CssMinimizerPlugin()
        ]
    },
    // The plug-in used
    plugins: [
        new CleanWebpackPlugin(),
        new BundleAnalyzerPlugin(),
        new MiniCssExtractPlugin({
            // Output directory
            filename: 'css/[hash].css'
        }),
        // Generate an HTML file
        new HtmlWebpackPlugin({
            // Directory to use HTML templates
            template: resolve('.. /public/index.html'),
            // Automatically import packaged files, default true
            inject: true}})]Copy the code

Added the build shortcut command

 "scripts": {
    "build": "webpack --config=config/webpack.pro.js"
  }
Copy the code

We can run NPM run build and find a dist folder in the root directory. The dist folder is the result of the bundle. Webpack-bundle-analyzer will also open a page to help us see the size of the bundle. This allows us to see the optimization space more clearly

4. Optimize the configuration file

In fact, it is not hard to see that our dev and Pro files have a lot of redundant code, so if we want to change the configuration of the two files, we need to change them again, and there is some maintenance cost, so we can extract the common parts

Create webpack.base.js under config and extract the common configuration,

//config/webpack.base.js
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const resolve = p= > path.resolve(__dirname, p);
module.exports = {
    // the entry tells WebPack where to start and it will find all dependencies and process them
    entry: resolve('.. /index.js'),
    / / export
    output: {
        // The directory name of the final output
        path: resolve('.. /dist'),
        // The final output file name
        filename:'./js/[name].js'
    },
    resolve: {
        extensions: ['.js'.'.json'.'.wasm'].alias: {
            "@": path.resolve(__dirname, 'components/')}},module: {
        rules: [{// Matches files of type JS or JSX
                test: /.jsx? $/.// exclude files under node_modules, because files under node_module don't need to be handled by us, others have already handled them
                exclude: /node_modules/.// The loader to use
                use: {
                    loader: 'babel-loader'.options: {
                        presets: ['@babel/preset-env'.'@babel/preset-react'}}}, {test: /.(le|c)ss$/,
                exclude: /node_modules/,
                use: [
                    'style-loader'.'css-loader'.'postcss-loader'.'less-loader'] {},test: /.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource'.generator: {
                    filename: 'images/[hash][ext][query]'}}},// The plug-in used
    plugins: [
        // Generate an HTML file
        new HtmlWebpackPlugin({
            // Directory to use HTML templates
            template: resolve('.. /public/index.html'),
            // Automatically import packaged files, default true
            inject: true
        }),
        // Use the plug-in to define the global variable DEV
        new webpack.DefinePlugin({
            'process':JSON.stringify({
                env:'development'})})],node: {
        global: true}},Copy the code

Modify the webpack. Dev. Js

//config/webpack.dev.js
const path = require("path");
const resolve = p= > path.resolve(__dirname, p);
const webpackBaseConfig = require('./webpack.base');// Webpack common configuration
const {merge} = require('webpack-merge'); // Used to merge webPack configuration
const webpackDevConfig = {
    devtool: 'source-map'./ / mode
    mode: "development".// Development server configuration
    devServer: {
        static: {
            / / directory
            directory: resolve('dist')},/ / compression
        compress: true./ / hot update
        hot: true.// Whether to open the browser automatically
        open: true./ / the port number
        port: 8888}}const result = merge(webpackBaseConfig, webpackDevConfig)
module.exports = result;
Copy the code

Modify the webpack. Pro. Js

//config/webpack.pro.js
const {CleanWebpackPlugin} = require("clean-webpack-plugin"); // The plugin used to clean up the directory of the last packed file
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // Package analysis tools
const MiniCssExtractPlugin = require('mini-css-extract-plugin');// Extract CSS into separate files
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // Compress the CSS file
const webpackBaseConfig = require('./webpack.base');// Webpack common configuration
const {merge} = require('webpack-merge'); // Used to merge webPack configuration
const webpack = require('webpack');
const webpackProConfig = {
    / / mode
    mode: "production".// Can be used to improve performance, code segmentation,
    optimization: {
        // Tell WebPack to compress bundles using TerserPlugin or other plugins defined in Optimization.minimizer.
        minimize: true.// Split the module
        splitChunks: {
            chunks: 'all'
        },
        // Allows you to override the default minimizer by providing one or more custom TerserPlugin instances.
        minimizer: [
            new CssMinimizerPlugin()
        ]
    },
    // The plug-in used
    plugins: [
        new CleanWebpackPlugin(),
        new BundleAnalyzerPlugin(),
        new MiniCssExtractPlugin({
            // Output directory
            filename: 'css/[hash].css'
        }),
        // Use the plug-in to define the global variable DEV
        new webpack.DefinePlugin({
            'process':JSON.stringify({
                env:'production'})})],module: {
        rules: [{test: /.(le|c)ss$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'.'postcss-loader'.'less-loader']]}},// Do not compile into the dist package to optimize the dist volume
    externals: {}}module.exports = merge(webpackBaseConfig, webpackProConfig);
Copy the code

5. At the end

So one of our most basic configuration will build good, basically can meet the daily use, compared with the official scaffolding configuration a lot less, but built by ourselves, so that flexibility is higher, if your project is not very complicated can consider to use their own building project, but if the project is large or a official is better, The official configuration is more mature and better suited to our needs

Git repository address is attached, if you are interested, please go and type it yourself!!