webpack Foundation: Code Splitting

Code separation is one of the most compelling features of web packs. This feature separates the code into bundles, which can then be loaded on demand or in parallel. Code separation can be used to obtain smaller bundles and control resource load priority, which can greatly reduce load time if used properly.

There are three common code separation methods:

  • Entry Start: Use entry configuration to manually separate code.

  • Prevent duplication: Use Entry dependencies or SplitChunks Plugin to remove and separate chunk s.

  • Dynamic import: Separate code by calling the module's inline function import.


1. Entry Start Point

Entry points

This is by far the simplest and most intuitive way to separate code. However, there are a lot of manual configurations and some potential pitfalls that we will address. Let's first see how to separate another module from the main bundle:

Create another-module.js file in the src directory:

src/another-module.js

import _ from 'lodash'

console.log(_.join(['another', 'module', 'chunk'], ' '));

This module relies on lodash and needs to be installed:

npm install lodash

webpack.config.js

module.exports = {
  entry: { // Configure multiple entry files
    index: './src/index.js',
    another: './src/another_module.js'
  },
}

Executing the webpack command, you can see the error

This error indicates a conflict and that the same filename appears after multiple entry files have been packaged, so we need to set up multiple exit different file name files for multiple entry files

webpack.config.js

module.exports = {
  entry: {
    index: './src/index.js',
    another: './src/another_module.js'
  },
  output: {
    filename: '[name].bundle.js', // Corresponds to multiple export file names
    path: path.resolve(__dirname, './dist'),
  },
}

Executing the webpack command, you can see that no error has been reported, and dist output two js files


The file another.bundle.js comes from entry.another, src/another.js, and is 1.37M in size because it was packaged by lodash

The file index.bundle.js comes from entry.index, which is src/index.js, and is 96kb in size

Looking at dist/app.html, you can see that two js files have been written to script

Executing npx webpack-dev-server shows that js file loading is normal, and the console can print another module chunk

But what if we also need lodash for other entries?

src/index.js

import _ from 'lodash'

console.log(_.join(['index', 'module', 'chunk'], ' '));

Execute the webpack command to see that the package was successful

But index.bundle.js is significantly larger because it also packages lodash in

Executing npx webpack-dev-server, you can see that the console prints out another module chunk index module chunk

problem

We found that lodash was packaged in both reference files and we expected that lodash should be public, but using this method caused duplicate packaging problems


2. Prevent duplication

2.1 Configure entry to extract common dependencies

webpack.config.js

module.exports = {
  entry: {
    index: {
      import: './src/index.js', // Modules to load at startup
      dependOn: 'shared', // The entry on which the current entry depends
    },
    another: {
      import: './src/another_module.js',
      dependOn: 'shared',
    },

    shared: 'lodash' // When the above two modules have the lodash module, they are extracted and named shared chunk
  },
  output: {
    filename: '[name].bundle.js', // Corresponds to multiple export file names
    path: path.resolve(__dirname, './dist'),
  },
}

Execute the webpack command to see the results of the package

shared.bundle.js has been extracted, which packages the lodash public module for extraction

Index.bundle.js other.bundle.js volume also decreases

Looking at dist/index.html, you can see that all three files are loaded

Execute npx webpack-dev-server to see that the page is loading properly, open the console to see that the printed results are also output properly


2.2 SplitChunksPlugin

SplitChunksPlugin Automatically helps us to pull out public modules

webpack.config.js

module.exports = {
  entry: { // Multi-entrance
    index: './src/index.js',
    another: './src/another_module.js',
  },
  output: {
    filename: '[name].bundle.js', // Corresponds to multiple export file names
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    splitChunks: { // Code Split
      // include all types of chunks
      chunks: 'all' 
    }
  },
}

Executing the webpack will see the result of the package, and the file has been split into chunks

Executing npx webpack-dev-server shows that the code is also loaded properly


3. Dynamic Import

When it comes to dynamic code splitting, webpack provides two similar techniques. The first, and recommended, option is to use import() syntax that complies with the ECMAScript proposal for dynamic import. The second is the legacy functionality of webpack, using webpack-specific require.ensure.

Let's try the first one here

First, let's start with a section of the previous code comments

webpack.config.js

module.exports = {
  entry: { // Multi-entrance
    index: './src/index.js',
    // another: './src/another_module.js',
  },
  output: {
    filename: '[name].bundle.js', // Corresponds to multiple export file names
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    // splitChunks: {
    //   // include all types of chunks
    //   chunks: 'all'
    // }
  },
}

