Webpack builds a local server

1. Build webpack local service

1. Why build a local server?

The code we have developed so far requires two operations in order to run:

  • Operation 1: npm run build, compile the relevant code;
  • Operation 2: Open the index.html code through the live server or directly through the browser, View the effect;

Frequent operation of this process will affect our development efficiency. We hope that when the file changes, compilation and display can be completed automatically;

In order to complete automatic compilation, webpack provides several optional methods:

  • webpack watch mode;
  • webpack-dev-server (commonly used);
  • webpack-dev-middleware;

2.webpack-dev-server

The above method can monitor file changes, but in fact it does not have the function of automatically refreshing the browser:

  • Of course, currently we can use live-server in VSCode to complete such a function;
  • However, we hope to have the function of live reloading without using live-server;

Install webpack-dev-server

npm install webpack-dev-server -D

Modify the configuration file and add the serve parameter when starting:

module.exports = {<!-- -->
   devServer: {<!-- -->}
}
// package.json
{<!-- -->
    "scripts": [
        "serve": "webpack serve --config wk.config.js"
    ]
}

webpack-dev-server does not write to any output files after compilation, but keeps the bundle files in memory:

  • In fact, webpack-dev-server uses a library called memfs (memory-fs written by webpack itself)

3. Understand hot module replacement (HMR)

What is HMR?

  • The full name of HMR is Hot Module Replacement, which is translated as Hot Module Replacement;
  • Module hot replacement refers to replacing, adding, and deleting modules while the application is running without refreshing the entire page;

HMR improves development speed in the following ways:

  • Do not reload the entire page, which preserves the state of some applications and does not lose it;
  • Only update the content that needs to be changed, saving development time;
  • Modified css and js source code will be updated immediately in the browser, which is equivalent to directly modifying the style in the browser’s devtools;

How to use HMR?

  • By default, webpack-dev-server already supports HMR, we just need to turn it on (it is already turned on by default);
  • Without turning on HMR, when we modify the source code, the entire page will automatically refresh using live reloading;

4. Turn on HMR

Modify webpack configuration:

module.exports = {<!-- -->
    devServer: {<!-- -->
        hot: true
    }
}

The browser can see the following effect:

But you will find that when we modify the code of a certain module, the entire page is still refreshed:

  • This is because we need to specify which modules are updated and perform HMR;
if (module.hot) {<!-- -->
    module.hot.accept("./utils.js", () => {<!-- -->
        console.log("util updated")
    })
}

5. Framework HMR

There is a question: when developing other projects, do we often need to manually write module.hot.accpet related APIs?

  • For example, when developing Vue and React projects, we modify the components and hope to perform hot updates. How should we operate at this time?

In fact, the community already has very mature solutions for these:

  • For example, in vue development, we use vue-loader, which supports HMR of vue components and provides an out-of-the-box experience;
  • For example, in react development, there is React Hot Loader, which adjusts react components in real time (currently React has been officially deprecated and replaced by react-refresh);

6.host configuration

host sets the host address:

  • The default value is localhost;
  • If you want to be accessible from other places, you can set it to 0.0.0.0;

The difference between localhost and 0.0.0.0:

  • localhost: Essentially a domain name, usually resolved to 127.0.0.1;
  • 127.0.0.1: Loop Back Address, which actually means that the packets sent by our host itself are directly received by ourselves;
    • Normal database packages are often application layer – transport layer – network layer – data link layer – physical layer;
    • The loopback address is obtained directly at the network layer, and does not frequently access the data link layer and physical layer;
    • For example, when we monitor 127.0.0.1, hosts in the same network segment cannot access it through the IP address;
  • 0.0.0.0: Listen to all addresses on IPV4 and find different applications based on the port;
    • For example, when we monitor 0.0.0.0, the host in the same network segment can be accessed through the IP address;

7.port, open, compress

portSets the listening port, which is 8080 by default.

open Whether to open the browser:

  • The default value is false, setting it to true will open the browser;
  • It can also be set to a value similar to Google Chrome;

compress Whether to enable gzip compression for static files:

  • The default value is false and can be set to true;

2. Vue project configuration

1.Proxy (Vue project learning)

Proxy is a very commonly used configuration option in our development. Its purpose is to set up a proxy to solve the problem of cross-domain access:

  • For example, one of our API requests is http://localhost:8888, but the domain name of the local startup server is http://localhost:8000. At this time, cross-domain problems will occur when sending network requests;
  • Then we can send the request to a proxy server first. There is no cross-domain problem between the proxy server and the API server, and our cross-domain problem can be solved;

We can make the following settings:

  • target: represents the target address of the proxy. For example, /api-hy/moment will be proxy to http://localhost:8888/api-hy/moment;
  • pathRewrite: By default, our /api-hy will also be written to the URL. If you want to delete it, you can use pathRewrite;
  • secure: By default, servers forwarded to https are not accepted. If you want to support it, you can set it to false;
  • changeOrigin: It indicates whether to update the host address in the headers requested after the proxy;

