This is the third installment in the React + typescript configuration series that takes you through the webpack configuration of your template project. I strive to achieve the following objectives in the configuration of the whole project:

Flexibility: I chose to use JS instead of JSON when configuring ESLint for flexibility. Using JS files allows you to import other modules and configure them dynamically based on your development environment, taking full advantage of the JS language.

Novelty: I think it is an excellent quality to always be aware of new things and try to use them. Of course, chasing new is very easy to run into pit, but, it doesn’t matter, I have helped you step over, step on the past I will not write 😂. From my eslint parserOptions. EcmaVersion set to 2020, there are often rings out yarn upgrade – the latest can be reflected.

Strict: Just as I usually use strict equality === rather than non-strict equality == in most cases to judge equality, I think the more strict, the clearer the analysis, the earlier the problem can be found. For example, we will use some Webpack plugins later to strictly check module case for loop dependencies.

Comfort: The project will integrate as many tools as possible that are useful in the current front-end ecosystem and make development more enjoyable (in other words, fancy).

Production ready: The configuration is optimized for different packaging environments and ensures that it is ready for production.

This paper will be divided into three parts:

  1. dev server
  2. Development environment optimization
  3. Production environment optimization

Readers who are reading this for the first time are advised to look at the first two:

  1. Configure react + typescript from scratch
  2. Configure react + typescript from scratch: Linters and formatter

Project address: React-typescript-boilerplate

dev server

When I first started to learn front-end framework, I was also tortured by Webpack. I learned node by myself before I started to write front-end. It is very convenient to write NodeJS, and the modular scheme commonJS comes with it. The most popular packaging tool was Webpack, followed by Gulp. When configuring WebPack, you can’t always remember the fields in webPack configuration, and you have to drag a bunch of related tools like ES6 compiler Babel, CSS preprocessor Sass/Less, CSS post-processor postCSS, And various Webpack loaders and plugins. React uses crA (create-react-app) and Vue uses VUE-CLI. In fact, it is also quite useful, but to be honest, I personally think that CRA is not as good as vue-CLI design, both ease of use and scalability are completely defeated, CRA is not convenient for users to modify the webpack configuration, Vue-CLI is not only easy for users to modify the Webpack configuration, It also allows users to save templates and its own plug-in system. I feel react officials are also aware of this, so they claim that they will focus on optimizing the relevant tool chain in the near future. Now, if I create a new front-end project, I will choose to configure it by myself rather than using the official CLI, because I think I am quite familiar with all kinds of front-end construction tools. After I finish my graduation and job hunting in the first half of the year, I will extract some common configurations into an NPM package. Now it is too tiring to copy every time you write a project. The build configuration of one project has optimization points, and other projects have to be manually synchronized, which is inefficient.

Technology selection

As a statically typed language, TypeScript offers a huge improvement in type hints over JS. With the help of IDE type hints and code completion, we need to know what fields webPack configuration objects have without having to look up the official documentation, and it’s easy to type correctly, so the development language is TypeScript.

There is a Configuration Languages section in the official documentation that describes how the Webpack command line tool uses the TS Configuration file. I think the webpack-dev-server command line tool should be the same.

But I’m not going to use the official documentation, I’m not going to use a command-line tool at all, and using the Node API is the most flexible way to configure. There are several ways to configure WebPack devServer:

  1. webpack-dev-serverThis is the least flexible way, but of course it is very convenient for simple scenarios
  2. webpack-dev-serverNode API, called in the Node scriptweb-dev-serverThe Package provides the Node API to start devServer
  3. express + Webpack DevServer-related middlewareIn fact,webpack-dev-serverIs the use ofexpressAnd some devServer-related middleware development. In this way, the middleware is directly exposed and we can flexibly configure the options of each middleware.
  4. koa + Webpack DevServer-related middlewareI actually found webPack middleware related to WebPack devServer on Github. In fact, WebPack devServer is a Node server, with which framework technology implementation is not important, we need to achieve the function of the line.

I ended up with express + WebPack DevServer-related middleware, why not koA? Because I think the official use is Express, express must be more mature and stable than KOA, less pit.

Implement the most basic packaging function

From simple to complex, we will first implement the most basic packaging function to enable it to package TSX files, on this basis step by step enrichment, optimize our configuration.

Configuration entry file

Install TypeScript first:

# Native install development relies on typescript
yarn add typescript -D
Copy the code

Json configuration file is required for each TypeScript project. Create a new tsconfig.json file in the SRC directory using the following command:

cd src && npx tsc --init && cd.Copy the code

Let’s temporarily adjust it to this:

{
    "compilerOptions": {
        /* Basic Options */
        "jsx": "react"."isolatedModules": true./* Strict Type-Checking Options */
        "strict": true./* Additional Checks */
        "noUnusedLocals": true."noUnusedParameters": true."noImplicitReturns": true."noFallthroughCasesInSwitch": true./* Module Resolution Options */
        "moduleResolution": "node"."esModuleInterop": true."resolveJsonModule": true."baseUrl": ". /"."paths": {
            // Configure module path mapping
            "@ / *": [". / *"],},/* Experimental Options */
        "experimentalDecorators": true."emitDecoratorMetadata": true./* Advanced Options */
        "forceConsistentCasingInFileNames": true."skipLibCheck": true.// The following options have no effect on Babel compiling TypeScript but allow VSCode and other editors to correctly report errors
        "target": "ES2019"."module": "ESNext"}}Copy the code

We will compile TypeScript using Babel. Babel compiles TypeScript code by removing TypeScript types and then compiling it using plugins as normal javascript code. TSC is not involved in the compilation process. So many options in tsconfig.json such as target and module are useless.

Enabling the isolatedModules option provides some extra checks when Babel compiles code. The esModuleInterop option is used to allow modules without the default attribute to use the default import. For a simple example, if this option is not enabled, You can only import fs modules like this:

import * as fs from 'fs';
Copy the code

When enabled, you can use the default import directly:

import fs from 'fs';
Copy the code

The ESM default import is essentially importing the module’s default property:

import fs from 'fs';
/ / is equivalent to
import * as __module__ from 'fs';
let fs = __module__.default;
Copy the code

However, node’s built-in fs module does not have the default attribute. Turning on the isolatedModules option will automatically convert without the default attribute:

import fs, { resolve } from 'fs';
/ / convert
import * as fs from 'fs';
let { resolve } = fs;
Copy the code

We add an entry file SRC /index.tsx that says simply:

import plus from './plus';

console.log(plus(404.404.404.404.404)); / / = > 2020
Copy the code

SRC /plus.ts:

export default function plus(. nums: number[]) {
  return nums.reduce((pre, current) = > pre + current, 0);
}
Copy the code

