wepack4配置入门

一、 webpack 简介

1.1 webpack 是什么

webpack是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。
它将根据模块依赖关系进行静态分析,打包生成对应的静态资源(bundle)

在这里插入图片描述

webpack会从打包入口开始,找到所有的依赖(js/json/css/img/less/…),webpack会将这些依赖作为 chunk 块(代码块)。然后将 chunk 块中的各种文件内容转变为浏览器可以理解的内容:less -> css、es6语法 -> 浏览器可以理解的语法 … 这些转变后的打包产物称为bundle

Chunk是Webpack打包过程中,一堆module的集合。我们知道Webpack的打包是从一个入口文件开始,也可以说是入口模块,入口模块引用这其他模块,模块再引用模块。Webpack通过引用关系逐个打包模块,这些module就形成了一个Chunk。

如果我们有多个入口文件,可能会产出多条打包路径,一条路径就会形成一个Chunk。

在这里插入图片描述

1.2 webpack 五个核心概念

1.2.1 Entry

入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图

1.2.2 Output

输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名

1.2.3 Loader

Loader 让 webpack 能够去处理那些非 JavaScript 文件 (webpack 自身只理解 JavaScript)。
loader 将 webpack 不识别的资源翻译为 webpck 识别的资源。

1.2.4 Plugins

插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化压缩,一直到重新定义环境中的变量等。

1.2.5 Mode

模式(Mode)指示 webpack 使用相应模式的配置

选项 描述 特点
development 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。 能让代码本地调试运行的环境
production 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin, UglifyJsPlugin 和 TerserPlugin。 能让代码优化上线运行环境。

二、 webpack 的初体验

2.1 初始化配置

  1. 初始化 package.json

输入指令:

npm init
  1. 下载并安装 webpack

输入指令:

npm install webpack webpack-cli -g
npm install webpack webpack-cli -D
  • webpack-cli: webpack命令行,支持使用指令的方式使用webpack的功能。
  • -g: 全局安装
  • -D: 本地安装

2.2 编译打包应用

  1. 创建文件
// index.js
import data from './data.json';
console.log(data);
function add(x, y) {
    
    
  return x + y;
}
console.log(add(1, 2));
// package.json
{
    
    
  "name": "webpack_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    
    
    "dev": "webpack src/index.js -o build/built.js --mode=development",
    "pro": "webpack src/index.js -o build/built.js --mode=production"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    
    
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11"
  }
}
  1. 运行指令
  • 开发环境指令:
webpack src/index.js -o build/built.js --mode=development

webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js。整体打包环境,是开发环境。

  • 生产环境指令:
webpack src/index.js -o build/built.js --mode=production

webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js。 整体打包环境,是生产环境。

  1. 结论
  • webpack 能够编译打包 js 和 json 文件。
  • 能将 es6 的模块化语法转换成浏览器能识别的语法。
  • 生产环境比开发环境多一个压缩js代码。
  1. 问题
  • 不能编译打包 css、img 等文件。
  • 不能将 js 的 es6 基本语法转化为 es5 以下语法

三、 webpack 开发环境的基本配

3.1 创建配置文件

  1. 创建文件
  • webpack.config.js: webpack的配置文件
  • 作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
  • 所有构建工具都是基于nodejs平台运行的,模块化默认采用commonjs
  1. 配置内容如下
const {
    
     resolve } = require("path"); // node 内置核心模块,用来处理路径问题。
module.exports = {
    
    
  entry: "./src/index.js", // 入口文件
  output: {
    
    
    // 输出配置
    filename: "built.js", // 输出文件名
    path: resolve(__dirname, "build"), // 输出文件路径配置
  },
  mode: "development", //开发环境
};
  1. 运行指令: webpack

3.2 打包样式资源

  1. 创建文件
/* index.css */
html, body{
    
    
  margin: 0;
  padding: 0;
  height: 100%;
  background-color: pink;
}
// index.js
// 引入样式资源
import './index.css';
import './index.less';
// index.less
#title {
  color: #fff;
}
  1. 下载安装 loader 包
npm i css-loader style-loader less-loader less -D
  1. 修改配置文件
// resolve用来拼接绝对路径的方法
const {
    
     resolve } = require("path");

module.exports = {
    
    
  // webpack配置
  // 入口起点
  entry: "./src/index.js",
  // 输出
  output: {
    
    
    // 输出文件名
    filename: "built.js",
    // 输出路径
    // __dirname nodejs的变量,代表当前文件的目录绝对路径
    path: resolve(__dirname, "build"),
  },
  // loader的配置
  module: {
    
    
    rules: [
      // 详细loader配置
      // 不同文件必须配置不同loader处理
      {
    
    
        // 匹配哪些文件
        test: /\.css$/,
        // 使用哪些loader进行处理
        use: [
          // use数组中loader执行顺序:从右到左,从下到上 依次执行
          // 创建style标签,将js中的样式资源插入进行,添加到head中生效
          "style-loader",
          // 将css文件变成commonjs模块加载js中,里面内容是样式字符串
          "css-loader",
        ],
      },
      {
    
    
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          // 将less文件编译成css文件
          // 需要下载 less-loader和less
          "less-loader",
        ],
      },
    ],
  },
  // plugins的配置
  plugins: [
    // 详细plugins的配置
  ],
  // 模式
  mode: "development", // 开发模式
  // mode: 'production'
};

  1. 运行指令: webpack

