[webpack series] advanced level

Time:2021-12-8

This article will continue to introduce morewebpackConfiguration, it is recommended to read it first[webpack series] BasicsContent of the. If you find any errors in the text, please correct them in the comments area. All the code in this article can be found ingithubFound.

Packaging multi page applications

Previously, we configured a single page application, but our application may need to be a multi page application. Now let’s do a multi page applicationwebpackto configure.
Let’s take a look at our directory structure first

├── public
│   ├── detail.html
│   └── index.html
├── src
│   ├── detail-entry.js
│   ├── index-entry.js

publicThere are belowindex.htmlanddetail.htmlTwo pages, corresponding tosrcThere are belowindex-entry.jsanddetail-entry.jsTwo entry files.

staywebpack.config.jsto configure

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
// ...

module.exports = {
  entry: {
    index: path.resolve(__dirname, 'src/index-entry.js'),
    detail: path.resolve(__dirname, 'src/detail-entry.js')
  },
  output: {
    Path: path. Resolve (_dirname, 'dist'), // output directory
    Filename: '[name]. [hash: 6]. JS', // output file name
  },
  plugins: [
    // index.html
    new HtmlWebpackPlugin({
      Template: path. Resolve (_dirname, 'public / index. HTML'), // specify the template file. If not specified, the default index.html file will be generated
      Filename: 'index. HTML', // packed file name
      Chunks: ['index '] // specify the imported JS file, corresponding to the chunkname configured in the entry 
    }),
    // detail.html
    new HtmlWebpackPlugin({
      Template: path. Resolve (_dirname, 'public / detail. HTML'), // specify the template file. If not specified, the default index.html file will be generated
      Filename: 'detail. HTML', // packed file name
      Chunks: ['detail '] // specify the imported JS file, corresponding to the chunkname configured in the entry
    }),
    //Automatically clear dist directory before packaging
    new CleanWebpackPlugin()
  ]
}

npm run buildAfter that, you can see the generateddistThe contents are as follows

dist
├── assets
│   └── author_ee489e.jpg
├── detail.dbcb15.js
├── detail.dbcb15.js.map
├── detail.html
├── index.dbcb15.js
├── index.dbcb15.js.map
└── index.html

index.htmlThe packaged has been introduced into the pageindex.dbcb15.jsDocuments,detail.htmlDocuments have also been introduceddetail.dbcb15.jsFile. For more configurations, seehtml-webpack-plugin

Separate CSS styles from the generated file

webpack4yescssImprovement of module support and in processingcssSome adjustments have also been made in the way of file extractionmini-css-extract-pluginTo replace the previously usedextract-text-webpack-plugin, it is easy to use.

The plug-in willcssExtract to a separate file for each containingcssofjsCreate a filecssFiles, supportcssandsourcemapOn demand loading of.
Andextract-text-webpack-pluginIt has the following advantages

  1. Asynchronous loading
  2. No duplicate compilation (performance)
  3. Easier to use
  4. Specific tocss

installextract-text-webpack-plugin

npm i -D mini-css-extract-plugin

to configurewebpack.config.js

// webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// ...

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(c|le)ss$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'],
        exclude: /node_modules/
      },
      {
        test: /\.sass$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
        exclude: /node_modules/
      },
      // ...
    ]
  },
  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash:6].css'
    })
  ]
}

npm run buildThen you’ll find outdist/cssThe catalogue has been extractedcssThe file is.

[webpack series] advanced level

At this time, we found two problems:

  1. Package generatedcssThe file is not compressed.
  2. All files are namedhashAll parts are the same, and there is a cache problem.

Compress CSS files

adoptoptimize-css-assets-webpack-pluginPlug in compressioncsscode

npm i -D optimize-css-assets-webpack-plugin

to configurewebpack.config.js

// webpack.config.js
//...
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
  //...
  plugins: [
    //...
    new OptimizeCssPlugin()
  ]
}

So you cancssThe file is compressed.

For the second question, we first need to understandhashchunkHashcontentHashThe difference.

Differences and uses of hash, chunkhash and contenthash

hash