Compile the TypeScript

We know that the default modular system of WebPack only supports JS files. For other types of files, such as JSX, TS, TSX, VUE and image fonts, we need to install the corresponding Loader. For TS files, there are three popular schemes:

  1. babel + @babel/preset-typescript

  2. ts-loader

  3. awesome-typescript-loader

Awesome-typescript-loader forget it, the author has abandoned maintenance. We must use Babel first, because Babel Ecology has many useful plug-ins. React-babel-karma-gulp is an example of a react-babel-karma-gulp, although it can be used with ts-loader. But I don’t think we need to add a TS-Loader since Babel already compiles TypeScript, so I choose option 1. It is important to note that Babel does not check TypeScript types by default. Later in the Webpack plugin section we will solve this problem by configuring fork-ts-checker-webpack-plugin.

Add the WebPack configuration

We will put all node scripts in the project root destination scripts folder, since SRC is the front-end project and scripts is the Node project, we should configure tsconfig.json separately, To generate the initial tsconfig.json file, run the following command:

cd ./scripts && npx tsc --init && cd.Copy the code

We adjust to sauce:

// scripts/tsconfig.json
{
    "compilerOptions": {
        /* Basic Options */
        "target": "ES2019"."module": "commonjs"./* Strict Type-Checking Options */
        "strict": true./* Additional Checks */
        "noUnusedLocals": true."noUnusedParameters": true."noImplicitReturns": true."noFallthroughCasesInSwitch": true./* Module Resolution Options */
        "moduleResolution": "node"."esModuleInterop": true."resolveJsonModule": true./* Experimental Options */
        "experimentalDecorators": true."emitDecoratorMetadata": true./* Advanced Options */
        "forceConsistentCasingInFileNames": true."skipLibCheck": true}}Copy the code

A few caveats:

  • “Target “: “ES2019”, in fact, you can set the compiler level very low, you can use TSC to transcode, the shortcoming is that after transcoding, the code volume is generally larger, the execution efficiency is also reduced, native syntax is generally optimized. I like to turn it up a little bit, and generally just don’t use syntax that isn’t supported on the platform where the code runs. I’ve been trying to use alternative chains in TypeScript since TypeScript3.7 supported them, but the problem is that I’ve always set the compile level to the highest, ESNext, because alternative chains are standard in ES2020, so TSC doesn’t transcode alternative chains. Then Node 12 didn’t support optional chains, so I got a syntax error, and I was down to ES2019.

  • Strict type-checking Options: this part is fully open. If you are in TypeScript’s boat, use Strict type-checking to reject AnyScript

Next, we create a new scripts/configs folder to store the configuration files including WebPack. Ts, webpack.dev.ts, and webapck.prod.ts. Ts holds some common configuration files. Webpack.dev. ts is used by the development environment and is read by devServer, and webapck.prod.ts is used when we build the production environment bundle.

We then install webpack and webpack-Merge and their type declaration files:

yarn add webpack webpack-merge @types/webpack @types/webpack-merge -D
Copy the code

Webpack-merge is a merge tool designed for merge Webpack configurations, providing some advanced ways to merge. However, I am not currently using the advanced merge method, just use it as a normal merge tool. You can explore this optimization in the future.

To compile TSX, we need to install babel-Loader and related plug-ins:

yarn add babel-loader @babel/core @babel/preset-typescript -D
Copy the code

New Babel configuration file babel.config.js, now let’s just add a TypeScript preset:

// babel.config.js
module.exports = function (api) {
  api.cache(true);

  const presets = ['@babel/preset-typescript'];
  const plugins = [];

  return {
    presets,
    plugins,
  };
};
Copy the code

Add babel-loader to webpack.common.ts:

// webpack.common.ts`
import { Configuration } from 'webpack';
import { projectName, projectRoot, resolvePath } from '.. /env';

const commonConfig: Configuration = {
  context: projectRoot,
  entry: resolvePath(projectRoot, './src/index.tsx'),
  output: {
    publicPath: '/'.path: resolvePath(projectRoot, './dist'),
    filename: 'js/[name]-[hash].bundle.js'./ / salt hash
    hashSalt: projectName || 'react typescript boilerplate',},resolve: {
    // We import modules such as TS without suffix, webpack will try to import using the suffix provided by this array
    extensions: ['.ts'.'.tsx'.'.js'.'.json'],},module: {
    rules: [{// Those who import JSX should drink less
        test: /\.(tsx? |js)$/.loader: 'babel-loader'.// Enable caching
        options: { cacheDirectory: true },
        exclude: /node_modules/,},],},};Copy the code

I think the React + TS project should not have a JSX file. If the JSX file is imported, webpack will report an error that the corresponding loader cannot be found. Please let us deal with the problem in time.

DevServer is developed using Express

Let’s start by installing Express and some middleware associated with WebPack devServer:

yarn add express webpack-dev-middleware webpack-hot-middleware @types/express @t
ypes/webpack-dev-middleware @types/webpack-hot-middleware -D
Copy the code

Webpack-dev – Middleware is used for express middleware.

  1. As a static file server, the in-memory file system is used to host bundles compiled by WebPack
  2. If the file is modified, the server’s request is delayed until compilation is complete
  3. With Webpack-hot-middleware to achieve hot updates

Webpack-hot-middleware The Express middleware registers itself as a Webpack plug-in and listens for webpack compilation events. Import the webpack-hot-middleware/client.js client patch provided by the plugin into any entry that you want to implement hot updates. This front-end code gets devServer’s Server Sent Events connection, and devServer issues notifications to the client when a compile event occurs. After receiving the notification, the client will compare the hash value to determine whether the local code is up to date. If it is not, the client will pull the update patch from devServer to implement hot updates using other tools such as react-hot-loader.

Here are two requests sent by the client patch after I changed a line of code in another electron project I was still working on:

The h value returned in the first request can be guessed as the hash value by flicking your toes. If it does not match the local hash value, the user requests the patch again.

We create a new file scripts/start.ts to start our devServer:

import chalk from 'chalk';
import getPort from 'get-port';
import logSymbols from 'log-symbols';
import open from 'open';
import { argv } from 'yargs';
import express, { Express } from 'express';
import webpack, { Compiler, Stats } from 'webpack';
import historyFallback from 'connect-history-api-fallback';
import cors from 'cors';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';

import proxy from './proxy';
import devConfig from './configs/webpack.dev';
import { hmrPath } from './env';

