Webpack from entry to abandonment

Posted by brucemalti on Mon, 20 May 2019 03:47:59 +0200

Preface

Tips
If you have used webpack and have been using webpack 1, please refer to Migration from v1 to v2 (v2 There is little difference between v3 and v3) Understand the content of version changes properly, and then read this article selectively.

First of all, this article is based on the latest. webpack Version (i.e. v3.4.1) was written without fear of obsolescence for a long time. Secondly, this should be a very long article covering the basic usage methods, and the need for more advanced functions can be further studied by referring to official documents. Thirdly, even the basic functions, but also a wide range of content, I try to explain as easy as possible to understand, my learning process of doubts and pits one by one explanation, if there are omissions, please correct. Thirdly, in order to explain clearly and effectively, I will demonstrate writing from scratch. demo, as long as you follow it step by step, it will be much clearer. Finally, the official document is also a pitfall!

Who is Webpack?

To borrow the official statement:

webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.

In short, webpack is a module bundler that packages any resource such as JavaScript files, CSS files, images, etc. into one or a few files.

Why use a Web pack?

First of all, the definition has shown that webpack can package multiple resource modules into one or a few files, which means that compared with previous HTTP requests to obtain resources, only a small number of HTTP requests need to be initiated.

Tips 
To understand the significance of merging HTTP requests, see Here.

Second, webpack can transform your resources into the "format" most suitable for browsers and improve application performance. For example, only refer to the resources used by the application (excluding unused code), lazy load resources (only load the corresponding resources when needed). Thirdly, for the development stage, webpack also provides real-time loading and hot loading functions, which greatly saves development time. In addition, there are many outstanding points worth exploring. However, the core of webpack is the function of packaging.

webpack, gulp/grunt, npm, what's the difference between them?

webpack is a module bundler, which packs all modules into one or a few files, so that you can run the whole application with only a few files, instead of loading a lot of pictures, CSS files, JS files, font files and so on as before. gulp/grunt is an automated build tool, or task runner, which lets the code do all the repetitive manual operations, such as compressing JS code, CSS code, code checking, code compilation, etc. Automated build tools can not package all modules together, nor can they build dependency graphs between different modules. Comparing the two, gulp/grunt There are loader s and plugin s that can do some of the things gulp / grunt can do, but after all, the plug-ins of Web pack are not as rich as gulp / grunt plug-ins and can do very limited things. So somebody combines the two, putting web pack into gulp / grunt. However, a better way is to replace gulp / grunt with npm scripts. npm is the node package manager used to manage the third-party package of node, npm Good support for task commands eventually eliminates the need to write task code, replacing it with a few command lines from the ancestors. A few command lines alone are enough to complete all your module packaging and automated building requirements.

Ready to start

Let's first look at a complete configuration file for a webpack, which is Intermediate sample Of course, there are many configurations that you can't use even if the software is abandoned, so don't worry.

Basic configuration

Before you start, make sure you have installed the current one. Node A newer version.

Then execute the following command to create our demo directory:

$ mkdir webpack-demo && cd webpack-demo && npm init -y
$ npm i --save-dev webpack
$ mkdir src && cd src && touch index.js

We use tool libraries. lodash To demonstrate our demo. First install:

$ npm i --save lodash

src/index.js

import _ from 'lodash';

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    
  return element;
}

document.body.appendChild(component());

Tips
import and export are already ES6 standards, but not yet supported by most browsers (thankfully, Chrome 61 has started default support, see ____________ ES6 modules However, webpack provides support for this feature, but in addition to this feature, other ES6 features will not receive special support from webpack. Babel Translating (transpile).

Then create a new release directory:

cd .. && mkdir dist && cd dist && touch index.html

dist/index.html

<!DOCTYPE html>
<html>
<head>
    <title>webpack demo</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

Now, we run webpack to package index.js as bundle.js, which is installed locally. After webpack, the binary version of webpack can be accessed through node_modules/.bin/webpack.

$ cd ..
$ ./node_modules/.bin/webpack src/index.js dist/bundle.js # The first parameter is the packaged entry file, and the second parameter is the packaged export file.

Chirping, output a wave roughly as follows:

Hash: de8ed072e2c7b3892179
Version: webpack 3.4.1
Time: 390ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  544 kB       0  [emitted]  [big]  main
   [0] ./src/index.js 225 bytes {0} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]
    + 1 hidden module