3.3 打包 HTML 资源

  1. 创建文件
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
</head>

<body>
  <h1 id="title">hello html</h1>
</body>

</html>
function add(x, y) {
    
    
  return x + y;
}

console.log(add(2, 3));
  1. 下载安装 plugin 包
npm install --save-dev html-webpack-plugin
  1. 修改配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/index.js",
  output: {
    
    
    filename: "built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      // loader的配置
    ],
  },
  plugins: [
    // plugins的配置
    // html-webpack-plugin
    // 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
    // 需求:需要有结构的HTML文件
    new HtmlWebpackPlugin({
    
    
      // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
      template: "./src/index.html",
    }),
  ],
  mode: "development",
};

3.4 打包图片资源

  1. 创建文件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
</head>
<body>
  <div id="box1"></div>
  <div id="box2"></div>
  <div id="box3"></div>
  <img src="./angular.jpg" alt="angular">
</body>
</html>
import './index.less';
#box1{
  width: 100px;
  height: 100px;
  background-image: url('./vue.jpg');
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

#box2{
  width: 200px;
  height: 200px;
  background-image: url('./react.png');
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

#box3{
  width: 300px;
  height: 300px;
  background-image: url('./angular.jpg');
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
  1. 下载安装 loader 包
npm install --save-dev html-loader url-loader file-loader
  1. 修改配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/index.js",
  output: {
    
    
    filename: "built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.less$/,
        // 要使用多个loader处理用use
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
    
    
        // 问题:默认处理不了html中img图片
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        // 使用一个loader
        // 下载 url-loader file-loader,url-loader 依赖 file-loader
        loader: "url-loader",
        options: {
    
    
          // 图片大小小于8kb,就会被base64处理
          // 优点: 减少请求数量(减轻服务器压力)
          // 缺点:图片体积会更大(文件请求速度更慢)
          limit: 8 * 1024,
          // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
          // 解析时会出问题:[object Module]
          // 解决:关闭url-loader的es6模块化,使用commonjs解析
          esModule: false,
          // 给图片进行重命名
          // [hash:10]取图片的hash的前10位
          // [ext]取文件原来扩展名
          name: "[hash:10].[ext]",
        },
      },
      {
    
    
        test: /\.html$/,
        // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
        loader: "html-loader",
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
    }),
  ],
  mode: "development",
};

3.5 打包其他资源

  1. 使用范围

不需要做任何处理,只需要原封不动输出即可,比如字体图标。

  1. 配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/index.js",
  output: {
    
    
    filename: "built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      // 打包其他资源(除了html/js/css资源以外的资源)
      {
    
    
        // 排除css/js/html资源
        exclude: /\.(css|js|html|less)$/,
        loader: "file-loader",
        options: {
    
    
          name: "[hash:10].[ext]",
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
    }),
  ],
  mode: "development",
};

3.6 devServer

  1. 作用

开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器),开发者只需要关注编码

  1. 配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/index.js",
  output: {
    
    
    filename: "built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      // 打包其他资源(除了html/js/css资源以外的资源)
      {
    
    
        // 排除css/js/html资源
        exclude: /\.(css|js|html|less)$/,
        loader: "file-loader",
        options: {
    
    
          name: "[hash:10].[ext]",
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
    }),
  ],
  mode: "development",

  // 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
  // 特点:只会在内存中编译打包,不会有任何输出
  // 启动devServer指令为:npx webpack-dev-server
  devServer: {
    
    
    // 项目构建后路径
    contentBase: resolve(__dirname, "build"),
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 3000,
    // 自动打开浏览器(默认浏览器)
    open: true,
  },
};

  • npx是执行Node软件包的工具,它从 npm5.2版本开始,就与npm捆绑在一起。
  • 默认情况下,首先检查路径中是否存在要执行的包(即在项目中);如果存在,它将执行;若不存在,意味着尚未安装该软件包,npx将安装其最新版本,然后执行它;
  1. 运行指令
npx webpack-dev-server

3.7 开发环境配置

  1. 目的

开发环境配置:能让代码运行(代码运行即可)

运行项目指令:

  • webpack: 会将打包结果输出出去
  • npx webpack-dev-server: 只会在内存中编译打包没有输出
  1. 配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      // loader的配置
      {
    
    
        // 处理less资源
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
    
    
        // 处理css资源
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
    
    
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: "url-loader",
        options: {
    
    
          limit: 8 * 1024,
          name: "[hash:10].[ext]",
          // 关闭es6模块化
          esModule: false,
          outputPath: "imgs",
        },
      },
      {
    
    
        // 处理html中img资源
        test: /\.html$/,
        loader: "html-loader",
      },
      {
    
    
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: "file-loader",
        options: {
    
    
          name: "[hash:10].[ext]",
          outputPath: "media",
        },
      },
    ],
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
    }),
  ],
  mode: "development",
  devServer: {
    
    
    contentBase: resolve(__dirname, "build"),
    compress: true,
    port: 3000,
    open: true,
  },
};

  1. 运行指令