hashIs based on the wholemodule identifierFrom the sequence calculation, the default webpack is to assign one to each moduleidIt is used to handle the dependencies between modules. By defaultidThe naming rule is to assign an integer according to the order of module introduction(123…)。 Any modification, addition or deletion of a module’s dependency will affect the whole systemidThe sequence is affected and thus changedhashValue. That is, every time you modify or add or delete any file, the name of all fileshashThe values will change and the file cache for the entire project will be invalidated.

output: {
  Path: path. Resolve (_dirname, 'dist'), // output directory
  Filename: '[name]. [hash: 6]. JS', // output file name
}

new MiniCssExtractPlugin({
  filename: 'css/[name].[hash:6].css'
})

[webpack series] advanced level

You can see the packagejsandcssDocumenthashThe values are the same, so this is unreasonable for modules that have not changed.

Of course, you can see that for resources such as pictureshashYou can still generate a unique value.

chunkhash

chunkhashAccording to different entry files, analyze the dependent files and build the correspondingchunk, generate the corresponding hash value. We willfilenameconfigurechunkhashTake a look at the packaging results.

output: {
  Path: path. Resolve (_dirname, 'dist'), // output directory
  Filename: '[name]. [chunkash: 6]. JS', // output file name
}

new MiniCssExtractPlugin({
  filename: 'css/[name].[chunkhash:6].css'
})

[webpack series] advanced level
You can see the package at this timeindex.jsanddetail.jsofchunkhashIt’s different. But you’ll findindex.jsandindex.cssas well asdetail.jsanddetail.cssofchunkhashIt is consistent and can be changed arbitrarilyjsperhapscssWill cause corresponding problemscssandjsDocumentchunkhashThis is unreasonable. So it was pulled out of herecssFile will usecontenthash, to distinguishcssDocuments andjsUpdate of files.

contenthash

contenthashIt is for the file content level. Only the content of your own module has changed, sohashThe value changes.

output: {
  Path: path. Resolve (_dirname, 'dist'), // output directory
  Filename: '[name]. [chunkash: 6]. JS', // output file name
}

new MiniCssExtractPlugin({
  filename: 'css/[name].[contenthash:6].css'
})

[webpack series] advanced level
OK, you can see the separationcssThe file has been linked to the entry filehashValues are distinguished.

How to use

In order to achieve an ideal cache, we generally use them as follows:

  1. JSFile usagechunkhash
  2. DetachedCSSStyle file usagecontenthash
  3. gif|png|jpe?g|eot|woff|ttf|svg|pdfSuch usehash

Load on demand

Most of the time, we don’t need to load all the data in one page at oncejsperhapscssFile, but it should be used before loading the corresponding filejsperhapscssFile.

import()

For example, now we need to click a button to use the correspondingjscssFile, requiredimport()Syntax:

// index-entry.js

import './index.sass';
//...
const handle = () => import('./handle');
const handle2 = () => import('./handle2');


document.querySelector('#btn').onclick = () => {
  handle().then(module => {
    module.handleClick();
  });

  handle2().then(module => {
    module.default();
  });
}
// handle.js

import './handle.css';

export function handleClick () {
  console.log('handleClick');
}
// handle2.js

export default function handleClick () {
  console.log('handleClick2');
}

npm run buildAs you can see, there’s more here3A file, and it will not be loaded until we click the button3Files.

[webpack series] advanced level

webpackChunkName

These files may not be easy to distinguish. We can set themwebpackChunkNameTo define the generated file name

// index-entry.js
const handle = () => import(/* webpackChunkName: "handle" */ './handle');
const handle2 = () => import(/* webpackChunkName: "handle2" */ './handle2');

We’ll put these documentshashLength set to8Distinguish

// webpack.config.js
module.exports = {
    output: {
      Path: path. Resolve (_dirname, 'dist'), // output directory
      Filename: '[name]. [chunkash: 6]. JS', // output file name
      chunkFilename: '[name].[chunkhash:8].js'
    }
    // ...
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:6].css',
      chunkFilename: 'css/[name].[contenthash:8].css'
    }),

npm run buildView later

[webpack series] advanced level
Of course, we can alsohandleandhandle2DocumentwebpackChunkNameSet to the same, so that the two files will be packaged together to generate a file, which can reduce the number of requests.

Hot module replacement (HMR)

During the development process, we hope to load our modified code without refreshing the page in the browser, so as to improve our development efficiency. Let’s see how to configure:

  1. openwebpack-dev-serverHot update switch for
  2. useHotModuleReplacementPluginplug-in unit

