Leak fill a vacancy

By learning how to use Webpack to improve front-end development efficiency (I), we have been able to process various file resources through the Loader and Piugin mechanism of Webpack. For those of you who have noticed the lack of resource handling for font files and HTML tags, let’s address this issue first.

Following the previous article, our directory structure is shown as follows:

webpack.config.js

// webpack.config.js
// Add a loader for fonts
      {
        test: /\.(eot|woff|woff2|ttf)$/.use: [{
          loader: 'url-loader'.options: {
            name: '[name].[hash:7].[ext]'.limit: 8192.outputPath: 'font'.// Package to the dist/font directory}}},Copy the code

How about we randomly download a font from the Internet, place it in a SRC folder, and modify SRC /index.html

<! -- src/index.html -->

      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>my-webpack</title>
</head>

<body>
  <h1>Webpack method good!! Front end method good!!</h1>
</body>

</html>
Copy the code

Introduce fonts in index. SCSS

/* src/index.scss */
/* Add the following styles */
@font-face {
  font-family: 'myFont';
  src: url('./font/ZaoZiGongFangQiaoPinTi-2.ttf');
}

h1 {
  font-family: 'myFont';
}
Copy the code

Previously, we had to delete the dist folder every time we repacked. Now we can use the clean-webpack-plugin, which deletes the specified folder every time we pack. We can run NPM I clean-webpack-plugin -d from the command line

Modify the webpack. Config. Js

// webpack.config.js
// Add the following imports
const CleanWebpackPlugin = require('clean-webpack-plugin');

// Add the following plug-ins
plugins: [
    new CleanWebpackPlugin(['dist']) In the latest V2 release, if the dist folder is deleted by default, just new CleanWebpackPlugin() is required.].Copy the code

After executing NPM run build on the command line, our dist folder will be deleted automatically and the following result will be printed. You can see that although we successfully packaged the font file, the font file is too big, and even Webpack issued a warning [big].

  • Open font fileCDNTo speed up
  • Present the already made style diagram through the design
  • usingfont-spiderCompress the font

Let’s try the third option, which I recommend, at the command line

npm i font-spider -D
font-spider ./dist/index.html
Copy the code

You can see that the font file size of nearly 4MB is suddenly reduced to less than 6KB!! The page looks exactly the same as before.

HTML
The <img> tag was introduced
html-loader
HTML
img.src
require
npm i html-loader -D

Modify the following files

// webpack.config.js
// Add loader to HTML
      {
        test: /\.html$/.use: {
          loader: 'html-loader'.options: {
            attrs: ['img:src'] // img represents the parse tag and SRC represents the value to be parsed in the attrs array in the form of key:value}}}Copy the code
<! -- src/index.html -->
<body>
    +  <img src="./leaf.png" alt="">
</body>
Copy the code

From the command line, execute NPM run build and check dist/index.html

Dynamic loading

Imagine if our entry file was large (containing all the business logic code), the first screen would load slowly and the user experience would be poor. Here we solve the problem from two aspects:

  • Module decoupling decouples the entry file and separates the basic module (UI, tool class) from the business module, which not only facilitates code maintenance and expansion, but also reduces the volume of the entry file.
  • Dynamic loading. It is impossible for users to use all functions at the beginning. In this case, we can introduce the secondary modules that need to be triggered by events dynamically in the subsequent interaction process. insrcAdding a file to a directorydynamic.js
// dynamic.js
export default() = > {console.log('Im dynamically loaded.');
}
Copy the code

Modify the following files

<! -- src/index.html -->
<body>
  + <button id="btn">Click on me to load dynamic.js dynamically</button>
</body>
Copy the code
// src/index.js
// Add the following content
const btn = document.getElementById('btn');
// Click the button to load dynamic.js dynamically
btn.onclick = (a)= > {
  import(/* webpackChunkName: "dynamic" */ './dynamic.js').then(function (module) {
    const fn = module.default; fn(); })}Copy the code

Run NPM run build and you can see

/* webpackChunkName: "dynamic" */

ChunkName
"dynamic"
id
JS
Chunk Names

Now let’s go to dist/index.html, at this point

dynamic.js

Separate development and production environments

Looking back at our webpack.config.js, we were writing a lot of code, and since we were working on a real project with two modes of development and production, each of which was doing its job, we might as well split the configuration.

Run NPM I webpack-merge cross-env -d

Webpack-merge merges webpack configuration items, and cross-env sets and uses environment variables.

Added webpack.base.js to provide basic WebPack Loader plugin configuration

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) = > path.resolve(__dirname, targetPath);
constdevMode = process.env.NODE_ENV ! = ='production';
// In Node, the global variable process represents the current Node process.
// process.env contains information about the system environment.
// NODE_ENV is a user-defined variable used in Webpack to determine whether the current production or development environment is in place.
// We can inject the NODE_ENV variable by writing NODE_ENV=development to NPM run dev via cross-env.

module.exports = {
  entry: {
    index: pathResolve('js/index.js')},output: {
    path: pathResolve('dist'),},module: {
    rules: [{test: /\.html$/.use: {
          loader: 'html-loader'.options: {
            attrs: ['img:src'},},}, {test: /\.(eot|woff|woff2|ttf)$/.use: [{
          loader: 'url-loader'.options: {
            name: '[name].[hash:7].[ext]'.limit: 8192.outputPath: 'font',},}],}, {test: /\.(sa|sc|c)ss$/.use: [
          devMode ? 'style-loader' : {  // If you are in development mode, insert the 
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '.. / '}},'css-loader'.'postcss-loader'.'sass-loader',]}, {test: /\.(png|jpg|jpeg|svg|gif)$/.use: [{
          loader: 'url-loader'.options: {
            limit: 8192.name: '[name].[hash:7].[ext]'.outputPath: 'img',},}],},],},plugins: [
    new htmlWebpackPlugin({
      minify: {
        collapseWhitespace: true.// Remove whitespace
        removeAttributeQuotes: true.// Remove quotes
        removeComments: true // Remove comments
      },
      filename: pathResolve('dist/index.html'),
      template: pathResolve('src/index.html'),}})];Copy the code

New webpack.dev.js for development mode

const path = require('path');
const webpack = require('webpack');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');

const pathResolve = (targetPath) = > path.resolve(__dirname, targetPath);

module.exports = smart(base, {
  mode: 'development'.output: {
    filename: 'js/[name].[hash:7].js'
  },
  devServer: {
    contentBase: pathResolve('dist'),
    port: '8080'.inline: true.historyApiFallback: true.hot: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
})
Copy the code

New webpack.prod.js for production mode

const path = require('path');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) = > path.resolve(__dirname, targetPath);

module.exports = smart(base, {
  mode: 'production'.devtool: 'source-map'.// Generates a complete.map file for debugging, but also slows down the packaging, suitable for error checking of packaged code
  output: {
    filename: 'js/[name].[chunkhash:7].js'.chunkFilename: 'js/[name].[chunkhash:7].js',},plugins: [
    new CleanWebpackPlugin(['dist']),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:7].css',})]});Copy the code

Package. json needs to be modified accordingly

// cross-env determines the runtime environment --config determines which configuration file to run"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js"."build": "cross-env NODE_ENV=production webpack --config webpack.prod.js "
Copy the code

The battle for cache

There is no doubt about the position of caching in the front end, the correct use of caching can greatly improve the loading speed and performance of applications. Webpack uses hash values as part of file names to make efficient use of caching. When the file is modified and repackaged, the hash value changes, causing the cache to be invalidated and the HTTP request to pull the resource again. Webpack has three hash strategies, which are:

hash

At the project level, that is, every time you modify any file, the hash value of all file names will change. So if any file is modified, the file cache for the entire project will be invalidated. If we change the naming strategy of filename for the entire project to name.[hash:7](:7 means the first seven bits are taken from the full hash value), we can see that the hash value of the packaged file is the same, so the hash is updated for the module that is not changed, causing the cache to be invalidated.

chunkhash

Chunkhash parses dependent files according to different Entry files, constructs corresponding chunks, and generates corresponding hash values. If the naming policy of filename is changed to name.[chunkhash:7], we can see that the hash values of files whose Chunk Names are “index” are consistent, but the hash values of different chunks are different. This avoids the situation where a file changes and the entire project hash value changes.

contenthash

The problem is that index. SCSS is imported into index.js as a module, and its chunkhash value is consistent. If one of them changes, the associated file’s chunkhash value will also change. The contenthash is calculated based on the contents of the file. The contenthash value changes only when the contents of the file change. We changed the CSS file naming policy to name.[contenthash:7], and changed SRC /index.js, leaving the other files unchanged.

Production environment configuration optimization

tree-shaking

Shake the leaves off a tree so the weight is lightened.It’s like removing useless code from our app to reduce the volume. The introduction of modules borrowed from ES6 is static analysis, so WebPack can determine exactly what code is loaded at compile time, that is, modules that are not referenced are not packaged, reducing our package size, reducing the application load time, and presenting a better user experience. So how do you use it?

New SRC/utils. Js

// src/utils.js
const square = (num) = > num ** 2;
const cube = num= > num * num * num;
// Two methods are exported
export {
  square,
  cube
}
Copy the code

New SRC/shake. Js

// src/shake.js
import { cube } from './utils.js';
// Only the cube method is used
console.log('cube(3) is' + cube(3));
Copy the code

Add shake.js to webpack.base.js

  entry: {
    +  shake: pathResolve('src/shake.js')},Copy the code

If the square method is not packed, you can see that tree-shaking is working. All of this is automatically implemented by Webpack in the production environment.

splitChunks

Literally split code block, by default this will only affect code blocks loaded on demand, because changing the initialized code block will affect the script tags needed to run the project in the HTML. Remember we introduced SRC /dynamic.js dynamically in SRC /index.js, and eventually dynamic.js was packaged separately thanks to splitChunks.

In actual production, we often introduce third-party libraries (JQuery, Lodash), often these third-party libraries volume as high as dozens of KB doping in business code, and not like a business code updated frequently, this time we need to split them, can not only keep the third-party libraries persistent cache, and can reduce the volume of business code.

Modify the webpack. Prod. Js

Module. exports adds the following content
  optimization: {
    runtimeChunk: {
      name: 'manifest'.// The definition of webpackJsonp and the definition of asynchronous loading are injected, and the module information list is packaged separately to facilitate caching
    },
    splitChunks: {
      cacheGroups: { // Cache group. By default, all modules from node_modules are assigned to cache group called 'venders', and all modules referenced more than twice are assigned to cache group 'default'.
        vendor: {
          chunks: "all".// select all, async, and INITIAL
          test: /[\\/]node_modules[\\/]/.// The range of modules selected by the cache group
          name: "vendor".// Chunk Names and packaged file Names
          minChunks: 1.// Number of references >=1
          maxInitialRequests: 5.// The number of requests to load code blocks at page initialization should be <=5
          minSize: 0.// The minimum size of the code block
          priority: 100.// Cache priority weight}}}},Copy the code

NPM I lodash -s

Modify the SRC/index. Js

// Add the following content
import _ from 'lodash';
Copy the code

After executing NPM run build, you can see that loDash is packaged into index.js before optimization and vendor.js after optimization.

Compress code to remove redundancy

Often in THE CSS code, there are many styles that are not used, they are redundant, we need to remove them, and compress the rest of the CSS styles to reduce the SIZE of the CSS file.

NPM I glob optimize -csS-assets -webpack-plugin purifycss-webpack Purify -CSs-d

Modify the webpack. Prod. Js

// Add the following imports
const glob = require('glob'); // Match the required files
const PurifyCssWebpack = require('purifycss-webpack'); // Remove redundant CSS
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); / / compress CSS

// Add the following plug-ins
new PurifyCssWebpack({
    paths: glob.sync(pathResolve('src/*.html')) // Synchronously scan all CSS references in HTML files and remove redundant styles
})

// Add the following optimizations
optimization: {
    minimizer: [
      new OptimizeCSSAssetsPlugin({}) / / compress CSS]}Copy the code

Run NPM run build

CSS
JS
uglifyjs-webpack-plugin

@babel/core provides translation apis for Babel, such as babel.transform, to translate code. Babel-loader like WebPack calls these apis to complete the translation process.

@babel/ Preset -env can automatically convert ES2015+ code to ES5 based on the target browser or runtime environment configured.

Run the NPM i@babel/core@babel /preset-env babel-loader @babel/plugin-syntax-dynamic-import -d command

Create a.babelrc file

{
  "presets": [// configure the default environment ["@babel/preset-env", {
      "modules": false}]],"plugins": [
    "@babel/plugin-syntax-dynamic-import"// handle dynamic loading in SRC /index.js]}Copy the code

Modify the webpack. Base. Js

// Add new js parsing rules
    {
        test: /\.(js|jsx)$/.use: 'babel-loader'.exclude: /node_modules/
    },
Copy the code

Then run NPM I uglifyjs-webpack-plugin -d

Modify the webpack. Prod. Js

// Add the following imports
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");

// Add the following optimizations
optimization: {
    minimizer: [+new UglifyJsPlugin({ JS / / compression
        cache: true.parallel: true.sourceMap: true}})]Copy the code

After executing NPM run build, you can see that the size of the packaged file is greatly reduced, and you are done, JS is compressed.

Take index. HTML as an example, we can open Chrome developer tools, select More Tools, and click the Coverage panel to see the usage of JS, CSS and other files, which can be optimized with our customized Webpack configuration.

Multiple page

Sometimes you need to build multiple pages at the same time. With the htML-webpack-plugin, you simply add the configuration items for the new page to your plugins.

New SRC/main. HTML


      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>main page</title>
</head>

<body>
  <h1>I am Main Page</h1>
</body>

</html>
Copy the code

Modify the webpack. Base. Js

// Modify the following
  plugins: [
    new htmlWebpackPlugin({ / / configuration index. HTML
      minify: {
        collapseWhitespace: true.removeAttributeQuotes: true.removeComments: true
      },
      filename: pathResolve('dist/index.html'),
      template: pathResolve('src/index.html'),
      chunks: ['manifest'.'vendor'.'index',]// The chunk used to configure index.html, i.e. which JS files to load, the core of the manifest module management, must be loaded first, otherwise an error will be reported
    }),
    new htmlWebpackPlugin({ / / configuration. The main HTML
      minify: {
        collapseWhitespace: true.removeAttributeQuotes: true.removeComments: true
      },
      filename: pathResolve('dist/main.html'),
      template: pathResolve('src/main.html'),
      chunks: ['manifest'.'shake'] // Configure chunk for index.html, load manifest.js,shake.js}),].Copy the code

NPM run build builds index.html and main.html successfully.

conclusion

At this point, we got rid of the shackle of the third party scaffolding, and gradually built our own front-end process tools, which are quick to use, functional, fast and convenient, and reusable. Hope friend can do it yourself, don’t talk about webpack paper, to understand its architecture, optimization, with ease into your own project, refused to use before trival, non-standard development process, do not do “CV engineer”, to create their own knowledge system, the working process, improve the development efficiency of front end.

Finally, the source code for this project has been deployed on Github with many additional optimizations (less support, ESLint detection, compression for image formats…). , so that you can directly download and experience, and assist in the development of the project, and will continue to maintain in the future, I hope friends can learn from each other, make suggestions.

Easy-frontend is a fast, simple, easy-to-use front-end development efficiency improvement tool

Star project is your greatest encouragement to me!!

Front-end road, do not forget the original aspiration, I wish you all a speedy fortune!