npx webpack-dev-server

四、 webpack 生产环境的基本配置

为什么要单独配置生产环境

  • 开发环境中将css放在了js中,导致js文件的体积变大
  • 因为将css放在了js中,所以只有加载完js,css才会生效,会导致出现闪现情况
  • 开发环境的目的是使得代码跑起来,能够调测;生产环境的目的是让代码跑的更快,更平稳。生产环境会使用代码压缩,css兼容性保障等手段
  • 压缩,兼容性等耗时较长,放在开发环境会使得构建较慢,不利于开发调测

4.1 提取 css 成单独文

  1. 下载插件
npm install --save-dev mini-css-extract-plugin
  1. 修改配置文件
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: [
          // 创建style标签,将样式放入
          // 'style-loader', 

          // 这个loader取代style-loader。作用:提取js中的css成单独文件
          MiniCssExtractPlugin.loader,
          // 将css文件整合到js文件中
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
    
    
      // 对输出的css文件进行重命名,默认名称是main.css
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};
  • style-loader: 创建style标签,将样式放入
  • MiniCssExtractPlugin.loader: 提取js中的css成单独文件

4.2 css 兼容性处理

  1. 下载 loader
npm install --save-dev postcss-loader postcss-preset-env
  1. 修改配置文件
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          /*
            css兼容性处理:postcss --> postcss-loader postcss-preset-env

            帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式

            "browserslist": {
              // 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
              "development": [
                "last 1 chrome version",
                "last 1 firefox version",
                "last 1 safari version"
              ],
              // 生产环境:默认是看生产环境
              "production": [
                ">0.2%",
                "not dead",
                "not op_mini all"
              ]
            }
          */
          // 使用loader的默认配置
          // 'postcss-loader',
          // 修改loader的配置
          {
    
    
            loader: 'postcss-loader',
            options: {
    
    
              ident: 'postcss',
              plugins: () => [
                // postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
    
    
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};

  1. 修改 package.json
{
    
    
  "browserslist": {
    
    
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  }
}

4.3 压缩 css

  1. 下载安装包
npm install --save-dev optimize-css-assets-webpack-plugin
  1. 修改配置文件
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          {
    
    
            loader: 'postcss-loader',
            options: {
    
    
              ident: 'postcss',
              plugins: () => [
                // postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
    
    
      filename: 'css/built.css'
    }),
    // 压缩css
    new OptimizeCssAssetsWebpackPlugin()
  ],
  // optimization: {
    
    
  //   minimize: true,
  //   minimizer: [
  //     // 压缩 js
  //     new TerserWebpackPlugin(),
  //     // 压缩css 
  //     new OptimizeCssAssetsWebpackPlugin(),
  //   ],
  // },
  mode: 'development'
};
  • webpack官方文档介绍时并不是将 OptimizeCssAssetsWebpackPlugin 插件配置在plugins数组中。而是配置在 optimization.minimizer 数组中。
  • 原因是: 配置在plugins中,webpack就会在启动时使用这个插件。而配置在 optimization.minimizer中,就只会在optimization.minimize这个特性开启时使用。所以webpack推荐,像压缩类的插件,应该配置在optimization.minimizer数组中。以便于通过optimization.minimize统一控制。(生产环境会默认开启minimize)
  • 需要注意的是:如果配置了minimizer,就表示开发者在自定义压缩插件。内部的JS压缩器就会被覆盖掉。因此需要手动配置js压缩。

4.4 js 语法检查

  1. 下载安装包
npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
  1. 修改配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      /*
        语法检查: eslint-loader 依赖 eslint 库
          注意:只检查自己写的源代码,第三方的库是不用检查的
          设置检查规则:
            package.json中eslintConfig中设置~
              "eslintConfig": {
                // 继承 airbnb-base 的规则检查
                "extends": "airbnb-base"
              }
            airbnb(检查规则) --> eslint-config-airbnb-base  eslint-plugin-import eslint
      */
      {
    
    
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "eslint-loader",
        options: {
    
    
          // 自动修复eslint的错误
          fix: true,
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
    }),
  ],
  mode: "development",
};

  1. 配置 package.json

配置检查规则

{
    
    
  "eslintConfig": {
    
    
    "extends": "airbnb-base",
    "env": {
    
    
      // eslint不认识 window、navigator全局变量,支持浏览器端全局变量
      "browser": true
    }
  }
}

4.5 js 兼容性处理&es6语法转化

  1. 作用

将 es6 的语法转化为 es5 的语法

  1. 下载安装包
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
  1. 修改配置文件
const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      /*
        js兼容性处理:babel-loader @babel/core (babel的核心库)
          1. 基本js兼容性处理 --> @babel/preset-env
            问题:只能转换基本语法,如promise高级语法不能转换
          2. 全部js兼容性处理 --> @babel/polyfill  
            问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
            使用方法:
              引入即可,在index.js中 import '@babel/polyfill ';
          3. 需要做兼容性处理的就做:按需加载  --> core-js
      */
      {
    
    
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
    
    
          // 预设:指示babel做怎么样的兼容性处理
          presets: [
            [
              "@babel/preset-env",
              {
    
    
                // 按需加载
                useBuiltIns: "usage",
                // 指定core-js版本
                corejs: {
    
    
                  version: 3,
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
    
    
                  chrome: "60",
                  firefox: "60",
                  ie: "9",
                  safari: "10",
                  edge: "17",
                },
              },
            ],
          ],
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
    }),
  ],
  mode: "development",
};