Now you have your first bundle. js.

Use configuration files

Using webpack as above should be the most frustrating posture, so we need to use the configuration file of webpack to improve our posture level.

$ touch webpack.config.js

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js', // Entry Starting Point, which can specify multiple entry Starting Points
  output: { // Output, only one output configuration can be specified
    filename: 'bundle.js', // Output file name
    path: path.resolve(__dirname, 'dist') // The directory where the output file is located
  }
};

Implementation:

$ ./node_modules/.bin/webpack --config webpack.config.js # ` - config `Make the configuration file for webpack, default is `webpack.config.js'.`

So you can omit -- config webpack.config.js. But writing. / node_modules/.bin/webpack every time is really unpleasant, so we need to use NPM. Scripts.

package.json

{
  ...
  "scripts": {
    "build": "webpack"
  },
  ...
}

Tips
In npm scripts, we can directly refer to the binary version of the locally installed npm package by the package name without writing the entire path of the package.

Implementation:

$ npm run build

After a wave of output, you get the packaged file.

Tips
bulid is not npm The built-in attributes of scripts require npm run to execute scripts. See npm run.

Packing other types of files

Because other files and JS file types are different, loading them into JS files requires loader processing.

Loading CSS

We need to install two loader s to process CSS files:

$ npm i --save-dev style-loader css-loader

style-loader Adding CSS to DOM by inserting <style> tags, css-loader Interpretation import/require() interprets @import and url() as well.

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js', 
    path: path.resolve(__dirname, 'dist')
  },
  module: { // How to deal with different types of modules in a project
    rules: [ // A rule array that specifies how modules are handled when different modules are created
      {
        test: /\.css$/, // A regular expression or array of regular expressions that match a particular file
        use: [ // loader usage list applied to modules
          'style-loader',
          'css-loader'
        ]
      }
    ]
  }
};

Let's create a CSS file:

cd src && touch style.css

src/style.css

.hello {
  color: red;
}

src/index.js

import _ from 'lodash';
import './style.css'; // Introducing CSS files through `import'

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  element.classList.add('hello'); // Add the class name to the corresponding element
    
  return element;
}

document.body.appendChild(component());

Execute npm run build and open index.html to see the red font. CSS The file is now packaged in bundle.js. Open the browser console and you can see what webpack has done.

Loading pictures

$ npm install --save-dev file-loader

file-loader Indicates that webpack sends out the required objects in file format and returns the common URL of the file, which can be used for loading any file.

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js', 
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      { // Adding rules for loading images
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
};

We add the following pictures to the catalog of the current project:

  webpack-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
+   |- icon.jpg
    |- style.css
    |- index.js
  |- /node_modules

src/index.js

import _ from 'lodash';
import './style.css';
import Icon from './icon.jpg'; // Icon is the URL of the image

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  element.classList.add('hello');
  
  const myIcon = new Image();
  myIcon.src = Icon;

  element.appendChild(myIcon);
  
  return element;
}

document.body.appendChild(component());

src/style.css

.hello {
  color: red;
  background: url(./icon.jpg);
}

Then npm run build. Now you can see individual pictures and picture-based background pictures.

Load font

file-loader is also used to load fonts.

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js', 
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      },
      { // Rules for Increasing Font Loading
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
};

Add fonts to the current project catalog as follows:

  webpack-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
+   |- my-font.ttf
    |- icon.jpg
    |- style.css
    |- index.js
  |- /node_modules

src/style.css

@font-face {
  font-family: MyFont;
  src: url(./my-font.ttf);
}

.hello {
  color: red;
  background: url(./icon.jpg);
  font-family: MyFont;
}

After running the packaging command, you can see the packaged files and the changed pages.