src.index.js

// import _ from 'lodash'
//
// console.log(_.join(['index', 'module', 'chunk'], ' '));

Create an async-module.js file under src:

function getComponent() {
  // import returns Promise
  // Load a module
  return import('lodash').then(({ default: _ }) => {
    const element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    return element
  }).
  catch((error) =>'An error occurred while loading the component')
}
getComponent().then(component => {
  document.body.appendChild(component)
})

src/index.js

import './async-module';

Executing the webpack, you can see that the public module has also been pulled out

Executing npx webpack-dev-server, you can see that a Hello webpack is loaded on the page

This indicates that dynamic import enables pull-out modules

So what happens if dynamic import is used with static import?

src/index.js Unscrambles the previous comment

import _ from 'lodash'

console.log(_.join(['index', 'module', 'chunk'], ' '));

Execution of webpack command succeeded, but we found that code separation was not implemented

This means that once we join a static resource, we need to turn on optimization.splitChunks.chunks

module.exports = {
  // ...
  optimization: {
    splitChunks: {
      // include all types of chunks
      chunks: 'all'
    }
  },
}

Execute the webpack command and you can see that the package was successful and the result was packaged

You can see that the public module has been pulled out

Executing npx webpack-dev-server will see the resource loaded successfully

Open multiple entry again

webpack.config.js

module.exports = {
  entry: { // Multi-entrance
    index: './src/index.js',
    another: './src/another_module.js',
  },
  output: {
    filename: '[name].bundle.js', // Corresponds to multiple export file names
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    splitChunks: { // Code Split
      // include all types of chunks
      chunks: 'all' 
    }
  },
}

Executing the webpack will see the result of the package, and the file has been split into chunks

Execute npx webpack-dev-server to see that the resource is still loaded successfully


4. Lazy Loading

Loading lazily or on demand is a good way to optimize your web pages or applications. This actually separates your code at some logical breakpoints, and then references or is about to reference other new blocks of code as soon as certain actions are completed in some blocks of code. This speeds up the initial loading of the application and reduces its overall size, as some code blocks may never be loaded.

We've created src/math.js before

export function add (x, y) {
  return x + y
}

export function reduce (x, y) {
  return x - y
}

src/index.js

const button = document.createElement('button')
button.textContent = 'Click to perform addition'
button.addEventListener('click', () => {
 // Magic Note webpackChunkName Modifies the lazy loading package file name
 // Even without using webpackChunkName, webpack 5 automatically assigns meaningful file names in development mode.
 import(/* webpackChunkName: 'math' */ './math.js').then(({ add }) => {
  console.log(add(4, 5))
 })
}) 
document.body.appendChild(button)

Executing the webpack will reveal a new js file. Opening it will reveal that the file contains the add and reduce functions that we have written. You can see that this module has been isolated

Execute npx webpack-dev-server and you can see that there is already a button on the page

As you can see in the figure above, math.bundle.js is loaded after clicking the button and the function is executed to print the output


5. Prefetch, Preload

Webpack v4.6.0+ adds support for pre-fetching and pre-loading.

When declaring import, use these built-in instructions below to let the webpack output a "resource hint" to inform the browser:

  • Prefetch: Resources that may be needed for some future navigation (when all the content on the page is loaded and the network is idle, load resources)

  • Preload: Resources may be required under current navigation

5.1 prefetch

src/index.js

const button = document.createElement('button')
button.textContent = 'Click to perform addition'
button.addEventListener('click', () => {
 // webpackPrefetch: true begins prefetching when dynamically introduced
 import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({ add }) => {
  console.log(add(4, 5))
 })
})
document.body.appendChild(button)

Executing npx webpack-dev-server, you can see that math.bundle.js is pre-fetched

5.2 preload

The preload directive differs from the prefetch directive in many ways:

  • preload chunk starts loading in parallel when the parent chunk loads. The prefetch chunk starts loading after the parent chunk has finished loading.
  • preload chunk has a medium priority and downloads immediately. prefetch chunk downloads when the browser is idle.
  • preload chunk is immediately requested in the parent chunk for the current moment. prefetch chunk will be used at some point in the future.
  • Browser support varies.


Source address: https://gitee.com/yanhuakang/webpack-test

If it's useful, give it a compliment (\*^^^\*)

Tags: Front-end Webpack

Posted on Tue, 30 Nov 2021 20:26:08 -0500 by aeroswat