4.6 js 压缩

  1. 产环境下会自动压缩js代码
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    })
  ],
  // 生产环境下会自动压缩js代码
  mode: 'production'
};

生产环境下会启用 UglifyJsPlugin 插件,进行js压缩。

  1. 手动配置
    webpack认为,如果配置了minimizer,就表示开发者在自定以压缩插件。内部的JS压缩器就会被覆盖掉。

需要修改配置如下:

const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.js",
    path: resolve(__dirname, "build"),
  },
  optimization: {
    
    
    minimize: true,
    minimizer: [
      // 压缩 js
      new TerserWebpackPlugin(),
      // 压缩css
      new OptimizeCssAssetsWebpackPlugin(),
    ],
  },
  mode: "development",
};

4.7 HTML 压缩

  1. 修改配置文件
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html',
      // 压缩html代码
      minify: {
    
    
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

4.8 生产环境配置

  1. 修改配置文件
const {
    
     resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    
    
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
    
    
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
    
    
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 再执行babel
      */
      {
    
    
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
    
    
          fix: true
        }
      },
      {
    
    
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
    
    
          presets: [
            [
              '@babel/preset-env',
              {
    
    
                useBuiltIns: 'usage',
                corejs: {
    
    version: 3},
                targets: {
    
    
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      {
    
    
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
    
    
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      {
    
    
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
    
    
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
    
    
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html',
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

五、 webpack 优化配置

5.1 性能优化总览

  1. 性能优化分类
  • 开发环境性能优化
  • 生产环境性能优化
  1. 开发环境性能优化
  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map
  1. 生产环境性能优化
  • 优化打包构建速度
    • oneOf
    • babel缓存
    • 多进程打包
    • externals
    • dll
  • 优化代码运行的性能
    • 缓存(hash-chunkhash-contenthash)
    • tree shaking
    • code split
    • 懒加载/预加载
    • pwa

5.2 HMR

  1. 介绍
  • HMR: hot module replacement 热模块替换 / 模块热替换
  • 作用:一个模块发生变化,会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度
  • 样式文件:可以使用HMR功能: 因为style-loader内部实现了
  • js文件:默认不能使用HMR功能: 需要修改js代码,添加支持HMR功能的代码
    • 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。
  • html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了(本地写的html代码没有更新浏览器) (不用做HMR功能)
    • 解决:修改entry入口,将html文件引入 entry: ['./src/js/index.js', './src/index.html']
  1. js文件开启HMR功能
// index.js
// 引入
import print from './print';
import '../css/iconfont.css';
import '../css/index.less';

console.log('index.js文件被加载了~');

print();

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

console.log(add(1, 3));

if (module.hot) {
    
    
  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
  module.hot.accept('./print.js', function() {
    
    
    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();
  });
}
// print.js
console.log('print.js被加载了~');

function print() {
    
    
  const content = 'hello print';
  console.log(content);
}

export default print;
  1. 配置文件
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      // loader的配置
      {
    
    
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
    
    
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
    
    
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
    
    
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
    
    
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
    
    
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
    
    
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    
    
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }
};

5.3 source-map

  1. 简介

source-map: 一种提供源代码到构建后代码映射技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)

  1. 类别

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

  • source-map:外部
    • 错误代码准确信息 和 源代码的错误位置
  • inline-source-map:内联
    • 只生成一个内联source-map
    • 错误代码准确信息 和 源代码的错误位置
  • hidden-source-map:外部
    • 错误代码错误原因,但是没有错误位置
    • 不能追踪源代码错误,只能提示到构建后代码的错误位置
    • 可以隐藏源代码,不隐藏构建代码
  • eval-source-map:内联
    • 每一个文件都生成对应的source-map,都在eval
    • 错误代码准确信息 和 源代码的错误位置
  • nosources-source-map:外部
    • 错误代码准确信息, 但是没有任何源代码信息
    • 可以隐藏源代码,也隐藏构建代码
  • cheap-source-map:外部
    • 错误代码准确信息 和 源代码的错误位置
    • 只能精确到行
  • cheap-module-source-map:外部
    • 错误代码准确信息 和 源代码的错误位置
    • module 会将 loader 的 source map 加入
  1. 内联和外部的区别
  • 外部生成了文件,内联没有(内联直接将source-map信息放在js里面了)
  • 内联构建速度更快
  1. source-map 的选择
  • 开发环境:速度快,调试更友好
    • 速度快(eval>inline>cheap>…) eval-cheap-souce-map > eval-source-map
    • 调试更友好: souce-map > cheap-module-souce-map > cheap-souce-map
    • 建议: eval-source-map(vue,react脚手架) / eval-cheap-module-souce-map
  • 生产环境:源代码要不要隐藏? 调试要不要更友好
    • 内联会让代码体积变大,所以在生产环境不用内联
    • nosources-source-map 全部隐藏
    • hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
    • source-map(调试友好) / cheap-module-souce-map(速度快些)
  1. 配置信息
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      // loader的配置
      {
    
    
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
    
    
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
    
    
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
    
    
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
    
    
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
    
    
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
    
    
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    
    
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    hot: true
  },
  devtool: 'eval-source-map'
};

5.4 oneOf

  1. 解决问题
  • 不使用 oneOf 的情况下,所有的 rules 都会匹配一次,有的可以命中,有的无法命中。
  • 使用 oneOf 的情况下,oneOf 中的 loader 只会匹配一个
  • 优化生产环境打包构建速度
  1. 配置文件
const {
    
     resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = "production";

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  "css-loader",
  {
    
    
    // 还需要在package.json中定义browserslist
    loader: "postcss-loader",
    options: {
    
    
      ident: "postcss",
      plugins: () => [require("postcss-preset-env")()],
    },
  },
];

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: "pre",
        loader: "eslint-loader",
        options: {
    
    
          fix: true,
        },
      },
      {
    
    
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
    
    
            test: /\.css$/,
            use: [...commonCssLoader],
          },
          {
    
    
            test: /\.less$/,
            use: [...commonCssLoader, "less-loader"],
          },
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            options: {
    
    
              presets: [
                [
                  "@babel/preset-env",
                  {
    
    
                    useBuiltIns: "usage",
                    corejs: {
    
     version: 3 },
                    targets: {
    
    
                      chrome: "60",
                      firefox: "50",
                    },
                  },
                ],
              ],
            },
          },
          {
    
    
            test: /\.(jpg|png|gif)/,
            loader: "url-loader",
            options: {
    
    
              limit: 8 * 1024,
              name: "[hash:10].[ext]",
              outputPath: "imgs",
              esModule: false,
            },
          },
          {
    
    
            test: /\.html$/,
            loader: "html-loader",
          },
          {
    
    
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: "file-loader",
            options: {
    
    
              outputPath: "media",
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: "css/built.css",
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: "production",
};

5.5 缓存

  1. 缓存分类
  • babel缓存(让第二次打包构建速度更快)
    • cacheDirectory: true
  • 文件资源缓存(让代码上线运行缓存更好使用)
    • 使用场景: 服务器配置了缓存,用户会使用缓存文件,此时出了个bugfix版本,将变更的文件替换,如何使用户放弃缓存,使用新的文件
    • 文件资源缓存方法
      • hash: 每次wepack构建时会生成一个唯一的hash值。
        • 问题: 因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
      • chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
        • 问题: js和css的hash值还是一样的因,为css是在js中被引入的,所以同属于一个chunk
      • contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
  1. 配置文件
const {
    
     resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = "production";

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  "css-loader",
  {
    
    
    // 还需要在package.json中定义browserslist
    loader: "postcss-loader",
    options: {
    
    
      ident: "postcss",
      plugins: () => [require("postcss-preset-env")()],
    },
  },
];

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: "pre",
        loader: "eslint-loader",
        options: {
    
    
          fix: true,
        },
      },
      {
    
    
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
    
    
            test: /\.css$/,
            use: [...commonCssLoader],
          },
          {
    
    
            test: /\.less$/,
            use: [...commonCssLoader, "less-loader"],
          },
          /*
            正常来讲,一个文件只能被一个loader处理。
            当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
              先执行eslint 在执行babel
          */
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            options: {
    
    
              presets: [
                [
                  "@babel/preset-env",
                  {
    
    
                    useBuiltIns: "usage",
                    corejs: {
    
     version: 3 },
                    targets: {
    
    
                      chrome: "60",
                      firefox: "50",
                    },
                  },
                ],
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true,
            },
          },
          {
    
    
            test: /\.(jpg|png|gif)/,
            loader: "url-loader",
            options: {
    
    
              limit: 8 * 1024,
              name: "[hash:10].[ext]",
              outputPath: "imgs",
              esModule: false,
            },
          },
          {
    
    
            test: /\.html$/,
            loader: "html-loader",
          },
          {
    
    
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: "file-loader",
            options: {
    
    
              outputPath: "media",
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: "css/built.[contenthash:10].css",
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: "production",
  devtool: "source-map",
};

5.6 tree shaking&树摇

  1. 目的

tree shaking:去除无用代码,减少代码体积

  1. 前提
  • 必须使用ES6模块化
  • 开启production环境
  1. 问题

可能会把 css@babel/polyfill (副作用)文件干掉

在package.json中配置:

  • “sideEffects”: false 所有代码都没有副作用(都可以进行tree shaking)
  • “sideEffects”: [“.css", ".less”] 不要进行 tree shaking
  1. 配置文件
const {
    
     resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = "production";

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  "css-loader",
  {
    
    
    // 还需要在package.json中定义browserslist
    loader: "postcss-loader",
    options: {
    
    
      ident: "postcss",
      plugins: () => [require("postcss-preset-env")()],
    },
  },
];

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: "pre",
        loader: "eslint-loader",
        options: {
    
    
          fix: true,
        },
      },
      {
    
    
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
    
    
            test: /\.css$/,
            use: [...commonCssLoader],
          },
          {
    
    
            test: /\.less$/,
            use: [...commonCssLoader, "less-loader"],
          },
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            options: {
    
    
              presets: [
                [
                  "@babel/preset-env",
                  {
    
    
                    useBuiltIns: "usage",
                    corejs: {
    
     version: 3 },
                    targets: {
    
    
                      chrome: "60",
                      firefox: "50",
                    },
                  },
                ],
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true,
            },
          },
          {
    
    
            test: /\.(jpg|png|gif)/,
            loader: "url-loader",
            options: {
    
    
              limit: 8 * 1024,
              name: "[hash:10].[ext]",
              outputPath: "imgs",
              esModule: false,
            },
          },
          {
    
    
            test: /\.html$/,
            loader: "html-loader",
          },
          {
    
    
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: "file-loader",
            options: {
    
    
              outputPath: "media",
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: "css/built.[contenthash:10].css",
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: "production",
  devtool: "source-map",
};

5.7 code split

  1. 目的
  • 代码分割,将一份大的代码分割为多个代码,并行加载,提高加载速度
  • 实现按需加载
  1. 方式一: 多入口

通过多入口的方式拆分文件

const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  // 单页面应用 单入口最终输出一个bundle
  // entry: './src/js/index.js',
  entry: {
    
    
    // 多页面应用 多入口:每个入口输出一个bundle
    index: "./src/js/index.js",
    test: "./src/js/test.js",
  },
  output: {
    
    
    // [name]:取文件名
    filename: "js/[name].[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: "production",
};

  1. 方式二: splitChunks
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
  // 单入口
  // entry: './src/js/index.js',
  entry: {
    
    
    index: './src/js/index.js',
    test: './src/js/test.js'
  },
  output: {
    
    
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html',
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件(如果公共文件太小,也不会单独生成chunk)。如果有会打包成单独一个chunk
  */
  optimization: {
    
    
    splitChunks: {
    
    
      chunks: 'all'
    }
  },
  mode: 'production'
};
  1. 方式三: 通过js代码,让某个文件被单独打包成一个chunk
  • import动态导入语法:能将某个文件单独打包
  • webpackChunkName: 配置打包文件的名字,若不配置则为 0,1,2,3…
function sum(...args) {
    
    
  return args.reduce((p, c) => p + c, 0);
}

import(/* webpackChunkName: 'test' */ "./test")
  .then(({
     
      mul, count }) => {
    
    
    // 文件加载成功~
    // eslint-disable-next-line
    console.log(mul(2, 5));
  })
  .catch(() => {
    
    
    // eslint-disable-next-line
    console.log("文件加载失败~");
  });

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));

5.8 lazy loading&懒加载

console.log('index.js文件被加载了~');

// import { mul } from './test';

document.getElementById('btn').onclick = function() {
    
    
  // 懒加载~:当文件需要使用时才加载~
  // 预加载 prefetch:会在使用之前,提前加载js文件 
  // 正常加载可以认为是并行加载(同一时间加载多个文件)  
  // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({
     
      mul }) => {
    
    
    console.log(mul(4, 5));
  });
};