Loading JSON files

Because webpack support for JSON files is built-in, it can be added directly.

src/data.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "author": "Sam Yang"
}

src/index.js

import _ from 'lodash';
import './style.css';
import Icon from './icon.jpg';
import Data from './data.json'; // Data variables contain objects parsed by JSON that can be used directly

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  element.classList.add('hello');

  const myIcon = new Image();
  myIcon.src = Icon;

  element.appendChild(myIcon);

  console.log(Data);
    
  return element;
}

document.body.appendChild(component());

For loading other files, you can find the corresponding loader.

Output management

We only have one input file before, but the reality is that we often have more than one input file. At this time, we need to input more than one entry file and manage the output file. We added a print.js file to the src directory.

src/print.js

export default function printMe() {
  console.log('I get called from print.js!');
}

src/index.js

import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

function component() {
  const element = document.createElement('div');
  const btn = document.createElement('button');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  // element.classList.add('hello');

  // const myIcon = new Image();
  // myIcon.src = Icon;

  // element.appendChild(myIcon);

  // console.log(Data);

  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = printMe;

  element.appendChild(btn);
    
  return element;
}

document.body.appendChild(component());

dist/index.html

<!DOCTYPE html>
<html>
<head>
    <title>webpack demo</title>
    <script src="./print.bundle.js"></script>
</head>
<body>
    <!-- <script src="bundle.js"></script> -->
    <script src="./app.bundle.js"></script>
</body>
</html>

webpack.config.js

const path = require('path');

module.exports = {
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js', // Dynamic generation of bundle names based on entry starting names can be done using folder structures such as "js/[name]/bundle.js"
    path: path.resolve(__dirname, 'dist')
  },
  // ...
};

Tips
Filename:'[name]. bundle. js'replaces [name] with the corresponding entry start name. For other available substitutions, see ____________. output.filename.

Now you can pack the files. But if we change the entry file name or add the entry file, index.html will not automatically reference the new file, and manual modification is too frustrating. It's time to use plug-ins (plugin) to accomplish this task. We use HtmlWebpackPlugin Automatic generation html file.

What's the difference between loader and plugin?
Loader, which focuses on the word "load", is used to pre-process files, and is only used to process different types of files when loading different types of files. And plugin, as its name implies, is used to increase the functionality of web pack, which acts on the whole process of Web pack construction. In the big company of webpack, loader is the security uncle, responsible for handling the different people who enter the company, while plugin is the staff of different positions in the company, responsible for various businesses in the company. Every time we add a new type of business needs, we need to add a plugin.

Install plug-ins:

$ npm i --save-dev html-webpack-plugin

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [ // Plug-in attributes, an array of instances of a plug-in
    new HtmlWebpackPlugin({
      title: 'webpack demo',  // Titles for generating HTML documents
      filename: 'index.html' // File name written to HTML file, default `index.html`
    })
  ],
  // ...
};

You can delete the index.html file of the dist folder and execute the packing command. Whew, we see that an index.html file has been automatically generated under the dist directory, but even if the original index.html is not deleted, the index.html generated by default by the plug-in will replace the original index.html.

Now, when you look at the dist directory in detail, although a new package file has been generated, the original package file bundle.js and other unused files still exist in dist. In the directory, so before each build we need the clear dist directory, which we use. CleanWebpackPlugin Plugins.

$ npm i clean-webpack-plugin --save-dev

webpack.config.js

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

module.exports = {
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']) // The first parameter is the string array of directories to clean up
  ],
  // ...
};

Packed, now, only the files generated by the package exist in dist.

development environment

webpack provides many functions that are easy to use in development. Let's take a look at them.

Using source map

When your code is packaged, it's hard to trace the original location of the error if the packaged code is wrong. At this point, we need a tool called source map, which maps the compiled code back to the original source code. Your error originates from a location in b.js before the packaged code, and the code map can tell you which pattern the error is. The location of the block. webpack By default, 10 styles of code mapping are provided, and their use can significantly affect the speed of building and rebuild ing. The differences between the ten styles can be seen. devtool . See how to choose mapping style. Webpack devtool source map . Here, we select the slower inline-source-map to display the error location accurately.

