preface

Our goal is to use Webpack to build a scaffolding based on React + React-Router + DVA + ES6 + less + ANTD for middle and background development. Students may ask why they need to build their own scaffolding because there are so many excellent scaffolding in the community. But for me, sharing is also a process of learning again. Only realizing it by myself can make a deeper impression. Generally speaking, it basically holds one idea: sharing in learning and learning in sharing.

Initialize the project

mkdir React-Whole-barrels  // Create a project folder with an arbitrary name
cd  React-Whole-barrels        
mkdir src                  // Create a new SRC folder
npm init -y   // Initialize the project
Copy the code

Create index.js and index.html files:

// index.js
console.log('hello world')
// index.html<! DOCTYPE html><html lang="en">
<head>
	<meta charset="UTF-8">
	<title>React-Whole-barrels</title>
</head>
<body>
	<div id='root'></div>
</body>
</html>
Copy the code

Install webPack and configure it

Install webpack

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

Create a build folder to store the WbePack configuration file

mkdir build
touch webpack.dev.js
Copy the code

Webpack.dev.js and write the basic configuration

const path = require('path');

module.exports  = {
    mode: 'development'.// mode: indicates the dev environment
    entry: './src/index.js'.// Import file
    module: {},               // Let WebPack handle those non-javascript files
    plugins: [],              / / the plugin
    output: {
        filename: 'bundle.js'.// The name of the packaged file
        path: path.resolve(__dirname, '.. /dist') // Directory for storing the packaged folder}}Copy the code

Package. The json changes

  "scripts": {
    "start": "webpack --config ./build/webpack.dev.js"
  },
Copy the code

Configure the Babel

Since react and ES6 are used, the configuration of Babel is essential

npm install @babel/polyfill core-js@2  --save
// @babel/polyfill: Emulates an ES6 + environment, providing shippers for ES6 methods and functions
// core-js@2: @babel/preset-env implementation declares core-js version when polyfill is introduced on demand
npm install babel-loader @babel/core  @babel/preset-env @babel/preset-react  --save-dev
// babel-loader and @babel/core are core modules
// @babel/preset-env is an intelligent preset that allows you to use the latest JavaScript
// @babel/preset-react Switch JSX
Copy the code

Extension: If you are developing a library and want to implement on-demand substitution, you can use the following two tools to implement @babel/ plugin-transform-Runtime to avoid polyfill contamination of global variables and reduce the packaging volume. @babel/runtime/corejs2 is more suitable as a development tool library. It is equivalent to @babel/runtime + babel-polyfill. You don’t have to use @babel/ Runtime anymore

. Babelrc file

{
    "presets": [["@babel/preset-env", {  // Translate es6 syntax into ES5 syntax
            "targets": {
               "chrome": "67",},"useBuiltIns": "usage".// Add @babel/polyfill as needed.
            "corejs": "2",}],"@babel/preset-react",]."plugins": [
		"@babel/plugin-proposal-class-properties"]}Copy the code

1. The solution: Support for the experimental syntax ‘classProperties’ isn’t currently enabled NPM i-d@babel /plugin-proposal-class-properties and configure it in plugins. UseBuiltIns and transform-Runtime cannot be used at the same time. If transform-runtime is used, do not use useBuiltInsor

Change the webpack. Dev. Js

. module: {rules: [{
            test: /\.js$/.exclude: /node_modules/.// exclude code in node_modules
            use: [{
                loader: 'babel-loader'}],}},Copy the code

Install the react

Install react, react-dom, and react-router

npm install react react-dom react-router react-router-dom --save
Copy the code

This part of the code is more, you can see the SRC code on Github

webpack-dev-server

After writing the above code and running NPM start, open index.html and you will find nothing. At this point, we need to configure a simple WEB server pointing to index.html.

/ / install webpack - dev - server
npm install webpack-dev-server --save-dev

/ / configuration webpack. Dev. Js. devServer: {contentBase: path.join(__dirname, '.. /dist')},...// Change the NPM start command
    "start": "webpack-dev-server --config ./build/webpack.dev.js"
Copy the code

Run the NPM start command to see the contents of our code. See here for more configuration for dev

Use the htML-webpack-plugin and clean-webpack-plugin plug-ins

So far, we’ve found that we need to manually put index.html into the dist folder and manually import bundle.js. This problem can be solved with the HTML-webpack-plugin. After the htML-webpack-plugin is introduced, an instance is generated in plugins. HtmlWebpackPlugin can accept a parameter as a template file, and an HTML file with the parameter as template is automatically generated after packaging. And the package generated JS file automatically into the HTML file. The clean-webpack-plugin can be used to clean up the last package before each package, thus avoiding redundant files. It is also used to generate an instance directly in plugins.

// Install html-webpack-plugin and clean-webpack-plugin
npm install html-webpack-plugin clean-webpack-plugin --save-dev
Copy the code

Change the webpack. Dev. Js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); . plugins: [/ / the plugin
        new HtmlWebpackPlugin({   // Automatically add template HTML to the dist file
            template: 'src/index.html',}).new CleanWebpackPlugin(), // Clean up the dist file before HtmlWebpackPlugin runs].Copy the code

Less configuration

Style using less preprocessor, then need to use less,less-loader, CSS -loader,style-loader, etc

npm install less less-loader css-loader style-loader postcss-loader --save-dev
// less-loader compiles less
// css-loader // Compiles CSS
// style-loader creates a style tag and adds CSS to it
// PostCSs-loader provides the function of automatically adding vendor prefixes. However, it needs to be used together with the autoprefixer plug-in
npm install autoprefixer --save-dev
Copy the code

Change the webpack.dev.js configuration

rules: [
  ...
      {
            test: /\.less$/.exclude: /node_modules/.use: ['style-loader',
                {
                    loader: 'css-loader'.options: {
                        importLoaders: 2}},'less-loader'.'postcss-loader'] {},test: /\.css$/.use: ['style-loader'.'css-loader'.'postcss-loader']}]Copy the code

postcss.config.js

module.exports = { // Add the CSS vendor prefix automatically
    plugins: [
        require('autoprefixer')]}Copy the code

ICONS and pictures processing

Install file-loader and url-loader

npm install file-loader url-loader --save-dev
Copy the code

Filr-loader helps us do two things: 1. When we come across an image file, we pack it and move it to the dist directory 2. The address of the image module is then obtained and returned to the import module into the variable

Url-loader can basically realize the function of file-loader, but the difference is that there is no image file under the DIST file packaged by URL-Laoder. This is because url-loader converts images to base64 strings directly into bundle.js. The advantage of webpack.dev.js is that it packs images directly into JS, without adding additional images to the request. The disadvantage of HTTP requests is that if the package file is very large, the load will take a long time and affect the experience so we can configure webpack.dev.js like this

rules: [
  ...
       {
            test: /\.(png|jpg|gif|jpeg)$/.use: {
                loader: 'url-loader'.options: {
                    name: '[name]_[hash].[ext]'.// placeholder
                    outputPath: 'images/'.// Package the file name
                    limit: 204800.// If the size is smaller than 200KB, pack it into a js file. If the size is larger than 200KB, use file-loader to pack it into imgages}},}, {test: /\.(eot|woff2? |ttf|svg)$/.use: {
                loader: 'url-loader'.options: {
                    name: '[name]-[hash:5].min.[ext]'.// Same as above
                    outputPath: 'fonts/'.limit: 5000,}},}]Copy the code

Module hot replacement HMR

Module hot replacement also known as HMR, code updates only update the modified parts of the display. There are the following

  • More convenient for style debugging
  • Only the parts of the modified code are updated to improve development efficiency
  • Preserve application state that is lost when the page is fully reloaded.

There are two ways to configure HMR, one is cli and the other is Node.js API. We use the second way here. If you want to know the implementation of two kinds of HMR and the implementation principle of HMR, you can see here. We implement HMR using the plugins Webpack-dev-Middleware and Webpack-hot-middleware under a custom development service

// Install webpack-dev-middleware webpack-hot-middleware
npm install webpack-dev-middleware webpack-hot-middleware --save-dev
// Don't forget to install Express, which is how we start the local service
npm install express --save-dev
Copy the code

New dev – server. Js

const path = require('path');
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require("webpack-hot-middleware")
const config = require('./webpack.dev.js');

const complier = webpack(config);   // The compiler repackages the code once it executes
const app = express();  // Generate an instance
const DIST_DIR = path.resolve(__dirname, '.. / '.'dist');  // Set the static access file path

let devMiddleware = webpackDevMiddleware(complier, {
    quiet: true.noInfo: true.stats: 'minimal'
})

let hotMiddleware = webpackHotMiddleware(complier,{
    log: false.heartbeat: 2000
 })

app.use(devMiddleware)

app.use(hotMiddleware)

// Set the path to access static files
app.use(express.static(DIST_DIR))


app.listen(8080, () = > {console.log("Successful startup: localhost:"+ 8080)})// Listen on the port
Copy the code

Change webpack.dev.js to add the following:

const webpack = require('webpack'); . entry: {Webpack-hot-middleware /client? NoInfo =true&reload=true is mandatory
        main: ['webpack-hot-middleware/client? noInfo=true&reload=true'.'./src/index.js']},... plugins: [ ... new webpack.NamedModulesPlugin(),// Used to display the relative path of the module when starting HMR
        new webpack.HotModuleReplacementPlugin(), // Enable module hot update. Different from module hot update, hot loading is the whole page refresh
    ]
Copy the code

More webpack-hot-Middleware configurations are available here

Modify the startup command:

"start": "node ./build/dev-server.js"
Copy the code

Install ANTD and DVA

npm install antd babel-plugin-import --save
Copy the code

Once antD is installed, you can use the UI components in AND-Design. Use babel-plugin-import to implement the loading on demand effect. Babelrc

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

After successful installation, overwrite index.js and create router.js

// index.js
import dva from 'dva';
// 1. Initialize
const app = dva({});
// app.use();
// 2. Model
// app.model();
// 3. Router
app.router(require('./router').default);
// 4. Start
app.start('#root');
Copy the code

The amount of code here is a bit too much to list, you can view it on Github

When using the React -router, you may GET an error or Cannot GET when you hit refresh. There are two solutions. 1. Change BrowserRouter to HashRouter. 2. DevServer must set historyApiFallback: true because of the custom service we are using, we can use connect-history-api-fallback to implement the same functionality as historyApiFallback. See the specific implementation of the code

public path

By deploying resources around the world, CDN enables users to access resources nearby and speeds up access. If we upload static resources of a web page to a CDN service, publicPath will fill in the URL provided by the CDN when accessing these resources. We currently use /, as opposed to the current path, because our resources are in the same folder. ” “Webpack. Dev. Js

    output: {
        ...
        publicPath : '/'
    }
Copy the code

Using sourcemap

SourceMap is essentially a mapping relationship where the code in the packaged JS file can be mapped to the specific location of the code file. This mapping relationship helps us find errors directly in the source code. You can use it directly in DevTool. Proper use of Source-Map can help us improve development efficiency and locate errors faster. Devtool configurations are different for production and development environments. We can add devtool in webpack.dev.js.

devtool:"cheap-module-eval-source-map".// Development environment configuration best practices
devtool:"cheap-module-source-map".// Production configuration best practices
Copy the code

Production environment construction

So far we have configured webpacks for development environments. The build goals for development and production environments are very different, and in production environments, we have shifted to focus on smaller bundles, lighter source maps, As well as more optimized resources to improve load times. New webpack. Prod. Js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    mode: "production".Tree shaking is automatically enabled whenever the code is in production mode
    devtool:"cheap-module-source-map".entry: {                  // Import file
        main: './src/index.js'
    },  
    module: {                 
        rules:... omit// Same code as dev
    },
    plugins: [                     
        new HtmlWebpackPlugin({   
            template: 'src/index.html',}).new CleanWebpackPlugin(), 
    ],
    output: {
        publicPath: "/".filename: 'bundle.js'.path: path.resolve(__dirname, '.. /dist')}}Copy the code

Add a packaging script,

    "build": "webpack --config ./build/webpack.prod.js"
Copy the code

After executing the NPM run build, you will find that a series of files have been generated in the dist folder. You’ll notice that the production configuration is much the same as the development configuration. Next we’ll tune the WebPack configuration.

Extracting common configuration

There are many configuration pairs in webpack.dev.js and webpack.prod.js. We can extract the common configuration and use webpack-merge to merge the configurations in different environments.

npm install webpack-merge --save
Copy the code

Webpack configuration file changes webpack.dev.js

. const merge =require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
    mode: 'development'.devtool:"cheap-module-eval-source-map".entry: {                  
        main: ['webpack-hot-middleware/client? noInfo=true&reload=true'.'./src/index.js']},devServer: {
        contentBase: path.join(__dirname, '.. /dist')},plugins: [                     
        new webpack.NamedModulesPlugin(),  
        new webpack.HotModuleReplacementPlugin(), 
    ],
    output: {}}module.exports = merge.smart(commonConfig, devConfig)
Copy the code

webpack.prod.js

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const prodConfig = {
    mode: "production".// Whenever in production mode, the code is automatically compressed
    devtool:"cheap-module-source-map".entry: {
        main: './src/index.js'
    },  
    module: {},
    plugins: [].output: {}}module.exports = merge.smart(commonConfig, prodConfig)
Copy the code

webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const commonConfig = {
    module: {... Too many omissions},plugins: [                     
        new HtmlWebpackPlugin({   
            template: 'src/index.html',}).new CleanWebpackPlugin(), 
    ],
    output: {
        publicPath: "/".filename: 'bundle.js'.path: path.resolve(__dirname, '.. /dist')}}module.exports = commonConfig;
Copy the code

You can see the details here.

CSS file code segmentation

To package our CSS files separately, we need to use the mini-CSS-extract-plugin, which currently does not support HMR, so in order not to affect the development efficiency, we use it in the build environment. Optimize – CSS-assets-webpack-plugin this plugin can help us combine the same styles. The CSS-split-webpack-plugin allows you to split large CSS files

npm install mini-css-extract-plugin optimize-css-assets-webpack-plugin css-split-webpack-plugin --save-dev
Copy the code

Modify webpack.prod.js and synchronously modify webpack.common.js and webpack.dev.js see here

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CSSSplitWebpackPlugin = require('css-split-webpack-plugin').default; . module: {rules: [{
            test: /\.less$/.exclude: /node_modules/.use: [MiniCssExtractPlugin.loader,
                {
                    loader: 'css-loader'.options: {
                        importLoaders: 2}},'less-loader'.'postcss-loader'] {},test: /\.css$/.use: [MiniCssExtractPlugin.loader, 'css-loader'.'postcss-loader']]}},optimization: {
		minimizer: [new OptimizeCSSAssetsPlugin({})]
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].css'.chunkFilename: '[name].chunk.css'
		}),
      new CSSSplitWebpackPlugin({
            size: 4000.filename: '[name]-[part].[ext]'})]Copy the code