function openBrowser(compiler: Compiler, address: string) {
    if (argv.open) {
        let hadOpened = false;
        // Execute when compilation is complete
        compiler.hooks.done.tap('open-browser-plugin'.async (stats: Stats) => {
            // Open the browser without opening the browser and without compiling errors
            if(! hadOpened && ! stats.hasErrors()) {await open(address);
                hadOpened = true; }}); }}function setupMiddlewares(compiler: Compiler, server: Express) {
    constpublicPath = devConfig.output! .publicPath! ;// Set the proxy
    proxy(server);

    // Using browserRouter redirects all HTML pages to the home page
    server.use(historyFallback());

    The development of the chrome extension of / / may need to open the cross-domain, reference: https://juejin.cn/post/6844904049276354567
    server.use(cors());

    const devMiddlewareOptions: webpackDevMiddleware.Options = {
        // Keep the configuration consistent with webPack
        publicPath,
        // Output only when an error occurs or a new compile is available
        stats: 'minimal'.// If you need to export files to disk, you can enable it
        // writeToDisk: true
    };
    server.use(webpackDevMiddleware(compiler, devMiddlewareOptions));

    const hotMiddlewareOptions: webpackHotMiddleware.Options = {
        / / sse routing
        path: hmrPath,
        A compilation error will display an error message mask in the web page
        overlay: true.// Webpack is stuck automatically refreshing the page
        reload: true}; server.use(webpackHotMiddleware(compiler, hotMiddlewareOptions)); }async function start() {
    const HOST = '127.0.0.1';
    // Random ports will be used if all the four alternate ports are occupied
    const PORT = await getPort({ port: [3000.4000.8080.8888]});const address = `http://${HOST}:${PORT}`;

    // Load webPack configuration
    const compiler = webpack(devConfig);
    openBrowser(compiler, address);

    const devServer = express();
    setupMiddlewares(compiler, devServer);

    const httpServer = devServer.listen(PORT, HOST, err => {
        if (err) {
            console.error(err);
            return;
        }
        // logSymbols. Success is displayed as √ in Windows
        console.log(
            `DevServer is running at ${chalk.magenta.underline(address)} ${logSymbols.success}`,); });// We are listening for node signals, so use cross-env-shell instead of cross-env
    Reference: / / https://github.com/kentcdodds/cross-env#cross-env-vs-cross-env-shell
    ['SIGINT'.'SIGTERM'].forEach((signal: any) = > {
        process.on(signal, () => {
            // Close devServer first
            httpServer.close();
            // Randomly print 'See you again' and 'Goodbye' when CTRL + C
            console.log(
                chalk.greenBright.bold(`\nThe ${Math.random() > 0.5 ? 'See you again' : 'Goodbye'}! `));Exit the Node process
            process.exit();
        });
    });
}

// Those who have written Python will be familiar with this method
// require.main === module Checks whether the module is run directly
if (require.main === module) {
    start();
}

Copy the code

The Overlay option for webpackHotMiddleware is used to enable an error mask:

I’ve written a lot of details in the comments. Install some of the libraries used:

yarn add get-port log-symbols open yarg -D
Copy the code

The first three symbols are made by SindResorhus. Get-port is used to obtain available ports. Log-symbols provides the following four log characters, Open is used for system applications to open urIs (urIs include files and urls), and yargs is used to parse command-line arguments.

Webpack-dev-middleware does not support historyFallback and proxy in Webpack-dev-Server. We can even use Express to integrate mock functionality. Install the corresponding two middleware:

yarn add connect-history-api-fallback http-proxy-middleware @types/connect-history-api-fallback @types/http-proxy-middleware -D
Copy the code

Connect-history-api-fallback can be directly integrated into Express Server as express middleware, encapsulating HTTP-proxy-Middleware, and can add its own proxy configuration to proxyTable:

import { createProxyMiddleware } from 'http-proxy-middleware';
import chalk from 'chalk';

import { Express } from 'express';
import { Options } from 'http-proxy-middleware/dist/types';

interface ProxyTable {
    [path: string]: Options;
}

const proxyTable: ProxyTable = {
    // Example configuration
    '/path_to_be_proxy': { target: 'http://target.domain.com'.changeOrigin: true}};// Modify the link's helper function, change the color and underline it
function renderLink(str: string) {
    return chalk.magenta.underline(str);
}

function proxy(server: Express) {
    Object.entries(proxyTable).forEach(([path, options]) = > {
        const from = path;
        const to = options.target as string;
        console.log(`proxy ${renderLink(from)} ${chalk.green('- >')} ${renderLink(to)}`);

        // eslint-disable-next-line no-param-reassign
        if(! options.logLevel) options.logLevel ='warn';
        server.use(path, createProxyMiddleware(options));

        Use (path, proxyMiddleware(options)) below for a more flexible definition
    });
    process.stdout.write('\n');
}

export default proxy;
Copy the code

To start devServer, we also need to install two command-line tools:

yarn add ts-node cross-env -D
Copy the code

Ts-node lets you run TypeScript code directly. Cross-env is a tool for setting environment variables across operating systems. Add startup commands to NPM script:

// package.json
{
    "scripts": {
        "start": "cross-env-shell NODE_ENV=development ts-node --files -P ./scripts/tsconfig.json ./scripts/start.ts --open",}}Copy the code

The cross-env documentation states that if you want to handle node signals such as SIGINT on Windows, you should use the cross-env-shell command instead of the cross-env command.

By default, ts-Node does not read the files, include, and exclude fields in tsconfig.json for faster execution. Instead, it reads them based on module dependencies. This will cause some of the global.d.ts files we will write later not to be read. To do this, we need to specify –files, see help-my-types-are-missing for details. We don’t have a lot of Node code, and since we’re not constantly restarting the project, it doesn’t matter that TS-Node scans the entire scripts folder.

Start our dev Server and press CTRL + C to exit:

npm start
Copy the code

Development environment optimization

plugins

Each Webpack plugin is a class that contains the apply method, which is invoked whenever we call Compiler. run or Compiler. watch, passing the compiler as an argument. The Compiler object provides hooks from time to time that can be used to mount callback functions to perform various functions such as compression, tuning statistics, and typing success notifications after compilation.

Show packing progress

Webpack-dev-server uses the –progress parameter when packaging to output a percentage of the current packaging progress in real time on the console, but as you can see from the figure above, it just outputs stats. There are three ways I know how to show your packaging progress in real time:

  1. The webpack.Progressplugin plugin is built into Webpack

  2. progress-bar-webpack-plugin

  3. webpackbar

The built-in ProgressPlugig is very primitive. You can get the current progress in the callback function and print it in the format you like:

const handler = (percentage, message, ... args) = > {
  // e.g. Output each progress message directly to the console:
  console.info(percentage, message, ... args); };new webpack.ProgressPlugin(handler);
Copy the code