webpack.config.js

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

module.exports = {
  devtool: 'inline-source-map', // Control whether and how to generate source map
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  // ...
};

Now let's manually make some mistakes:

src/print.js

  export default function printMe() {
-   console.log('I get called from print.js!');
+   cosnole.log('I get called from print.js!');
  }

After packaging, open index.html and click on the button, and you will see the console display the following error:

 Uncaught ReferenceError: cosnole is not defined
    at HTMLButtonElement.printMe (print.js:2)

Now, we know exactly what went wrong and we can easily correct it.

Using webpack-dev-server

You must have the experience of building code manually and refreshing the browser manually to observe the effect of modification after every code modification is saved. This is very troublesome, so we need to load the code in real time. Fortunately, webpack provides support for loading code in real time. We need to install it. webpack-dev-server To gain support.

$ npm i --save-dev webpack-dev-server

webpack.config.js

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

module.exports = {
  devtool: 'inline-source-map',
  devServer: { // Detecting code changes and automatically recompiling and automatically refreshing browsers
    contentBase: path.resolve(__dirname, 'dist') // Setting the root directory of static resources
  },
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  // ...
};

package.json

{
  ...
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server --open"
  },
  ...
}

Tips
When using webpack-dev-server, webpack does not write all the generated files to disk, but puts them in memory to provide faster in-memory access for real-time updates.

Now, you can run npm start directly (start is The built-in properties of npm scripts can be run directly, and then the browser automatically loads the application pages, which are displayed at localhost:8080 by default.

HMR (Hot Module Replacement)

webpack provides support for hot replacement (or hot loading) of modules. This feature allows applications to replace, add, or delete modules while they are running without full overloading. For further understanding of its working mechanism, you can refer to Hot Module Replacement But that's not necessary. You can choose to skip the mechanism and read on.

Tips
Module Hot Replacement (HMR) only updates modules that have changed (replacement, addition, deletion) without reloading the entire page (live Reload), which can significantly speed up development. Once the hot mode of webpack-dev-server is opened, HMR will try to update the hot mode before attempting to reload the entire page.

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack'); // Introducing webpack makes it easy to call its built-in plug-ins

module.exports = {
  devtool: 'inline-source-map',
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    hot: true, // Tell dev-server that we are using HMR
    hotOnly: true // Specify that no page refresh is allowed if the hot load fails (this is the default behavior of webpack), so that we can know what kind of error the failure is due to.
  },
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    // print: './src/print.js'
  },
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    new webpack.HotModuleReplacementPlugin(), // Enable HMR
    new webpack.NamedModulesPlugin() // When printing log information, webpack defaults to using the digital ID of the module to refer to the module, which is not convenient for debug. This plug-in can replace it with the real path of the module.
  ],
  // ...
};

Tips
Webpack-dev-server creates a client script for each entry file. This script monitors the update of the dependent module of the entry file. If the entry file writes an HMR processing function, it can receive the update of the dependent module. Conversely, the update bubbles up until the client script still has no processing function, webpack-dev-server will reload the whole file. Page. If the entry file itself is updated, because it bubbles up to the client script and there is no HMR handler, it will cause page overload.

We have turned on the function of HMR. The interface of HMR has been exposed under the module.hot attribute. We only need to call it. HMR API Thermal loading can be realized. When the "loaded module" changes, the module relying on the module can detect the change and receive the module after the change.

src/index.js

import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

function component() {
  const element = document.createElement('div');
  const btn = document.createElement('button');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  // element.classList.add('hello');

  // const myIcon = new Image();
  // myIcon.src = Icon;

  // element.appendChild(myIcon);

  // console.log(Data);

  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = printMe;

  element.appendChild(btn);
    
  return element;
}

document.body.appendChild(component());

if(module.hot) { // It's customary to check whether the `module.hot'property is accessible.
  module.hot.accept('./print.js', function() { // Accept updates to a given dependent module and trigger a callback function to respond to these updates
    console.log('Accepting the updated printMe module!');
    printMe();
  });
}