Browser cache (Cathing)

To solve the problem of the browser file cache, for example, the file name does not change after the code is updated, and the browser does not force the refresh. When the browser requests the file, the file name is not changed and the browser directly reads the file from the cache without requesting it again. We can add hash values to the webpack.prod.js output file name. The reason for using HashedModuleIdsPlugin is that when you change a file, you can change the hash value of that file instead of changing all files.

plugins: [
    ...
        new webpack.HashedModuleIdsPlugin(),  // Generate a four-digit hash based on the relative path of the module
        new webpack.optimize.CommonsChunkPlugin({ // Work with the plugins above
            name: 'runtime'})].output: {
        filename: '[name].[contenthash].js'.// Key value corresponding to entry
        chunkFilename: '[name].[contenthash].js'.// Indirectly referenced files go through this configuration
    },
Copy the code

After running the NPM run build command, you can find that the JS file name in the dist file already has the hash value

Remember to modify webpack.common.js and webpack.dev.js synchronously. If you don’t know how to modify webpack.common.js, see here

File path optimization

After extension configuration, it is not necessary to add file extension name in require or import. It will try to add extension name in order to match the file name after mainFiles configuration. Alias is configured to speed up webPack’s search for modules. Webpack.com mon. Js changes:

    resolve: {       
        extensions: ['.js'.'.jsx'].// When importing a file as import login from './login/index', it looks for the.js file first, and then for the.jsx file
        mainFiles: ['index'.'view'].// If you refer to a folder directly, go back to the file that starts with index, and if it doesn't exist, go back to the file that starts with view
        // alias: {// alias:
        // home: path.resolve(__dirname, '.. / SRC /home') // Configure aliases to speed up webpack's module lookup
        / /}
    }
