preface

I recently started a multi-page project, and I didn’t use Webpack4 before, so I was ready to give it a try. This article is mainly some configuration introduction, for preparing to use Webpack4 students, can do some reference.

Webpack4 is a big change from the previous 2 and 3. The main thing is that a lot of configuration is already built in to make WebPack “right out of the box.” Of course, this is not always possible out of the box, but many of the previous configurations can be dispensable. Confused in before, for example, the compression code, need to increase the uglify plug-in, scope ascension (the scope hosting) need to increase the ModuleConcatenationPlugin. In webpack4, you only need to set mode to production. Of course, there will be no errors if these plug-ins are forcibly added.

So I suggest that if you want to migrate to webpack4, you start adding from 0, look at the history, and do a new configuration. Instead of deleting from the historical configuration and upgrading to webpack4. This will make webpack4 configuration more streamlined.

Packaging optimization

Packaging optimization is mainly to package all page loading dependencies properly when multi-page applications are built. There are many practices in the industry, including webpack4, and many articles about it. Let me add a few minor details that aren’t easy to notice. Some points I will not introduce in detail, students who are not familiar with Webpack configuration may not understand, you can search the corresponding keywords, there must be a very detailed article on the Internet.

First, when building a multi-page application, the following chunk packages are often removed:

  1. common: Puts dependency packages referenced by multiple pages into a Common Chunk. Most tutorials on the web are introduced twice and then plugged into Common. I recommend adjusting to the number of pages you have. In my project, I set the number of pages imported to be more than 1/3 before the Common package is entered.
  2. dll: Remove dependency packages, such as react/react-dom, which are referenced by every page and will not change, to prevent changes of other modules from polluting the DLL’s hash cache.
  3. manifest: WebPack runtime code. The runtime code for WebPack changes every time a dependency package changes, and if this part is not removed, it increases the possibility that the hash value of the common package will change.
  4. Page entry file corresponding topage.js

We then inject contentHash into the name of the chunk to achieve maximum caching effect. During the process of chunk splitting, the most critical idea was to release each iteration to minimize the change of chunk hash value. There are many, many practices in the industry, such as this article: github.com/pigcan/blog…

In webpack4, however, we don’t have to add so many plugins, a optimization configuration can do it all.

I’ll post my Optimization configuration for Webpack first, and then I’ll introduce it a little more to impress you

const commonOptions = {
  chunks: 'all'.reuseExistingChunk: true
}