预加载慎用,原因是兼容性问题比较大。

5.9 pwa & 渐进式网络开发应用程序(离线可访问)

  1. 现状

由于兼容性问题,应用并不广泛,有些大厂在用(淘宝)

  1. 下载安装包

通过 workbox(谷歌开源插件) 使用, webpack 需要安装 workbox-webpack-plugin

npm install --save-dev workbox-webpack-plugin
  1. 配置文件
const {
    
     resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    
    
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
    
    
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
    
    
          fix: true
        }
      },
      {
    
    
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
    
    
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
    
    
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
    
    
              presets: [
                [
                  '@babel/preset-env',
                  {
    
    
                    useBuiltIns: 'usage',
                    corejs: {
    
     version: 3 },
                    targets: {
    
    
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          },
          {
    
    
            test: /\.(jpg|png|gif)/,
            loader: 'url-loader',
            options: {
    
    
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false
            }
          },
          {
    
    
            test: /\.html$/,
            loader: 'html-loader'
          },
          {
    
    
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: 'file-loader',
            options: {
    
    
              outputPath: 'media'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: 'css/built.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html',
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true
      }
    }),
    new WorkboxWebpackPlugin.GenerateSW({
    
    
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        生成一个 serviceworker 配置文件(service-worker.js)~
      */
      clientsClaim: true,
      skipWaiting: true
    })
  ],
  mode: 'production',
  devtool: 'source-map'
};
import {
    
     mul } from "./test";
import "../css/index.css";

function sum(...args) {
    
    
  return args.reduce((p, c) => p + c, 0);
}

// eslint-disable-next-line
console.log(mul(2, 3));
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));