Copy the code

Tree Shaking

Tree Shaking can remove unreferenced parts of a file, and it only supports ES Modules, not CommonJS. Reason: ES Modules is static import, CommonJS is dynamic import, Tree Shaking only supports static import.

// webpack.dev.js    
  optimization: {   // For the development environment
        usedExports: true
    },
        
// package.json
  "sideEffects": [
    "*.css"].// If you use a csS-loader like this in your project and import a CSS file, you need to add it to the Side Effect list to avoid accidentally removing it in production mode
Copy the code

Note: When the mode option is set to Production, minification and tree shaking are automatically enabled

SplitChunksPlugin

SplitChunksPlugin can be used for both synchronous and asynchronous code splitting, which can separate third-party libraries from business code. Webpack.prod.js

. optimization: {splitChunks: {
            chunks: "all".// Works only for asynchronous import codes, and setting all and configuring both vendors works
            minSize: 30000.// Code splitting is done only when the imported library is larger than 30KB
            minChunks: 1.// A module is not split until it has been used at least once
            maxAsyncRequests: 5.// The maximum number of modules to load asynchronously at the same time is 5, if more than 5, no code splitting is done
            maxInitialRequests: 3.// The imported library can split up to 3 js files when loading the import file
            automaticNameDelimiter: '~'.// Generate a file link for the file name
            name: true.// Enable the custom name effect
            cacheGroups: {  // Determine where to put the split code
                vendors: {   // used with chunks: 'all' to indicate that if the library is introduced in node-modules, the library is partitioned off and vendors. Js
                    test: /[\\/]node_modules[\\/]/.priority: - 10.filename: 'vendors.js'
                },
                default: {  // Set the default store name for code that is not split from the Node-modules library
                    priority: - 20.reuseExistingChunk: true.// Avoid being repackaged and split
                    filename: 'common.js'}}}}Copy the code

