Poking fun at the

Webpack has been popular since its inception. As a powerful packaging tool, it only appears during the initial or optimization phases of a project. If you are not involved in the construction of the project, the chance of contact is almost zero. Even if they participated in the project, they would scramble together things from the Internet due to the short period of the project.

Take a look at webpack tutorials on the web, either to skim through some of the general configuration items; Or it is too deep into the principle, in the actual operation of useless. I’ve been going through the official Webpack documentation a few times lately, and it smells good. Chinese version of the official document, easy to understand, thank the translation team’s hard dedication. After watching it, although it is not perfect, but it will not be too hard to cope.

For blog posts on this tool class, I still use the style of domesticating JavaScript with Type, linking concepts together. As for the details, it’s a matter of official documentation.

This article is based on WebPack V4.31.0.

Tapable

Tapable is a small library that allows you to add and apply plug-ins to a javascript module. It can be inherited or blended into other modules. The EventEmitter class, similar to NodeJS, focuses on the firing and processing of custom events. In addition, Tapable allows you to access the emittee or producer of an event using the parameters of a callback function.

Tapable is the core of Webpack, many objects in Webpack (compile, compilation, etc.) are extended from Tapable, including Webpack is also an instance of Tapable. Objects that extend from Tapable have a number of hooks inside them throughout the webPack build process. We can use these hooks to do whatever we want when they are triggered.

Webpack aside, let’s take a look at the simple use of Tapable.

// Main.js
const {
  SyncHook
} = require("tapable");
class Main {
  constructor(options) {
    this.hooks = {
      init: new SyncHook(['init'])};this.plugins = options.plugins;
    this.init();
  }
  init() {
    this.beforeInit();
    if (Array.isArray(this.plugins)) {
      this.plugins.forEach(plugin= > {
        plugin.apply(this); })}this.hooks.init.call('Initializing... ');
    this.afterInit();
  }
  beforeInit() {
    console.log('Before initialization... ');
  }
  afterInit() {
    console.log('After initialization... '); }}module.exports = Main;
// MyPlugin.js
class MyPlugin {
  apply(main) {
    main.hooks.init.tap('MyPlugin', param => {
      console.log('Init hook, do something; ', param); }); }};module.exports = MyPlugin;
// index.js
const Main = require('./Main');
const MyPlugin = require('./MyPlugin');
let myPlugin = new MyPlugin();
new Main({ plugins: [myPlugin] });

// Before initialization...
// init hook, do something; Initializing...
// After initialization...
Copy the code

Call (params) is similar to EventEmitter. Emit (‘init’, params). Tap is similar to EventEmitter. On (‘init’, callback), which binds something we want to do to the init hook. The WebPack custom plug-in, described later, is a hook in the WebPack that inserts the custom.

Clarify concepts

  • Dependency graph In a single-page application, a single entry file is required to integrate various files scattered under a project. What is a dependency, what the current file needs, and what is a dependency of the current file. The form of dependency introduction is as follows:

    • ES2015 importstatements
    • CommonJS require()statements
    • AMD definerequirestatements
    • Style (url(...)) or HTML file (<img src=... >)
  • The entry Point indicates which module WebPack should use as a starting point for building its internal dependency Graph.

  • The Output attribute tells WebPack where to export the bundles it creates and how to name those files.

  • Modules determine how different types of modules in a project are handled. For example, set up loader to handle various modules. Set noParse to ignore modules that do not need WebPack parsing.

  • Resolve (resolve) sets how the module is resolved. When referencing dependencies, you need to know the path relationships between dependencies and which resolution rules to follow. For example, set the path alias (alias), resolve the module search directory (modules), resolve the loader package path (resolveLoader) and so on.

  • Externals prevent import packages from being packaged into bundles, and instead obtain these extension dependencies externally at runtime. For example, if the project references jQuery CDN resources, use import $from ‘jQuery ‘; Externals: {jQuery: ‘jQuery’} should be configured to remove jQuery from the bundle.

  • Plugins are used to customize the WebPack build process in a variety of ways. You can use the hooks in WebPack to do some optimizations or tricks.

  • Development Settings (devServer) are, as the name implies, options used during development. Development services, for example, root (contentBase), module hot replacement (hot, need to use with HotModuleReplacementPlugin), agents (proxy), etc.

  • Mode (mode) provides the mode configuration option that tells WebPack to use the built-in optimizations for the appropriate environment. Concrete visible mode (mode)

  • Optimization, starting with WebPack 4, performs different optimizations depending on the mode you choose, but all optimizations can be manually configured and rewritten. For example, CommonsChunkPlugin is replaced by Optimization.splitchunks.

Webpack is just about these configuration items, and it’s relatively easy to get started with these concepts.

The separation