HotModuleReplacementPluginThe plug-in isWebpackIt comes with it. It’s in thewebpack.config.jsDirect configuration

// webpack.config.js

module.exports = {
  devServer: {
    //...
    hot: true
  },
  plugins: [
    //...
    New webpack. Hotmodulereplacementplugin() // hot update plugin
  ]
}

Add in portal file

if (module && module.hot) {
    module.hot.accept()
}

This completes the hot update configuration, but at this timewebpackThe packing was wrong.

[webpack series] advanced level
Did a searchRelated issues, in the development environment, we useHotModuleReplacementPluginYou need to usehashTo output the file, usechunkhashWill causewebpackAn error is reported, but the production environment is OK. But now we just passprocess.env.NODE_ENVThis variable is obviously not a good way to distinguish the environment.
We’d better be able to distinguish between the configuration files of the development environment and the production environment.

Define configurations for different environments

We can define different configuration files for different environments, but these files will have a large number of similar configurations. At this time, we can define files as follows:

  1. webpack.base.js: define common configuration
  2. webpack.dev.js: define the configuration of the development environment
  3. webpack.prod.js: define the configuration of the production environment

We can pull some common configurations away fromwebpack.base.js, and thenwebpack.dev.jsandwebpack.prod.jsConfigure the corresponding environment. We still need to passwebpack-mergeTo merge the two profiles.

installwebpack-merge

npm i -D webpack-merge

Now?webpack.dev.jsThat’s it

// webpack.dev.js

const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.base');

module.exports = merge(baseConfig, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    Port: '9000', // the default is 8080
    Compress: true, // whether to enable gzip compression
    hot: true
  },
  output: {
    Path: path. Resolve (_dirname, 'dist'), // output directory
    Filename: '[name]. [hash: 6]. JS', // output file name
    chunkFilename: '[name].[hash:8].js'
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash:6].css',
      chunkFilename: 'css/[name].[hash:8].css'
    }),
    New webpack. Hotmodulereplacementplugin() // hot update plugin
  ]
});

At the same time, it is necessary topackage.jsonSpecify our profile in

// package.json

"scripts": {
  "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js",
  "build": "cross-env NODE_ENV=production webpack --config webpack.config.pro.js"
},

At this time, we can gracefully distinguish the configurations of different environments.

Copy static resources

Sometimes we need tohtmlA packaged third-party plug-in library is directly referenced in. This library does not need to be passedwebpackcompile. Like uslibThere is a in the directorylib-a.js, need topublic/index.htmlReference it directly in.

<!-- public/index.html -->
<script></script>

At this timebuildYou’ll find out laterdistNext is NolibDirectory, this file will not be found at this time. At this time, we need helpCopyWebpackPluginThis plug-in helps us put the files in the root directorylibCopy directory todistUnder the directory.

Install firstCopyWebpackPlugin

npm i -D CopyWebpackPlugin

to configurewebpack.config.js

// webpack.config.js

const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  //...
  plugins: [
    //...
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, 'lib'),
        to: path.resolve(__dirname, 'dist/lib')
      }
    ])
  ]
}

Run after this timenpm run buildYou’ll find out,distIt already exists in the directorylibDirectories and files.

For more configurations, seecopy-webpack-plugin

Resolve configuration

WebpackAfter startup, all dependent modules will be found from the configured entry module,Resolveto configureWebpackHow to find the file corresponding to the module.Webpackbuilt-inJavaScriptThe modular syntax parsing function will use the rules agreed in the modular standard by default, but you can also modify the default rules according to your own needs.

alias

resolve.aliasThe configuration item maps the original import path to a new import path through an alias.
For example, we areindex-entry.jsIntroduced inlib/lib-b.js, you may need to introduce

import '../lib/lib-b.js';

When the directory level is deep, the relative path becomes difficult to identify. Then we can configurelibAn alias for.

// webpack.config.js

module.exports = {
  //...
  resolve: {
    alias: {
      '@ lib': path. Resolve (_dirname, 'lib') // add an alias to the Lib directory
    }
  }
}

At this time, no matter which level of the directory you are at, you just need to introduce it like this

import '@lib/lib-b.js';

extensions

If the file is imported without a suffix,webpackIt will automatically add a suffix to try to access whether the file exists.resolve.extensionsUsed to configure the suffix list used in the attempt. The default is