/*
  1. eslint不认识 window、navigator全局变量
    解决:需要修改package.json中eslintConfig配置
      "env": {
        "browser": true // 支持浏览器端全局变量
      }
   2. sw代码必须运行在服务器上
      方式一:直接使用 nodejs
      方式二:使用 serve 包
        npm i serve -g
        serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceWorker
// 处理兼容性问题
if ("serviceWorker" in navigator) {
    
    
  window.addEventListener("load", () => {
    
    
    navigator.serviceWorker
      .register("/service-worker.js")
      .then(() => {
    
    
        console.log("sw注册成功了~");
      })
      .catch(() => {
    
    
        console.log("sw注册失败了~");
      });
  });
}

5.10 多进程打包

  1. 下载安装包
npm install --save-dev thread-loade
  1. 配置文件
const {
    
     resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = "production";

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  "css-loader",
  {
    
    
    // 还需要在package.json中定义browserslist
    loader: "postcss-loader",
    options: {
    
    
      ident: "postcss",
      plugins: () => [require("postcss-preset-env")()],
    },
  },
];

module.exports = {
    
    
  entry: "./src/js/index.js",
  output: {
    
    
    filename: "js/built.[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },
  module: {
    
    
    rules: [
      {
    
    
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: "pre",
        loader: "eslint-loader",
        options: {
    
    
          fix: true,
        },
      },
      {
    
    
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
    
    
            test: /\.css$/,
            use: [...commonCssLoader],
          },
          {
    
    
            test: /\.less$/,
            use: [...commonCssLoader, "less-loader"],
          },
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
              /* 
                放在某一个loader的后面,会对该loader开启多进程打包。 
                进程启动大概为600ms,进程通信也有开销。
                只有工作消耗时间比较长,才需要多进程打包
              */
              {
    
    
                loader: "thread-loader",
                options: {
    
    
                  workers: 2, // 进程2个
                },
              },
              {
    
    
                loader: "babel-loader",
                options: {
    
    
                  presets: [
                    [
                      "@babel/preset-env",
                      {
    
    
                        useBuiltIns: "usage",
                        corejs: {
    
     version: 3 },
                        targets: {
    
    
                          chrome: "60",
                          firefox: "50",
                        },
                      },
                    ],
                  ],
                  // 开启babel缓存
                  // 第二次构建时,会读取之前的缓存
                  cacheDirectory: true,
                },
              },
            ],
          },
          {
    
    
            test: /\.(jpg|png|gif)/,
            loader: "url-loader",
            options: {
    
    
              limit: 8 * 1024,
              name: "[hash:10].[ext]",
              outputPath: "imgs",
              esModule: false,
            },
          },
          {
    
    
            test: /\.html$/,
            loader: "html-loader",
          },
          {
    
    
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: "file-loader",
            options: {
    
    
              outputPath: "media",
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: "css/built.[contenthash:10].css",
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
    
    
      template: "./src/index.html",
      minify: {
    
    
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new WorkboxWebpackPlugin.GenerateSW({
    
    
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        生成一个 serviceworker 配置文件~
      */
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
  mode: "production",
  devtool: "source-map",
};

5.11 externals

  1. 作用

防止将某些包输出到某些依赖中,比如jq我们希望从cdn链接中获取,不希望打包进来。

  1. 配置文件
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    })
  ],
  mode: 'production',
  externals: {
    
    
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
};
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
</head>

<body>
  <h1 id="title">hello html</h1>
  <!-- 需要手动引入进来 -->
  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</body>

</html>

5.12 dll & 动态连接库

  1. 目的

使用dll技术,对某些库(第三方库:jquery、react、vue…)进行单独打包,这部操作是预先操作的。等到真正业务打包的时候就不需要打包这样文件了,只需要打包业务代码,引入预先打包好的第三方库。

  1. 配置文件
// webpack.dll.js
/*
  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
    当你运行 webpack 时,默认查找 webpack.config.js 配置文件
    需求:需要运行 webpack.dll.js 文件
      --> webpack --config webpack.dll.js
*/

const {
    
     resolve } = require('path');
const webpack = require('webpack');

module.exports = {
    
    
  entry: {
    
    
    // 最终打包生成的[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
  },
  output: {
    
    
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
    
    
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};

// webpack.config.js
const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
    
    
  entry: './src/index.js',
  output: {
    
    
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
    
    
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
    
    
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  mode: 'production'
};

六、 webpack 配置详情

6.1 entry

入口起点

  1. 字符串
entry: './src/index.js'
  • 单入口
  • 打包形成一个chunk
  • 输出一个bundle文件
  • 此时chunk的名称默认是main
  1. 数组
entry: ['./src/index.js', './src/add.js']
  • 多入口
  • 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
  • 此时chunk的名称默认是main
  • 只有在HMR功能中让html热更新生效
  • 数组的文件一般是没有互相依赖关系的,但是又处于某些原因要将它们打包在一起
  1. 对象
  entry: {
    
    
    index: "./src/index.js",
    add: "./src/add.js"
  }
  • 多入口
  • 有几个入口文件就形成几个chunk,输出几个bundle文件
  • 此时chunk的名称是key
  1. 特殊用法
entry: {
    
    
    // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
    index: ["./src/index.js", "./src/count.js"],
    // 形成一个chunk,输出一个bundle文件。
    add: "./src/add.js"
  }

6.2 output

const {
    
     resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/index.js",
  output: {
    
    
    // 文件名称(指定名称+目录)
    filename: "js/[name].js",
    // 输出文件目录(将来所有资源输出的公共目录)
    path: resolve(__dirname, "build"),
    // 所有资源引入公共路径前缀,例如:'imgs/a.jpg' --> '/imgs/a.jpg'
    publicPath: "/",
    chunkFilename: "js/[name]_chunk.js", // 非入口chunk的名称
    // library: '[name]', // 整个库向外暴露的变量名
    // libraryTarget: 'window' // 变量名添加到哪个上 browser
    // libraryTarget: 'global' // 变量名添加到哪个上 node
    // libraryTarget: 'commonjs'
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: "development",
};

6.3 module

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

module.exports = {
    
    
  entry: './src/index.js',
  output: {
    
    
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      // loader的配置
      {
    
    
        test: /\.css$/,
        // 多个loader用use
        use: ['style-loader', 'css-loader']
      },
      {
    
    
        test: /\.js$/,
        // 排除node_modules下的js文件
        exclude: /node_modules/,
        // 只检查 src 下的js文件
        include: resolve(__dirname, 'src'),
        // 优先执行
        enforce: 'pre',
        // 延后执行
        // enforce: 'post',
        // 单个loader用loader
        loader: 'eslint-loader',
        options: {
    
    }
      },
      {
    
    
        // 以下配置只会生效一个
        oneOf: []
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};

6.4 resolve

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

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  // 解析模块的规则
  resolve: {
    
    
    // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
    alias: {
    
    
      $css: resolve(__dirname, 'src/css')
    },
    // 配置省略文件路径的后缀名
    extensions: ['.js', '.json', '.jsx', '.css'],
    // 告诉 webpack 解析模块是去找哪个目录
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  }
};

6.5 devServer

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

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  resolve: {
    
    
    alias: {
    
    
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  devServer: {
    
    
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
    
    
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    hot: true,
    // 不要显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了一些基本启动信息以外,其他内容都不要显示
    quiet: true,
    // 如果出错了,不要全屏提示~
    overlay: false,
    // 服务器代理 --> 解决开发环境跨域问题
    proxy: {
    
    
      // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
      '/api': {
    
    
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
        pathRewrite: {
    
    
          '^/api': ''
        }
      }
    }
  }
};

6.6 optimization

const {
    
     resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
    
    
  entry: './src/js/index.js',
  output: {
    
    
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
    chunkFilename: 'js/[name].[contenthash:10]_chunk.js'
  },
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'production',
  resolve: {
    
    
    alias: {
    
    
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  optimization: {
    
    
    splitChunks: {
    
    
      chunks: 'all'
      // 默认值,可以不写~
      /* minSize: 30 * 1024, // 分割的chunk最小为30kb
      maxSiza: 0, // 最大没有限制
      minChunks: 1, // 要提取的chunk最少被引用1次
      maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
      maxInitialRequests: 3, // 入口js文件最大并行请求数量
      automaticNameDelimiter: '~', // 名称连接符
      name: true, // 可以使用命名规则
      cacheGroups: {
        // 分割chunk的组
        // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
        // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          // 优先级
          priority: -10
        },
        default: {
          // 要提取的chunk最少被引用2次
          minChunks: 2,
          // 优先级
          priority: -20,
          // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
          reuseExistingChunk: true
        } 
      }*/
    },
    // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
    // 解决:修改a文件导致b文件的contenthash变化,在b文件中引入a文件,打包后b文件通过引用a文件的contenthash来引入a文件,修改a文件,a文件的contenthash变化自然会导致b文件发生变化
    runtimeChunk: {
    
    
      name: entrypoint => `runtime-${
      
      entrypoint.name}`
    },
    minimizer: [
      // 配置生产环境的压缩方案:js和css
      new TerserWebpackPlugin({
    
    
        // 开启缓存
        cache: true,
        // 开启多进程打包
        parallel: true,
        // 启动source-map
        sourceMap: true
      })
    ]
  }
};

参考

附录一 package.json

{
    
    
  "name": "webpack_code",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    
    
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    
    
    "@babel/core": "^7.8.4",
    "@babel/polyfill": "^7.8.3",
    "@babel/preset-env": "^7.8.4",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel": "^6.23.0",
    "babel-loader": "^8.0.6",
    "core-js": "^3.6.4",
    "css-loader": "^3.4.2",
    "eslint": "^6.8.0",
    "eslint-config-airbnb-base": "^14.0.0",
    "eslint-loader": "^3.0.3",
    "eslint-plugin-import": "^2.20.1",
    "file-loader": "^5.0.2",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^1.1.3",
    "terser-webpack-plugin": "^2.3.5",
    "thread-loader": "^2.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3",
    "workbox-webpack-plugin": "^5.0.0"
  },
  "dependencies": {
    
    
    "jquery": "^3.4.1"
  },
  "browserslist": {
    
    
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    
    
    "extends": "airbnb-base",
    "env": {
    
    
      "browser": true
    }
  },
  "sideEffects": [
    "*.css"
  ]
}

猜你喜欢

转载自blog.csdn.net/letianxf/article/details/129227015
今日推荐