DellPlugin improves packaging speed

For third party libraries, the libraries in a long period of time, the basic will not update, when packaged separate packaging to improve packing speed, DllPlugin dynamic link library plug-in, the principle of which is dependent on the page with abstract base module packaging to the DLL file, when you need to import the exists in a DLL module, the module will no longer be packaged, I’m going to get it in a DLL.

cnpm install add-asset-html-webpack-plugin fs  --save
// add-asset-html-webpack-plugin: automatically import packaged DLL. Js files into HTML
// fs file read
Copy the code

New webpack. DLL. Js

const path = require('path');
const webpack = require('webpack');

module.exports = {
	mode: 'production'.entry: {
		vendors: ['lodash'].react: ['react'.'react-dom'],},output: {
		filename: '[name].dll.js'.path: path.resolve(__dirname, '.. /dll'),
		library: '[name]',},plugins: [
		new webpack.DllPlugin({
			name: '[name]'.path: path.resolve(__dirname, '.. /dll/[name].manifest.json'),})]};Copy the code

Webpack.com mon. Js changes

const fs = require('fs');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); . const plugins = [new HtmlWebpackPlugin({ 
        template: 'src/index.html',}).new CleanWebpackPlugin(), 
];

const files = fs.readdirSync(path.resolve(__dirname, '.. /dll'));
files.forEach((file) = > {
	if (/.*\.dll.js/.test(file)) {
		plugins.push(new AddAssetHtmlWebpackPlugin({    // Automatically import dlL. js files into HTML
			filepath: path.resolve(__dirname, '.. /dll', file),
		}));
	}
	if (/.*\.manifest.json/.test(file)) {
		plugins.push(new webpack.DllReferencePlugin({    // When packaging a third party library, the mapping is searched in the manifest.json file. If it is found, the mapping can be directly extracted from the global variable (i.e. the packaging file) to optimize the packaging speed without analyzing the third party library
			manifest: path.resolve(__dirname, '.. /dll', file), })); }});Copy the code