2.ChangeOrigin analysis (Vue project learning)

The official statement of changeOrigin is very vague. By looking at the source code, I found that it actually needs to modify the host attribute in the headers in the proxy request:

  • Because our real request actually needs to be requested through http://localhost:8888;
  • But because of the code used, its value is http://localhost:8000 by default;
  • If we need to modify it, we can set changeOrigin to true;

3.historyApiFallback (Vue project learning)

historyApiFallback is a very common attribute in development. Its main function is to solve the problem of SPA page returning 404 error when refreshing the page after routing jump.

boolean value: default is false

  • If set to true, when refreshing and returning a 404 error, the content of index.html will be automatically returned;

For values of object type, the rewrites attribute can be configured:

  • You can configure from to match the path and decide which page to jump to;

In fact, the historyApiFallback function in devServer is implemented through the connect-history-api-fallback library:

  • You can view the connect-history-api-fallback documentation

3. Differentiate development and testing environments

1. How to distinguish development environments

Currently, all our webpack configuration information is placed in a configuration file: webpack.config.js

  • As more and more configurations are made, this file becomes less and less easy to maintain;
  • And Some configurations are needed in the development environment, Some configurations are needed in the production environment, and of course some configurations are used in both development and production environments. ;
  • Therefore, we’d better divide the configuration to facilitate our maintenance and management;

So, how can you differentiate between different configurations at startup?

  • Option 1: Write two different configuration files. During development and production, just load different configuration files respectively;
  • Option 2: Use the same entry configuration file and distinguish them by setting parameters;
"script": {<!-- -->
    "build": "webpack --config ./config/common.config --env production",
    "serve": "webpack serve --config ./config/common.config"
}

2. Entry file analysis

Our previous rules for writing entry files were as follows: ./src/index.js, but if the location of our configuration file becomes the config directory, should we change it to…/src/ What about index.js?

  • If we write it like this, we will find that an error is reported, and we still need to write it as ./src/index.js;
  • This is because the entry file is actually related to another attribute context;

The function of context is for parsing entry (entry point) and loader (loader):

  • Official statement: The default is the current path (but after my testing, the default should be webpack’s startup directory)
  • It is also recommended to pass a value in the configuration;
module.exports = {<!-- -->
    context: path.resolve(__dirname, "./"),
    entry: "../src/index.js"
}
module.exports = {<!-- -->
    context: path.resolve(__dirname, "../"),
    entry: "./src/index.js"
}

3. Distinguish between development and production environment configuration

Here we create three files:

  • webpack.comm.conf.js
  • webpack.dev.conf.js
  • webpack.prod.conf.js

webpack.dev.conf.js

const {<!-- --> merge } = require("webpack-merge")
const commonConfig = require("./webpack.comm.config")

module.exports = merge(commonConfig, {<!-- -->
  mode: "development",
  devServer: {<!-- -->
    hot: true,
    // host: "0.0.0.0",
    // port: 8888,
    // open: true
    // compress: true
  }
})

webpack.comm.conf.js

const path = require("path")
const {<!-- --> VueLoaderPlugin } = require("vue-loader/dist/index")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {<!-- --> DefinePlugin } = require("webpack")

module.exports = {<!-- -->
  entry: "./src/main.js",
  output: {<!-- -->
    filename: "bundle.js",
    path: path.resolve(__dirname, "../build")
  },
  resolve: {<!-- -->
    extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
    alias: {<!-- -->
      utils: path.resolve(__dirname, "../src/utils")
    }
  },
  module: {<!-- -->
    rules: [
      {<!-- -->
        test: /\.css$/,
        use: [ "style-loader", "css-loader", "postcss-loader" ]
      },
      {<!-- -->
        test: /\.less$/,
        use: [ "style-loader", "css-loader", "less-loader", "postcss-loader" ]
      },
      {<!-- -->
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset",
        parser: {<!-- -->
          dataUrlCondition: {<!-- -->
            maxSize: 60 * 1024
          }
        },
        generator: {<!-- -->
          filename: "img/[name]_[hash:8][ext]"
        }
      },
      {<!-- -->
        test: /\.js$/,
        use: [
          {<!-- -->
            loader: "babel-loader"
          }
        ]
      },
      {<!-- -->
        test: /\.vue$/,
        loader: "vue-loader"
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({<!-- -->
      title: "e-commerce project",
      template: "./index.html"
    }),
    new DefinePlugin({<!-- -->
      BASE_URL: "'./'",
      coder: "'abc'",
      counter: "123"
    })
  ]
}

webpack.prod.conf.js

const {<!-- --> CleanWebpackPlugin } = require("clean-webpack-plugin")
const {<!-- --> merge } = require("webpack-merge")
const commonConfig = require("./webpack.comm.config")

module.exports = merge(commonConfig, {<!-- -->
  mode: "production",
  output: {<!-- -->
    clean: true
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
})