npm start. To demonstrate the effect, we made the following modifications:

src/print.js

  export default function printMe() {
-   console.log('I get called from print.js!');
+   console.log('Updating print.js...');
  }

We will see that the information printed by the console contains the following lines:

index.js:33 Accepting the updated printMe module!
print.js:2 Updating print.js...
log.js:23 [HMR] Updated modules:
log.js:23 [HMR]  - ./src/print.js
log.js:23 [HMR] App is up to date.

Tips
webpack-dev-server inline mode (This is the default mode) A client script is created for each entry, so you can see that some information is repeated twice in the output above.

But when you click on the button on the page, you will find that the console outputs the information from the old printMe function, because the onclick event is still bound to the original printMe function. We need to update the binding in module.hot.accept.

src/index.js

import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

// ...

// document.body.appendChild(component());
var element = component();
document.body.appendChild(element);

if(module.hot) {
  module.hot.accept('./print.js', function() {
    console.log('Accepting the updated printMe module!');
    // printMe();
    
    document.body.removeChild(element);
    element = component();
    document.body.appendChild(element);
  });
}

Tips
uglifyjs-webpack-plugin Upgrade to The code of ES 6 cannot be compressed correctly at v0.4.6, so some of the above codes use ES5 to temporarily facilitate later compression, as detailed. #49.

Module hot replacement can also be used for style modification, with the same effect as console modification.

src/index.js

import _ from 'lodash';
import printMe from './print.js';
import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

// ...

For npm start, make the following modifications:

/* ... */

body {
  background-color: yellow;
}

You can find that without overloading the page, we have hot-loaded the style modification, stick!

production environment

Automatic mode

We just need to run webpack -p (equivalent to webpack) optimize-minimize --define process.env.NODE_ENV="'production'") This command automatically builds the production version of the application. This command completes the following steps:

  • Use Uglify JsPlugin (webpack. optimize. Uglify JsPlugin) Compress JS files (this plug-in and uglifyjs-webpack-plugin Same)
  • Run the Loader Options Plugin plug-in, which is used for migration, see document
  • Setting NodeJS environment variables triggers some package packages to compile in different ways

It is worth mentioning that the process.env.NODE_ENV environment variable set by webpack-p is used for compiled code, which is valid only in packaged code. If in Referring to this environment variable in the webpack configuration file yields undefined, as you can see. #2537 . But sometimes we do need to What about using process.env.NODE_ENV in the webpack configuration file? One way is to run NODE_ENV='production' The webpack-p command, however, is problematic in Windows. In order to solve the compatibility problem, we adopt cross-env Solve cross-platform issues.

$ npm i --save-dev cross-env

package.json

{
  ...
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack -p",
    "start": "webpack-dev-server --open"
  },
  ...
}

Now you can use process.env.NODE_ENV in the configuration file.

webpack.config.js

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

module.exports = {
  // ...
  output: {
    // filename: 'bundle.js',
    // filename: '[name].bundle.js',
    filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js', // Use `process.env.NODE_ENV'in the configuration file`
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    // New webpack. HotModuleReplacement Plugin (), // Turn off HMR functionality
    new webpack.NamedModulesPlugin()
  ],
  // ...
};

Tips
[chunkhash] cannot be used with HMR, in other words, [chunkhash] (or [hash]) should not be used in development environments, which can lead to many problems. See you for details. #2393 Sum #377.

build, we get the production version of the compressed package file.

Multiple Profile Configuration

Sometimes we need to configure different configurations for different environments. Simple method Here we adopt more advanced methods. Prepare a basic configuration file, which contains all the configurations contained in the environment, and then use the ____________ webpack-merge Merge and export it with the configuration file for a specific environment, which reduces the duplication of the basic configuration.

$ npm i --save-dev webpack-merge

webpack.common.js

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

module.exports = {
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist'])
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
};

webpack.dev.js

const path = require('path');
const webpack = require('webpack');
const Merge = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');

module.exports = Merge(CommonConfig, {
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    hot: true,
    hotOnly: true
  },
  output: {
    filename: '[name].bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development') // The `process.env.NODE_ENV'variable is set in the compiled code.
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
});