Instead of displaying percentages, the progress-bar-webpack-plugin displays a progress bar drawn in characters:

This plugin is actually quite simple and practical, but there is a bug that if the progress bar is printed with other statements, the progress bar will be misplaced. Our devServer will output the address after startup:

console.log(`DevServer is running at ${chalk.magenta.underline(address)} ${logSymbols.success}`);
Copy the code

Using this progress bar plugin will cause the following problem, so abandon.

Webpackbar is a library under NuxT project, backed by NUxT, quality is absolutely guaranteed. I used progress-bar-webpack-plugin for some time before, because I searched webpack Progress on NPM official website, which was relatively reliable, but webPackBar was not found. Look at webPackbar package.json, sure enough, the keywords are empty. WebpackBar I was researching ant Design’s WebPack configuration when I saw it using this plugin and discovered this treasure:

Install webpackbar:

yarn add webpackbar @types/webpackbar -D
Copy the code

Add configuration to webpack.common.ts plugins array, color we use react blue:

import { Configuration } from 'webpack';

const commonConfig: Configuration = {
  plugins: [
    new WebpackBar({
      name: 'react-typescript-boilerplate'./ / the react blue
      color: '#61dafb',})]};Copy the code

Optimizing console output

We used the friendly-errors-webpack-plugin to make the console output more friendly. Here is what it looks like after a successful compilation:

yarn add friendly-errors-webpack-plugin @types/friendly-errors-webpack-plugin -D
Copy the code
// webpack.common.ts
import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [new FriendlyErrorsPlugin()],
};
Copy the code

Build notifications

Before my internship in my senior year, I did not write a complete vUE project. During my internship in my previous company, I mainly wrote VUE. At that time, I was very disgusted with the frequent error notification of VUE-CLI. Let’s match this project.

We use webpack-build-notifier to support error notification. This plugin is written in TypeScript and does not require the installation of types:

yarn add webpack-build-notifier -D
Copy the code
// webpack.common.ts
import WebpackBuildNotifierPlugin from 'webpack-build-notifier';

const commonConfig: Configuration = {
  plugins: [
    // suppressSuccess: true Sets to output the notification of success only when the first compilation succeeds, but not when the rebuild succeeds
    new WebpackBuildNotifierPlugin({ suppressSuccess: true})]};Copy the code

Since I don’t like popping notifications, I commented out the plugin in the template project and opened it myself if needed.

Check the case of the path

The following tests show that WebPack is case-insensitive to paths by default:

We use case-sensitive paths-webpack-plugin for strict case checking of paths:

yarn add case-sensitive-paths-webpack-plugin @types/case-sensitive-paths-webpack-plugin -D
Copy the code
// webpack.common.ts
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [new CaseSensitivePathsPlugin()],
};
Copy the code

The actual packaging tests found that the plugin was time-consuming, and because the eslint-import-plugin would default to an error for module paths that were only case-sensitive, our project was not integrated.

Cyclic dependency check

By default, webpack does not report circular dependencies. This plugin can be used to detect circular dependencies:

yarn add circular-dependency-plugin @types/circular-dependency-plugin -D
Copy the code
// webpack.common.ts
import CircularDependencyPlugin from 'circular-dependency-plugin';

import { projectRoot, resolvePath } from '.. /env';

const commonConfig: Configuration = {
  plugins: [
    new CircularDependencyPlugin({
      exclude: /node_modules/.failOnError: true.allowAsyncCycles: false.cwd: projectRoot,
    }),
  ],
};
Copy the code

CWD () is a bad way to use process.cwd() in official documentation. The project path and the work path are two different concepts. Never use preocess.cwd() to indicate the project path in node, because there will always be sandsculptors who don’t start from the project root. Process.cwd (), the working path, returns the path in which you were running Node. For example, if the project is in /code/projectRoot, some users will open Terminal directly in the system root directory. To a node. / code/projectRoot/index, js, then index. The process in the js. CWD () returns are/is the system root directory, not some people think or/code/projectRoot.

To get the project path, use path.resolve:

The eslint-import-plugin also reports errors with this problem, and TypeScript projects sometimes just need to import files in a loop, so they don’t integrate.

Clean up the bundles you packed last time

Having introduced some of the fancy plug-ins and some of the plug-ins that kept our project healthy, let’s start with some of the packaged plug-ins.

Clean-webpack-plugin deletes all files in the dist directory on the first build, but keeps the dist folder, and deletes all files that are no longer in use during each rebuild.

This project is also written in TypeScript, and there’s something strangely grounded about TypeScript projects:

yarn add clean-webpack-plugin -D
Copy the code
// webpack.common.ts
import { CleanWebpackPlugin } from 'clean-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [new CleanWebpackPlugin()],
};
Copy the code

Automatically generate index.html

As we all know, Tencent’s front-end interviews like to test the computer network, I have been asked many times how to update the strong cache. To solve the problem of strong cache immediate update, we usually adopt the hash value of the file name, and then do not use strong cache on the home page. In this way, whenever you update a strongly cached resource file, the hash value of the updated content will change, and the generated file name will also change. In this way, when you request the homepage, you will ask the server for the latest resource because you are accessing a new resource path. See my other article on browser HTTP caching: Exploring the browser HTTP caching mechanism with -koa2- Server Practices.

If the href of the link tag that introduces external styles is set manually in index. HTML, then every time we modify the style file, a new hash value will be generated. We all have to change the path manually, which is too much trouble, not to mention that in the development environment the file is stored in memory file system, you can’t see the file name.

Using the HTmL-webpack-plugin, you can automatically generate index.html and insert resource paths such as referenced bundles and split CSS.

Following the creat-react-app template, we created a new public folder and added index.html, favico.ico, manifest.json, etc. The public folder is used to store files that will be packaged and distributed together in the dist folder.

Install and configure html-webpack-plugin:

yarn add html-webpack-plugin @types/html-webpack-plugin -D
Copy the code
import HtmlWebpackPlugin from 'html-webpack-plugin';
import { __DEV__, projectName, resolvePath, projectRoot, hmrPath } from '.. /env';