package.json

    "dll": "webpack --config ./build/webpack.dll.js"
Copy the code

PWA optimization

The PWA, which stands for Progressive Web Application, implements the ability to retrieve pages from a local cache even if the server fails. Install the workbox-webpack-plugin first and then configure it in Plugins in webpack.prod.js, since this feature is only available online.

npm install workbox-webpack-plugin --save
Copy the code

Webpack. Prod. Js changes

const WorkboxPlugin = require('workbox-webpack-plugin'); . plugins: [ ... new WorkboxPlugin.GenerateSW({clientsClaim: true.skipWaiting: true})]Copy the code

Precach-manifest.js and service-worker.js will be stored in two files after the command is executed. Service-worker is the file that can make our page cached

Specify the environment

Selective compilation of Webpack can be made by specifying the environment. Selective compilation means selectively making certain statements valid and disabling specific statements, depending on the environment in which the package is packaged. This allows code optimization for a specific user’s environment to remove or add important code. The simplest example is that in a development environment we print logs, but in a production environment we invalidate all statements that print logs (let the program not run the printed statements, or even pack the files that don’t contain the log statements at all) and we can use the DefinePlugin built into WebPack to do this.

// webpack.dev.js. plugins: [ ... new Webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify('development'),}),]// webpack.prod.js
plugins: [
  ...
  new Webpack.DefinePlugin({
     'process.env.NODE_ENV': JSON.stringify('production'),}),]Copy the code

You can also specify the production environment with mode: production, set mode: “production”,webpack defaults to “process.env.node_env “: json.stringify (“production”). Don’t repeat Settings!

conclusion

At this point, a basic scaffolding is set up, and the entire code for this article is there. You can follow the code to configure it, and basically every step has a COMMIT, if you don’t know, you can look at the COMMIT. Of course, there are many imperfections in this paper, such as mock, ESLint, styleLint, TS, etc., which have not been added, and there are also many optimization points. Due to the lack of space, I will not write them all. I will add and improve the project in the future. Hopefully, this article will give you a deeper and more comprehensive understanding of the WebPack tool so that you can handle configuration changes in future projects. Creat-react-app or UMI are recommended if you want to use a project framework. If there are any mistakes in this article, please tell me if you find them!! Thank you very much!! If you feel helpful, welcome to give me a star! ! Thank you very much!!