webpack.prod.js

const path = require('path');
const webpack = require('webpack');
const Merge = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');

module.exports = Merge(CommonConfig, {
  devtool: 'cheap-module-source-map',
  output: {
    filename: '[name].[chunkhash].js'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new webpack.optimize.UglifyJsPlugin()
  ]
});

package.json

{
  ...
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack -p",
    "start": "webpack-dev-server --open",
    "build:dev": "webpack-dev-server --open --config webpack.dev.js",
    "build:prod": "webpack --progress --config webpack.prod.js"
  },
  ...
}

Now you just need to execute npm run build:dev or npm run build:prod will be available for development or production!

Tips
See the webpack command line options Command Line Interface.

code separation

Inlet separation

Let's first create a new file:

cd src && touch another.js

src/another.js

import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));

webpack.config.js

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

module.exports = {
  // ...
  entry: {
    app: './src/index.js',
    // print: './src/print.js'
    another: './src/another.js'
  },
  // ...
};

In cd. & & NPM run build, we found that we got two large files with the code separated by the entries, because both entries introduced lodash, which caused a lot of redundancy. In the same page, we only need to introduce a lodash.

Extract the same part

We use CommonsChunkPlugin Plug-ins extract the same parts and place them in a separate module.

webpack.config.js

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

module.exports = {
  // devtool: 'inline-source-map',
  // ...
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common' // The module name of the extracted module
    }),
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

In build, you can see that the results include the following parts:

    app.bundle.js    6.14 kB       0  [emitted]  app
another.bundle.js  185 bytes       1  [emitted]  another
 common.bundle.js    73.2 kB       2  [emitted]  common
       index.html  314 bytes          [emitted]

We separated lodash.

Dynamic introduction

We can also choose to implement code separation by introducing it dynamically, with the help of ___________ import() To achieve it.

webpack.config.js

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