const htmlMinifyOptions: HtmlMinifierOptions = {
    collapseWhitespace: true.collapseBooleanAttributes: true.collapseInlineTagWhitespace: true.removeComments: true.removeRedundantAttributes: true.removeScriptTypeAttributes: true.removeStyleLinkTypeAttributes: true.minifyCSS: true.minifyJS: true.minifyURLs: true.useShortDoctype: true};const commonConfig: Configuration = {
    output: {
        publicPath: '/',},plugins: [
        new HtmlWebpackPlugin({
            // HtmlWebpackPlugin calls HtmlMinifier to compress HTMl files
            // Compress only in production environment
            minify: __DEV__ ? false : htmlMinifyOptions,
            // Specify the HTML template path
            template: resolvePath(projectRoot, './public/index.html'),
            // The type is not easy to define.
            // Define some template parameters that can be accessed in the template
            templateParameters: (. args: any[]) = > {
                const [compilation, assets, assetTags, options] = args;
                constrawPublicPath = commonConfig.output! .publicPath! ;return {
                    compilation,
                    webpackConfig: compilation.options,
                    htmlWebpackPlugin: {
                        tags: assetTags,
                        files: assets,
                        options,
                    },
        			// Remove the backslash from publicPath to make stitching paths in templates more natural
                    PUBLIC_PATH: rawPublicPath.endsWith('/')? rawPublicPath.slice(0.- 1) : rawPublicPath, }; }}),]};Copy the code

To allow users to access the publish path via PUBLIC_PATH in index. HTML like create-react-app, configure the templateParameters option to add the PUBLIC_PATH variable to the template parameter. The htML-webpack-plugin supports partial EJS syntax by default, and we can dynamically set the path of favicon.ico, mainfest.json, etc. :


      
<html lang="en">
  <head>
    <link rel="icon" href="<%= `${PUBLIC_PATH}/favicon.ico` %>" />
    <link rel="apple-touch-icon" href="<%= `${PUBLIC_PATH}/logo192.png` %>" />
    <link rel="manifest" href="<%= `${PUBLIC_PATH}/manifest.json` %>" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
Copy the code

Copy files to dist

There are files in the public folder such as favico.icon and mainfest.json that need to be copied to the dist folder, We can use the copy-webpack-plugin to copy files to an in-memory file system with devServer and to disk with a production build:

yarn add copy-webpack-plugin @types/copy-webpack-plugin -D
Copy the code
// webpack.common.ts
import CopyPlugin from 'copy-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [
    new CopyPlugin(
      [
        {
          // All level 1 files
          from: The '*'.to: resolvePath(projectRoot, './dist'),
          // The target type is folder
          toType: 'dir'.// index.html is automatically generated by html-webpack-plugin and needs to be ignored
          ignore: ['index.html'],},] and {context: resolvePath(projectRoot, './public')}),],};Copy the code

Check TypeScript types

Babel supports TypeScript syntax compilation and does not support type checking. In order to support TS type checking with webpack, we use the webpack plugin fork-ts-checker-webpack-plugin. The Webpack plugin does TypeScript type checking in parallel in a separate process. This project is also written in TypeScript, so we don’t need to install types.

yarn add fork-ts-checker-webpack-plugin -D
Copy the code

Add to webpack.dev.ts and limit memory usage to 1G:

import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const devConfig = merge(commonConfig, {
  mode: 'development'.plugins: [
    new ForkTsCheckerWebpackPlugin({
      memoryLimit: 1024.// Babel transforms our front-end code, so it points to the front-end code tsconfig.json
      tsconfig: resolvePath(projectRoot, './src/tsconfig.json'),})]});Copy the code

Also modify webpack.prod.ts, because our production environment build does not occupy memory for a long time, so we can increase it a bit, we default to the production environment build to use 2G memory:

// webpack.prod.ts
const prodConfig = merge(commonConfig, {
  mode: 'production'.plugins: [
    new ForkTsCheckerWebpackPlugin({
      memoryLimit: 1024 * 2.tsconfig: resolvePath(projectRoot, './src/tsconfig.json'),})]});Copy the code

Cache artifact

The hard-source-webpack-plugin is a webpack plugin that provides intermediate caching steps for modules. To see the effect, you may need to run it twice, first at normal compilation speed, and then many times faster. Take a VSCode plug-in I developed to test it:

Node_modules /. Cache /hard-source;

Take 3.075 seconds to recompile:

Wow 🚀, more than 3.6 times faster…

The plugin takes a lot of time to package for the first time, and it will be built into the upcoming webpack5. See this issue: [spec: Webpack 5] -a Module disk Cache between Build processes. Therefore, we will not integrate this plug-in in this project.

Okay, that’s it for the plug-in section, so let’s configure loaders!

loaders

Webpack only supports js import by default and cannot process other files. You need to configure the corresponding Loader. For example, Excel -loader can parse Excel into an object, and file-loader can parse PNG images into the final distribution path. Loader is used for a class of files, plugin is used for various stages of webpack compilation.

We have only configured babel-loader so that Webpack can handle TypeScript files. We also need to support the import of style files, image files, font files, etc.

Working with style files

The final goal is to support CSS /less/sass syntaxes, as well as auto-complete browsers with postCSS and autoprefixer plug-ins.

CSS

To process CSS files we need to install style-loader and CSS-loader:

yarn add css-loader style-loader -D
Copy the code

Cs-loader handles @import and URL () in the CSS file and returns a combined CSS string. Style-loader inserts the returned CSS string into the DOM with the style tag. The hot update interface of Webpack is also implemented.

The official sample style-loader configuration looks like this:

module.exports = {
  module: {
    rules: [{// the I suffix ignores case
        test: /\.css$/i.use: ['style-loader'.'css-loader'],},],},};Copy the code

You can see that the match re uses the I suffix, I think this is not good, should not increase some meaningless error tolerance, use. CSS suffixes should not allow Webpack compilation to pass. We know that webpack loaders are loaded from right to left, so csS-loader that needs to be executed first should be executed after style-loader:

// webpack.common.ts
const commonConfig: Configuration = {
  module: {
    rules: [{test: /\.css$/.use: [
          'style-loader',
          {
            loader: 'css-loader'.options: {
              // CSS modules are performance-intensive and are disabled by default
              modules: false./ / open sourcemap
              sourceMap: true.// Specifies the number of LaoDers to use before the CSS Loader processes them
              importLoaders: 0,},},],},},};Copy the code
less

Less-loader depends on less:

yarn add less less-loader -D
Copy the code
// webpack.common.ts
const commonConfig: Configuration = {
  module: {
    rules: [{test: /\.less$/.use: [
          'style-loader',
          {
            loader: 'css-loader'.options: {
              modules: false.sourceMap: true.// It needs to be processed by less-loader first, so set this parameter to 1
              importLoaders: 1,}}, {// Let less-loader convert less files to CSS files
            // Submit it to CSS-loader
            loader: 'less-loader'.options: {
              sourceMap: true,},},],},},};Copy the code
sass

Actually, I never use less or stylus, I always use sass. Sass has two syntax formats, distinguished by a suffix. Sass is an indentation similar to yML. SCSS is a curly brace similar to CSS, but supports features such as nesting and variables. Since I have hardly seen any projects written in YML format and there are so few people using it, our template will only support the SCSS suffix. Sass-loader also relies on Node-sass. Node-sass is a bit of a dead end, so I configured the image of Node-sass in.npmrc.

yarn add node-sass sass-loader -D
Copy the code
// webpack.common.ts
const commonConfig: Configuration = {
  module: {
    rules: [{test: /\.scss$/.use: [
          'style-loader',
          {
            loader: 'css-loader'.options: {
              modules: false.sourceMap: true.importLoaders: 1,}}, {loader: 'sass-loader'.options: {
              // Sourcemap must be enabled for each loader in order to generate the correct Soucemap
              sourceMap: true,},},],},},};Copy the code
postcss

I remember when I learned CSS3 in the freshman web design class, many attributes need to be added to the browser header to deal with compatibility, at that time I was greatly reduced in interest in CSS, too much trouble. Since the emergence of Node, front-end engineering began to develop rapidly, before the front end was called cut diagram son, now front-end engineers can also use Node to do pseudo-full stack development.

Postcss is a post-processor tool, because there is CSS first, postCSS to deal with it, so called post-processor.

Less/SASS are called CSS preprocessors because they need to be precompiled into CSS by LESS or Node-sass.

Install the following plug-ins according to the create-react-app configuration for PostCSS:

yarn add postcss-loader postcss-flexbugs-fixes postcss-preset-env autoprefixer postcss-normalize -D
Copy the code

Add postcss.config.js to configure postcss:

module.exports = {
  plugins: [
    // Fix some flex layout related bugs
    require('postcss-flexbugs-fixes'),
    // Refer to browserslist's browser compatibility list to automatically convert modern CSS features that are not yet supported
    require('postcss-preset-env') ({// Automatically add the browser header
      autoprefixer: {
        // will add prefixes only for final and IE versions of specification
        flexbox: 'no-2009',},stage: 3,}).// Automatically import the required normalize.css content according to Browserslist
    require('postcss-normalize')]};Copy the code

We also need to add the Browserslist configuration to package.json

// package.json
{
	"browserslist": [
        "last 2 versions".// Extended Support Release (ESR) long-term supported version
        "Firefox ESR"."1%" >."ie >= 11"],}Copy the code

Reviewing the configuration of CSS, LESS, and Sass shows that there is a lot of duplication. We refactor and modify the importLoaders option:

function getCssLoaders(importLoaders: number) {
  return [
    'style-loader',
    {
      loader: 'css-loader'.options: {
        modules: false.sourceMap: true,
        importLoaders,
      },
    },
    {
      loader: 'postcss-loader'.options: { sourceMap: true}},]; }const commonConfig: Configuration = {
  module: {
    rules: [{test: /\.css$/.use: getCssLoaders(1),}, {test: /\.less$/.use: [
          Postcss-loader + less-loader Two loaders, so importLoaders should be set to 2. getCssLoaders(2),
          {
            loader: 'less-loader'.options: {
              sourceMap: true,},},],}, {test: /\.scss$/.use: [
          ...getCssLoaders(2),
          {
            loader: 'sass-loader'.options: { sourceMap: true},},],},],},};Copy the code

Work with images and fonts

Generally speaking, during the development of our project, we will use some pictures to test the effect, and then replace them with CDN instead of local pictures packaged by Webpack after the official launch. There are two common loaders for processing files, file-loader and url-loader. File-loader is used to parse the imported file to the URL when publishing it, and output the file to the specified location. The latter is the encapsulation of the former. Provides to convert images below the threshold size (set to 8192 bytes below) to Base64. I suddenly remembered a question from an interviewer at Tencent: What are the disadvantages of using Base64? In fact, I think the advantage of Base64 is that there is no need for a second request, and the disadvantage is that the size of the image transferred to Base64 will increase, becoming four times the original size.

yarn add url-loader -D
Copy the code
const commonConfig: Configuration = {
  module: {
    rules: [{test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
        use: [{loader: 'url-loader'.options: {
              // Images below 10K are converted to base64 dataUrl
              limit: 10 * 1024.// [hash] placeholders have the same meaning as [contenthash]
              // Are hash values for file contents. The default value is MD5 hash algorithm
              name: '[name].[contenthash].[ext]'.// Save to the images folder
              outputPath: 'images',},},],}, {test: /\.(ttf|woff|woff2|eot|otf)$/.use: [{loader: 'url-loader'.options: {
              name: '[name]-[contenthash].[ext]'.outputPath: 'fonts',},},],},},};Copy the code

Notice that I have inserted hash values into the file names, so that the strong cache needs to be updated immediately.

sourcemap

devtool Build speed Rebuild speed The production environment Quality (quality)
(none) +++ +++ yes Packaged code
eval +++ +++ no Generated code
cheap-eval-source-map + ++ no Converted code (line only)
cheap-module-eval-source-map o ++ no Original source code (line only)
eval-source-map + no Original source code
cheap-source-map + o yes Converted code (line only)
cheap-module-source-map o yes Original source code (line only)
inline-cheap-source-map + o no Converted code (line only)
inline-cheap-module-source-map o no Original source code (line only)
source-map yes Original source code
inline-source-map no Original source code
hidden-source-map yes Original source code
nosources-source-map yes No source code content

+++ very fast, ++ fast, + relatively fast, O medium, – relatively slow, — slow

Sourcemap is an essential feature of many front-end tools. Webpack, TypeScript, Babel, Powser-Assert and other tools that transform code provide sourcemap functionality. Source code is compressed, scrambled, polyfilled, Without Sourcemap, there is no way to debug and locate problems.

For compilation speed and modality friendliness, I chose eval-source-map. If the user feels slow to package and can tolerate no column numbers, consider using cheap-eval-source-map.

We changed the devtool for webpack.dev.ts to eval-source-map:

// webpack.dev.ts
import commonConfig from './webpack.common';

const devConfig = merge(commonConfig, {
  devtool: 'eval-source-map'});Copy the code

The error-overlay-webpack-plugin provides the same error mask as create-react-app:

One limitation, however, is that you can’t use any eval-based sourcemap. Interested readers can try the following.

Hot update

We add devServer front webpack – hot – middleware middleware, refer to the document we need to add webapck plug-in webpack. HotModuleReplacementPlugin:

// webpack.dev.ts
import { HotModuleReplacementPlugin, NamedModulesPlugin } from 'webpack';

const devConfig = merge(commonConfig, {
  plugins: [new HotModuleReplacementPlugin()],
});
Copy the code

To add the ‘webpack-hot-middleware/client’ hot update patch to our bundle, add an entry array:

// webpack.common.ts
import { __DEV__, hmrPath } from '.. /env';


const commonConfig: Configuration = {
    entry: [resolvePath(projectRoot, './src/index.tsx')]};if (__DEV__) {
    (commonConfig.entry as string[]).unshift(`webpack-hot-middleware/client? path=${hmrPath}`);
}
Copy the code

Adding queryString to entry lets you configure some options. How does that work? If you look at the ‘webpack-hot-middleware/client’ source code, webPack will inject queryString as a global variable into this file:

React-hot-loader supports hot updates for CSS. React-hot-loader supports hot updates for CSS. React-hot-loader supports hot updates for CSS.

Babel configuration optimization

Earlier we only configured a @babel/preset-typescript plug-in to compile typescript, but there are many other optimizations that can be made.

@babel/preset-env

In Babel, preset represents a set of plugins, and @babel/preset-env can cause Babel to add only the syntax and polyfills that need to be transformed according to our configured Browserslist.

@ Babel/preset – env installation:

yarn add @babel/preset-env -D
Copy the code

@babel/plugin-transform-runtime

We know that by default, when compiling each module, Babel inserts helper functions such as _extend when needed. This helper function is generated for each module that needs it, causing unnecessary code bloat. The @babel/plugin-transform-runtime plugin imports all helper functions from @babel/ Runtime to reduce the size of the code.

yarn add @babel/plugin-transform-runtime -D
Copy the code

@babel/preset-react

Although @babel/preset-typescript converts TSX to JS code, @babel/preset- React also integrates some useful plug-ins for react projects.

The following plug-ins are enabled by default for @babel/preset-react:

  • @babel/plugin-syntax-jsx
  • @babel/plugin-transform-react-jsx
  • @babel/plugin-transform-react-display-name

If development: true is set, it will also enable:

  • @babel/plugin-transform-react-jsx-self
  • @babel/plugin-transform-react-jsx-source

Install dependency @babel/ preth-react:

yarn add @babel/preset-react -D
Copy the code

react-hot-loader

To implement a partial refresh of the component, we need to install the React-hot-loader Babel plug-in.

yarn add react-hot-loader
Copy the code

This plug-in does not need to be installed as devDependencies, it will not be executed in production and will be kept to a minimum. The react Fast Refresh plugin is currently being developed, but webPack is not yet supported.

To see how this works, we installed the React family bucket and adjusted the default contents of the SRC folder:

yarn add react react-dom react-router-dom
yarn add @types/react @types/react-dom @types/react-router-dom -D
Copy the code

React is the core interface of the framework. React-dom is responsible for mounting our React component to the real DOM. React-dom-router is the routing library of the Web platform that implements the React-Router interface.

Let the react-hot-loader take over the react root.

// App.ts
import React from 'react';
import { hot } from 'react-hot-loader/root';

import './App.scss';

const App = (a)= > {
  return (
    <div className="app">
      <h2 className="title">react typescript boilerplate</h2>
    </div>
  );
};

export default hot(App);
Copy the code

Add hot update patches to Webpack Entry:

const commonConfig: Configuration = {
  entry: ['react-hot-loader/patch', resolvePath(projectRoot, './src/index.tsx')]};Copy the code

Install @hot-loader/react-dom to replace the default react-dom to add some additional hot updates. To replace the react-DOM we need to configure the webpack alias:

// webpack.common.ts
module.exports = {
  resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom',}}};Copy the code

In combination with the Babel plugin mentioned earlier, the final change to babel.config.js is:

const envPreset = [
  '@babel/preset-env',
  {
    // Import only the required polyfills
    useBuiltIns: 'usage'.// Specify the corJS version
    corejs: 3.// Disable modular schema conversion
    modules: false,},];module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['@babel/preset-typescript', envPreset],
    plugins: ['@babel/plugin-transform-runtime'].env: {
      // Development environment configuration
      development: {
        presets: [['@babel/preset-react', { development: true }]],
        plugins: ['react-hot-loader/babel'],},// Production environment configuration
      production: {
        presets: ['@babel/preset-react'].plugins: ['@babel/plugin-transform-react-constant-elements'.'@babel/plugin-transform-react-inline-elements'],}}}; };Copy the code

Note that we also installed two plugins for production optimization:

yarn add @babel/plugin-transform-react-constant-elements @babel/plugin-transform-react-inline-elements -D
Copy the code

@babel/plugin-transform-react-constant-elements promotes variables from a function component outside the function as follows to avoid repeated declarations and unnecessary garbage collection:

const Hr = (a)= > {
  return<hr className="hr" />; }; // const _ref = <hr className="hr" />; const Hr = () => { return _ref; };Copy the code

@babel/plugin-transform-react-inline-elements Readers can refer to the issue of React: Optimizing Compiler: inline ReactElements.

Production environment optimization

Add a copyright notice

This can be done directly with the BannerPlugin built into Webpack:

import { BannerPlugin } from 'webpack';

const mergedConfig = merge(commonConfig, {
  mode: 'production'.plugins: [
    new BannerPlugin({
      raw: true.banner: `/** @preserve Powered by react-typescript-boilerplate (https://github.com/tjx666/react-typescript-boilerplate) */`,})]});Copy the code

Note that we have added the @Preserve tag to the comments in the copyright notice. We will later use Terser to compress the code during the build in the production environment.

CSS split

If the CSS is included in our JS bundle, it will end up very bulky, and in severe cases, accessing the home page will result in a brief blank screen. To split CSS we use the mini-CSs-extract-plugin directly:

yarn add mini-css-extract-plugin -D
Copy the code

Modify the production environment configuration:

// webpack.prod.ts
import MiniCssExtractPlugin from 'mini-css-extract-plugin';

const prodConfig = merge(commonConfig, {
  mode: 'production'.plugins: [
    new MiniCssExtractPlugin({
      // Insert the hash value into the file name
      filename: 'css/[name].[contenthash].css'.chunkFilename: 'css/[id].[contenthash].css'.ignoreOrder: false,})]});Copy the code

Mini-css-extract-plugin also provides mini-CSs-extract-plugin. loader, which cannot coexist with style-loader. So we changed the configuration of webpack.common.ts to use style-loader in development environment and use mini-css-extract-plugin.loader in production environment:

import { loader as MiniCssExtractLoader } from 'mini-css-extract-plugin';
import { __DEV__ } from '.. /env';

function getCssLoaders(importLoaders: number) {
  return [
    __DEV__ ? 'style-loader' : MiniCssExtractLoader,
    {
      loader: 'css-loader'.options: {
        modules: false.sourceMap: true,
        importLoaders,
      },
    },
    {
      loader: 'postcss-loader'.options: { sourceMap: true}},]; }Copy the code

Code compression

JavaScript compression

Uglifyjs-webpack-plugin is used in many tutorials on webpack compression code. The uglifyjs-webpack-plugin has been abandoned for a long time, and it does not support ES6 syntax. Evilebottnawi, the core developer of webpack, has shifted to maintaining the terser-webpack-plugin. We use terser-webpack-plugin to compress code in production, and we can use webPack4’s new tree-shaking to remove dead code and further reduce bundle size:

yarn add terser-webpack-plugin @types/terser-webpack-plugin -D
Copy the code

Treeshake needs to configure the sideEffects field in package.json. See Tree Shaking for details.

CSS compression

Optimize CSS with csS-assets -webpack-plugin:

yarn add optimize-css-assets-webpack-plugin @types/optimize-css-assets-webpack-plugin -D
Copy the code

Modify the webpack. Prod. Ts:

import TerserPlugin from 'terser-webpack-plugin';
import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin';

const prodConfig = merge(commonConfig, {
  mode: 'production'.optimization: {
    // Use minimizer instead of default uglifyJS
    minimize: true.// Two minimizers: TerserPlugin and OptimizeCSSAssetsPlugin
    minimizer: [new TerserPlugin({ extractComments: false }), new OptimizeCSSAssetsPlugin()],
  },
});
Copy the code

Build analysis

Let’s add some WebPack plug-ins for build analysis

Time statistics

We use speed-measure-webpack-plugin to calculate the packaging time:

yarn add speed-measure-webpack-plugin -D
Copy the code

To this project, we finally met the first no TypeScript type declaration document library, new scripts/typings/index which s file, because you need to write the type of very few, the index, which s is as a global declaration file, Add the external module declaration of speed-measure-webpack-plugin to it:

// scripts/typings/index.d.ts
declare module 'speed-measure-webpack-plugin' {
    import { Configuration, Plugin } from 'webpack';

    // Check the official documentation and declare any options you need
  	// TypeScript is very flexible
    interface SpeedMeasurePluginOptions {
        disable: boolean;
        outputFormat: 'json' | 'human' | 'humanVerbose' | ((outputObj: object) = > void);
        outputTarget: string | ((outputObj: string) = > void);
        pluginNames: object;
        granularLoaderData: boolean;
    }

    // Inherit Plugin classes. Plugin classes have apply methods
    class SpeedMeasurePlugin extends Plugin {
        constructor(options? : Partial<SpeedMeasurePluginOptions>); wrap(webpackConfig: Configuration): Configuration; } export = SpeedMeasurePlugin; }Copy the code

Modify the webpack. Prod. Ts:

import SpeedMeasurePlugin from 'speed-measure-webpack-plugin';

const mergedConfig = merge(commonConfig, {
  // ...
});

const smp = new SpeedMeasurePlugin();
const prodConfig = smp.wrap(mergedConfig);
Copy the code

Analysis of the bundle

yarn add BundleAnalyzerPlugin @types/BundleAnalyzerPlugin -D
Copy the code

We added an NPM script for the build with bundle analysis, because sometimes we don’t want to open a browser to analyze the size and proportion of each module:

"scripts": {
    "build": "cross-env-shell NODE_ENV=production ts-node --files -P scripts/tsconfig.json scripts/build"."build-analyze": "cross-env-shell NODE_ENV=production ts-node --files -P scripts/tsconfig.json scripts/build --analyze",},Copy the code

Modify the webpack. Prod. Ts:

/ / add
import { isAnalyze } from '.. /env';

if(isAnalyze) { mergedConfig.plugins! .push(new BundleAnalyzerPlugin());
}
Copy the code

When we want to see the size and proportion of each module in the bundle, we can run NPM run build-Analyze, which will automatically open the page shown in the figure above in the browser.

The gzip compressed version is available

We use the officially maintained Compression -webpack-plugin to prepare the gzip compressed version of each packaged file:

yarn add compression-webpack-plugin @types/compression-webpack-plugin -D
Copy the code

Track resource size after gzip

The size-plugin is a Google plugin that shows the compressed size of each Chunk of Webpack gzip and the size change compared to the last time. The part in the red box in the figure above shows that the gizip size increased by 11B after I added a sentence of log.

yarn add size-plugin -D
Copy the code

There is no official types file for this library, we added the external module declaration of size-plugin:

// scripts/typings/index.d.ts
declare module 'size-plugin' {
    import { Plugin } from 'webpack';

    interface SizePluginOptions {
        pattern: string;
        exclude: string;
        filename: string;
        publish: boolean;
        writeFile: boolean;
        stripHash: Function;
    }

    class SizePlugin extends Plugin {
        constructor(options? : Partial<SizePluginOptions>); } export = SizePlugin; }Copy the code
// webpack.prod.ts
const mergedConfig = merge(commonConfig, {
  plugins: [
    // Do not output file size to disk
    new SizePlugin({ writeFile: false})]});Copy the code

conclusion

Recently learned a word TL; DR is simply:

Too long; didn’t read.

I often do that myself, haha. It’s over 10,000 words so I don’t think many people will see this. I think the whole process is very natural, from the development environment to the production environment, from the basic configuration to optimize the console display, preparing the gzip compressed version of the icing on the cake. In fact, most of the time in writing this article was spent in looking up information. I tried my best to describe the functions of each plug-in, and if there are any noteworthy points, I will mention them in the code comments or text description. May I know the article basis for some more bad or not how to manually configure webpack classmates pressure is bigger, is likely to see not bottom go to, this is normal, I’ve also is such, but I think if you are able to bullet persevere, although a lot of places don’t understand, you will always learn something useful to you, Or you can save it and look it up in your dictionary. Many configurations in this article are not strongly coupled with typescript in react+typescript. If you add a vue-loader, you can use vUE to develop. More importantly, I hope that some readers can learn from it the spirit of exploration, scary does not mean impossible, only practice exploration can master true knowledge.

Finally we add our build script build.ts:

// scripts/build.ts
import webpack from 'webpack';

import prodConfig from './configs/webpack.prod';
import { isAnalyze } from './env';

const compiler = webpack(prodConfig);

compiler.run((error, stats) = > {
  if (error) {
    console.error(error);
    return;
  }

  const prodStatsOpts = {
    preset: 'normal'.modules: isAnalyze,
    colors: true};console.log(stats.toString(prodStatsOpts));
});
Copy the code

I have been busy with graduation and job hunting recently. The next post is probably in a month or so. If you have any questions, you can ask me in the comments section of Github Issues or publishing platform. If you think this article is useful to you, you can appreciate the star 😁.

The next article should cover how to integrate popular libraries like Ant Design, LoDash and optimize their packaging…

This article is original content, first published in personal blog, reproduced please indicate the source.