export default {
  namedChunks: true.moduleIds: 'hashed'.runtimeChunk: {
    name: 'manifest'
  },
  splitChunks: {
    maxInitialRequests: 5.cacheGroups: {
      polyfill: {
        test: /[\\/]node_modules[\\/](core-js|raf|@babel|babel)[\\/]/.name: 'polyfill'.priority: 2. commonOptions },dll: {
        test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/.name: 'dll'.priority: 1. commonOptions },commons: {
        name: 'commons'.minChunks: Math.ceil(pages.length / 3), // Enter the common package only after at least 1/3 of the page is imported. commonOptions } } } }Copy the code

runtimeChunk

To extract the manifest before Webpack4, use CommonsChunkPlugin to configure a chunk whose name attribute is’ MANIFEST ‘. In webpack4, there is no need to import the plug-in manually, just configure runtimeChunk.

splitChunks

This configuration allows us to extract packets with certain rules. We may extract several packets, such as Verdor + Common, so splitChunks provide a cacheGroups field. Each key added to cacheGroups is an additional rule for extracting packets.

In many tutorials on the Internet, DLL is often specifically added a Webpack configuration, using DllPlugin to build DLL libraries, and then in their own project project webpack using DllReferencePlugin to map DLL libraries. Although this builds a lot faster, but, alas, is really f * * king annoying…..

I’m a bit of a bug, so I’d rather use splitChunks in webpack4, set up the rules, and pull out the DLLS. Of course, you can choose your own plan according to the actual situation.

In addition to the two chunks of DLL and Common, I also added a polyfill. This is because some of the new libraries we are using or using some ES6+ syntax (such as async/await) require runtime shippers. For example I used react16 project, the need to increase the Map/Set/requestAnimationFrame (reactjs.org/docs/javasc…). . I had to add Polyfill before the DLL was loaded, so I put all the packages core-JS and Babel introduced into Polyfill specifically to make sure that the chunks loaded later would execute. The priority field is used to set the import priority of chunk. The general project should be polyfill > DLL > Common > Page.

The maxInitialRequests configuration item in splitChunks indicates the maximum number of chunks initially requested in an entry (excluding chunks loaded on demand, that is, chunks introduced by script in the DOM). The default value is 3. I now have three in cacheGroups, and since runtimeChunk is configured to render the manifest, there are four chunks in total, up from the default three, so I need to reconfigure the value.

moduleIds

For those of you who know a little about how WebPack works, webPack assigns a moduleId to a module loaded in a project and maps the module. The problem is that if modules are added or deleted or their order changes during the project, the moduleId will change, which may affect the content hash value of all chunks. Invalidation of the cache just because the moduleId changes is definitely not what we want.

Before WebPack4, the HashedModuleIdsPlugin plugin could be used to map the module path to a hash value instead of the moduleId, because the module path is basically unchanged and therefore the hash value is basically unchanged.

In webpack4, however, you just need to set the moduleIds to hashed in the optimization configuration item.

namedChunks

In addition to the moduleId, we know that the separated chunk also has its chunkId. Similarly, chunkId suffers from cache invalidation due to changes in its chunkId. Since the manifest and the outgoing chunk packets contain chunkid-related data, if chunkId changes due to operations such as “adding and deleting pages”, it may affect many chunk cache failures.

Before webpack4, the ability to solidify chunkId and maintain cache was implemented by adding NamedChunksPlugin and replacing chunkId with chunkName. In webpack4, you simply set namedChunks to true in the optimization configuration item.

CSS related

Before Webpack 4, the extract-text-webpack-plugin was used to separate CSS from JS packages and package them separately. In Webpack, the MiniCssExtractPlugin needs to be replaced. And in a production environment or need HMR (module hot replacement), want to use MiniCssExtractPlugin. Replace style – loader loader.

Notice, there’s a pit here. Due to the development environment we will update configuration of the heat, CSS hot update current MiniCssExtractPlugin. Loader also to support itself, so you also need to increase the CSS – hot – loader. Remember that CSS-Hot-loader must not be used in a production environment. Otherwise, the contentHash values of all JS chunks will be inconsistent during each build, which will invalidate all JS caches. Because this configuration is added to a production environment without any errors and the page builds normally, it is easy to overlook.

Simplify entry files for multi-page applications

Those of you who use frameworks like react/ Vue know that we typically need an entry to index.js, like this:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

ReactDOM.render(<App />, document.getElementById('root'))
Copy the code

If you also need to use DVA, or add a Layout function to all react pages, it might look like this:

import React from 'react'
import dva from 'dva'
import Model from './model'
import Layout from '~@/layout'
import App from './app'

const app = dva()
app.router((a)= > (
  <Layout>
    <App />
  </Layout>
))
app.model(Model)
app.start(document.getElementById('root'))
Copy the code

If every page does this, it’s a little uncomfortable, because programmers are afraid of writing things that are repetitive. But it has to be there. You can’t separate it into a single file. Because this is an entry file, and multi-page projects, each page must have its own entry file, even if they look exactly the same. So our resource list would look something like this:

- src
  - layout.js
  - pages
    - pageA
      - index.js
      - app.js
      - model.js
    - pageB
      - index.js
      - app.js
      - model.js
Copy the code

Since all indexes are the same, my ideal entry file for a page would just need app.js, like this:

- src
  - layout.js
  - pages
    - pageA
      - app.js
      - model.js
    - pageB
      - app.js
      - model.js
Copy the code

As a front-end development engineer,NodeFor us, it should be tools that we are skilled with, not just tools that others have already packaged.

In this case, we could have created some temporary entry files from Node’s File System for each of our pages before webPack was built, using the same entry File template:

- src
  - .entires
    - pageA.js
    - pageB.js
  - layout.js
  - pages
Copy the code

These temporary files are then configured as entry to the Webpack. The code is as follows:

const path = require('path')
const fs = require('fs')
const glob = require('glob')
const rimraf = require('rimraf')
const entriesDir = path.resolve(process.cwd(), './src/.entries')
const srcDir = path.resolve(process.cwd(), './src')

// Return to webPack Entry configuration
module.exports = function() {
  if (fs.existsSync(entriesDir)) {
    rimraf.sync(entriesDir)
  }
  fs.mkdirSync(entriesDir)
  return buildEntries(srcDir)
}

function buildEntries(srcDir) {
  return getPages(srcDir).reduce((acc, current) = > {
    acc[current.pageName] = buildEntry(current)
    return acc
  }, {})
}
// Get page data, considering only level 1 directories
function getPages(srcDir) {
  const pagesDir = `${srcDir}/pages`
  const pages = glob.sync(`${pagesDir}/**/app.js`)
  return pages.map(pagePath= > {
    return {
      pageName: path.relative(pagesDir, p).replace('/app.js'.' '), // Retrieve the page folder name
      pagePath: pagePath
    }
  })
}
// Build a temporary entry file
function buildEntry({ pageName, pagePath }) {
  const fileContent = buildFileContent(pagePath)
  const entryPath = `${entriesDir}/${pageName}.js`
  fs.writeFileSync(entryPath, fileContent)
  return entryPath
}
// Replace the App module address in the template to return the contents of the temporary entry file
function buildFileContent(pagePath) {
  return ` import React from 'react' import dva from 'dva' import Model from './model' import Layout from '~@/layout' import App from 'PAGE_APP_PATH' const app = dva() app.router(() => ( 
       
        
       )) app.model(Model) app.start(document.getElementById('root')) `.replace(PAGE_APP_PATH, pagePath)
}
Copy the code

This way, we can simply remove duplicate entry files and add a Layout function. This is simple code, but the actual project may have multiple levels of directories, multiple models, etc., and you will need to customize it yourself.

Webpack4 has been out for a long time, the article is a little bit behind, so many I think we should understand the place did not write in detail. If you have any questions, please comment ~~