module.exports = {
  // ...
  entry: {
    app: './src/index.js',
    // print: './src/print.js'
    // another: './src/another.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    chunkFilename: '[name].bundle.js', // Specifies the name of the output of a non-entry block file
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist'])
    // new webpack.optimize.CommonsChunkPlugin({
    //   name: 'common'
    // }),
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

src/index.js

// import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

function component() {
  // The original content of this function is commented out.

  return import(/* webpackChunkName: "lodash" */ 'lodash').then(function(_) {
    const element = document.createElement('div');
    const btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;

    element.appendChild(btn);

    return element;
  }).catch(function(error) {
    console.log('An error occurred while loading the component')
  });
}

// document.body.appendChild(component());
// var element = component();
// document.body.appendChild(element);

// The original hot-loaded part is completely commented out ___________.

component().then(function(component) {
   document.body.appendChild(component);
 });

Tips
Note that the /* webpackChunkName:'lodash'*/ comment above is not optional. It can help us to name the separated module lodash.bundle.js instead of [id].bundle.js in conjunction with output.chunkFilename.

Now look at build.

Lazy loading

Now that we have import(), we can choose to load the corresponding module when needed, which reduces the pressure of loading a large number of unnecessary modules when the application is initialized, which can make our application run more efficiently.

src/print.js

console.log('The print.js module has loaded! See the network tab in dev tools...');

export default function printMe() {
  // console.log('Updating print.js...');
  console.log('Button Clicked: Here\'s "some text"!');
}

src/index.js

import _ from 'lodash';
// Other introductions of annotations...

function component() {
  const element = document.createElement('div');
  const btn = document.createElement('button');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  // element.classList.add('hello');

  // const myIcon = new Image();
  // myIcon.src = Icon;

  // element.appendChild(myIcon);

  // console.log(Data);

  btn.innerHTML = 'Click me and check the console!';
  // btn.onclick = printMe;

  element.appendChild(btn);

  btn.onclick = function() {
    import(/* webpackChunkName: "print" */ './print')
    .then(function(module) {
      const printMe = module.default; // Default functions for introducing modules

      printMe();
    });
  };
    
  return element;

  // The original dynamic introduction of annotations...
}

document.body.appendChild(component());
// var element = component();
// document.body.appendChild(element);

// Annotation of Hot Loading Part

// component().then(function(component) {
//    document.body.appendChild(component);
//  });

The console has no output at this time. Click on the button and you will see the following output of the console:

print.bundle.js:1 The print.js module has loaded! See the network tab in dev tools...
print.bundle.js:1 Button Clicked: Here's "some text"!

Explain that the print module is only introduced when we click on it.

Caching

When the browser first loads the website, it downloads many files. In order to reduce the pressure of downloading a large number of resources, the browser caches the resources so that the browser can load the website more quickly, but we need to update the file when the content of the file changes.

We can start with the output file name:

webpack.config.js

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

module.exports = {
  // ...
  output: {
    // filename: 'bundle.js',
    filename: '[name].[chunkhash].js',
    // chunkFilename: '[name].bundle.js',
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // ...
};

Tips
[chunkhash] is content-related, and as soon as the content changes, the hash of the file name changes after the build.

Another important point is to extract third-party libraries and put them in separate modules, because they are unlikely to change frequently, so there is no need to load these modules many times, and the extraction method is used. CommonsChunkPlugin Plug-in, this plug-inAbove It was mentioned that when the name of the entry file is specified, it will extract the entry file into a single file, and if it is not specified, it will extract the entry file. Runtime code of webpack.

webpack.config.js

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

module.exports = {
  // ...
  entry: {
    app: './src/index.js',
    vendor: [ // Third-party libraries can be consolidated together at this entry
      'lodash'
    ]
    // print: './src/print.js'
    // another: './src/another.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].bundle.js',
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor' // Put the code at the vendor entrance into the vendor module
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime' // Place the runtime code of webpack itself in the runtime module
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

Tips
The Commons Chunk Plugin instance containing vendor must be before it contains runtime, otherwise it will report an error.

src/index.js

// import _ from 'lodash';
// ...

// ...

If we create a new file h.js under src, then introduce it into index.js, save it and build it, we find that there are some unchanged modules. hash also changed because their module.id changed after adding h.js, but this is obviously unreasonable. In the development environment, we can use NamedModules Plugin replaces id with a specific path name. In the production environment, we can use it. HashedModuleIdsPlugin.

webpack.config.js

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

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new webpack.HashedModuleIdsPlugin(), // Replace the original `module.id'.`
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

When you perform the previous wave of operations, you will find that the module hash that has not been modified has not changed.

Shimming

Tips
You can simply interpret shim as a small library for API compatibility.

When using jQuery, we habitually use the $or jQuery variable, using const every time. The introduction of $= require("jquery") is too cumbersome. Wouldn't it be nice to set these two variables directly as global variables? This allows you to use these two variables directly in each module. In order to be compatible with this approach, we use the ____________ ProvidePlugin Plug-ins do this for us.

$ npm i --save jquery

webpack.config.js

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

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new webpack.ProvidePlugin({ // Setting global variables
      $: 'jquery',
      jQuery: 'jquery'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

src/print.js

console.log('The print.js module has loaded! See the network tab in dev tools...');
console.log($('title').text()); // Using jQuery

export default function printMe() {
  // console.log('Updating print.js...');
  console.log('Button Clicked: Here\'s "some text"!');
}

build, click the page button, and it works.

In addition, if you need to set the global variables of some modules when they are loaded, see ____________ Here.

A little crap at the end

Finally finished:) Thank you for your patience to see here. The configuration of webpack is still a bit cumbersome. But somebody said that it would take a lot of time in the early stage, and it would greatly improve your efficiency in the later stage. So, let's take this one. If you have other needs, you can continue to see them. Official Documentation . If you encounter difficulties, you can find:

I put the demo file I wrote in it. Here.

Reference

Topics: Webpack npm JSON JQuery