Front-end projects are getting more complex, and if you end up exporting as a bundle, it can greatly affect the loading speed. Cutting bundles, controlling resource loading priorities, loading on demand or in parallel, and proper application can greatly shorten loading time. The official documentation provides three common code separation methods:

  • The entry point configures multiple entry files, and then bundles the resulting entry files in and out of HTML.

    // webpack.config.js
    entry: {
        index: './src/index.js'.vendor: './src/vendor.js'
    }
    output: {
        filename: '[name].bundle.js',},plugins: [
    new HtmlWebpackPlugin({
        chunks: ['vendor'.'index'"})"Copy the code

    However, if the same module exists in both files, this means that the same module has been loaded twice. At this point, we need to extract the duplicate modules.

  • Preventing duplication In older versions of Webpack, the CommonsChunkPlugin was often used to extract common modules. SplitChunksPlugin is replaced in the new version, and can be set via optimization.splitChunks, which is common in multi-page applications.

  • Dynamic import means loading modules as needed, rather than loading them all at once. Webpack also provides prefetching and preloading methods. Non-entry chunk, which we can name by chunkFilename. For example, dynamic import of VUE routes.

    // webpack.config.js
    output: {
      chunkFilename: '[name].bundle.js',}// index.js
    import(/* webpackChunkName: "someJs" */ 'someJs');
    import(/* webpackPrefetch: true */ 'someJs');
    import(/* webpackPreload: true */ 'someJs');
    Copy the code

The cache

Browser-based caching strategy, we know that if the local cache hits, there is no need to request the resource again. Modules that are not changed frequently or will not be changed at all can be removed.

  // webpack.config.js
  output: {
    filename: '[name].[contenthash].js',}Copy the code

The idea is that as long as the contents of the module do not change, the corresponding name will not change, and the cache will work. In fact, this is not the case. The webPack file contains not only the user’s own code, but also the code that manages the user’s code, such as Runtime and Manifest.

Integration between module dependencies is not a simple splicing of code, but involves module loading and parsing logic. The injected Runtime and manifest change with each build. This results in some hashes changing even though the user code has not changed. Through optimization. RuntimeChunk extract the runtime code. Strip the third-party library through optimization.splitChunks. Like react, react-dom.

module.exports = {
  / /...
  optimization: {
  runtimeChunk: 'single'.splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/.name: 'vendor'.chunks: 'all',}}}}};Copy the code

Finally, HashedModuleIdsPlugin is used to eliminate the impact of module ID changes.

loader

Loader is used to convert the module source code. Loader is a Node module exported as a function. This function is called when the Loader transforms the resource. The given function calls the Loader API and is accessed through the This context.

// loader API;
this.callback(
  err: Error | null.content: string | Buffer, sourceMap? : SourceMap, meta? : any );// sync loader
module.exports = function(content, map, meta){
  this.callback(null, syncOperation(content, map, meta));
  return;
}
// async loader
module.exports = function(content, map, meta){
  let callback = this.async();
  asyncOperation(content, (error, result) => {
    if(error) callback(error);
    callback(null, result, map, meta);
    return; })}Copy the code

When multiple Loaders are serialized, the pitch method on the Loader is called from left to right before executing the Loader from right to left. If the result is returned in pitch, subsequent loaders will be skipped.

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- requested module is picked up as a dependency
    |- c-loader normal execution
  |- b-loader normal execution
|- a-loader normal execution

<! Return result in pitch -->

|- a-loader `pitch`
  |- b-loader `pitch` returns a module
|- a-loader normal execution
Copy the code

plugins

The custom plug-in for WebPack is similar to the one in Tapable at the beginning of this article. The Webpack plug-in is a JavaScript object with the Apply method. The Apply method is invoked by the Webpack Compiler, and the Compiler object is accessible throughout the compile life cycle. Hooks can be synchronous or asynchronous, depending on the API documentation provided with WebPack.

// Official example
class FileListPlugin {
  apply(compiler) {
    // Emit is an asynchronous hook, you can touch it using tapAsync, and you can also use tapPromise/tap(synchronous)
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
      // In the makefile, create a header string:
      var filelist = 'In this build:\n\n';
      // Go through all compiled resource files,
      // For each file name, add a line of content.
      for (var filename in compilation.assets) {
        filelist += The '-' + filename + '\n';
      }
      // Insert this list as a new file resource into the WebPack build:
      compilation.assets['filelist.md'] = {
        source: function() {
          return filelist;
        },
        size: function() {
          returnfilelist.length; }}; callback(); }); }}module.exports = FileListPlugin;
Copy the code
  • ProvidePlugin loads modules automatically without having to reference them everywhere. Somewhat similar to expose-loader.

    // webpack.config.js
    new webpack.ProvidePlugin({
      $: 'jquery',})// some.js
    $('#item');
    Copy the code
  • DllPlugin packages the base module into a dynamically linked library, and when the dependent module exists in the dynamically linked library, it does not need to be packaged again, but is directly fetched from the dynamically linked library. DLLPlugin is responsible for packaging the dynamic link library, and DllReferencePlugin is responsible for importing the DLLPlugin plug-in packaged dynamic link library file from the main configuration file.

    // webpack-dll-config.js
    // Execute the configuration file first
    output: {
      path: path.join(__dirname, "dist"),
      filename: "MyDll.[name].js".library: "[name]_[hash]"
    },
    plugins: [
      new webpack.DllPlugin({
        path: path.join(__dirname, "dist"."[name]-manifest.json"),
        name: "[name]_[hash]"})]// webpack-config.js
    // Execute the configuration file
    plugins: [
      new webpack.DllReferencePlugin({
        manifest: require(".. /dll/dist/alpha-manifest.json")}),]Copy the code
  • HappyPack starts the child process to process tasks and make full use of resources. However, inter-process communication is resource-intensive and should be handled as appropriate.

    const HappyPack = require('happypack');
    // loader
    {
      test: /\.js$/.use: ['happypack/loader? id=babel'].exclude: path.resolve(__dirname, 'node_modules'),},// plugins
    new HappyPack({
      id: 'babel'.loaders: ['babel-loader? cacheDirectory'],}).Copy the code
  • Webpack-bundle-analyzer An analysis tool packaged with webpack.

Webpack comes to an end.