extensions: ['.js', '.json']

That is, when you meetimport '@lib/lib-b';When,webpackI’ll look for it first@lib/lib-b.jsFile, if the file does not exist, look for it@lib/lib-b.jsonIf the file is still missing, an error will be reported.

If you want to use other suffix files first, such as.tsFile, which can be configured as follows

// webpack.config.js

module.exports = {
  //...
  resolve: {
    alias: {
      '@ lib': path. Resolve (_dirname, 'lib'), // add an alias for the Lib directory
      Extensions: ['. Ts','. JS', '. JSON'] // from left to right
    }
  }
}

Then you’ll find it first.tsYes. However, generally, we will put the high-frequency suffix in front, and the array should not be too long to reduce the number of attempts, otherwise it will affect the packaging speed.

Now let’s introducejsYou can omit the suffix when you file.

modules

resolve.modulesto configurewebpackWhich directories to find third-party modules? By default, onlynode_modulesSearch under the directory. If the modules under a folder in the project are often imported, you do not want to write a long path, such asimport '../../../components/link', you can configureresolve.modulesTo simplify.

// webpack.config.js

module.exports = {
  //...
  resolve: {
    Modules: ['. / SRC / components','node_modules'] // find from left to right
  }
}

Then you can passimport 'link'Introduced.

mainFields

Some third-party modules provide several copies of code for different environments. For example, they are provided separatelyes5andes6of2Code, here2The location of the code is written inpackage.jsonIn the file.

{
  "Jsnext: main": "ES / index. JS"
  "Main": "lib / index. JS" // code entry file using Es5 syntax
}

webpackWill be based onmainFieldsTo determine which code is preferred,mainFieldsThe default configuration is as follows:

mainFields: ['browser', 'main']

If you want to give priority toES6The code can be configured as follows:

mainFields: ['jsnext:main', 'browser', 'main']

enforceExtension

resolve.enforceExtensionIf configured astrue, all import statements must have a file suffix.

enforceModuleExtension

enforceModuleExtensionandenforceExtensionThe effect is similar, butenforceModuleExtensionOnly rightnode_modulesThe module under is valid. Because most of the import statements in the installed third-party modules do not have a file suffix, if you configure it at this timeenforceExtensionbytrue, then you need to configureenforceModuleExtension: falseTo be compatible with third-party modules.

Using web pack to solve cross domain problems

When developing locally, the port number of the front-end project is9000, but the server may be9001, according to the browser’s homology policy, you cannot directly request to the back-end service. Of course, you can configure it on the back endCORSCross domain can also be realized through related headerswebpackTo solve cross domain problems.

First, we set up a back-end service and install itkoakoa-router

npm i -D koa koa-router

newly buildserver/index.js

// server/index.js

const Koa = require('koa');
const KoaRouter = require('koa-router');

const app = new Koa();

//Create router instance object
const router = new KoaRouter();

//Register routing
router.get('/user', async (ctx, next) => {
  ctx.body = {
    code: 0,
    data: {
      Name: 'Arlene 11'
    },
    msg: 'success'
  };
});

app.use(router.routes());  //  Add routing Middleware
app.use(router.allowedMethods()); //  Make some restrictions on requests

app.listen(9001);

usenode server/index.jsAfter starting the service, inhttp://localhost:9001/userYou can access the results.

Modify laterhandle.js, the interface will be requested after clicking the button

import './handle.css';

export function handleClick () {
  console.log('handleClick');

  fetch('/api/user')
    .then(r => r.json())
    .then(data => console.log(data))
    .catch(err => console.log(err));
}

This is the interface message that will be found404, let’s configure itwebpack.config.dev.js

// webpack.config.dev.js

module.exports = {
  //...
  proxy: {
    '/api': {
      target: 'http://127.0.0.1:9001/',
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}

Request tohttp://localhost:9000/api/userWill now be proxied to the requesthttp://localhost:9001/user。 Click the button to initiate the request:

[webpack series] advanced level

last

Now, we’re rightwebpackWe have a better understanding of the configuration of. Let’s try it. All the code in this article can be viewedgithub

The follow-up will continue to be launchedwebpackOther contents of the series~

If you like this article, please praise it~

[webpack series] advanced level

More interesting content, welcome to WeChat official account.