Tian Jiale's Blog

webpack 学习笔记

webpack 简介

webpack 是什么

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

webpack 五个核心概念

Entry

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

Output

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

Loader

Loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。

Plugins

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

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 和 TerserPlugin 能让代码优化上线 运行的环境

webpack 的简易使用

初始化配置

  1. 初始化 package.json

    npm init
    
  2. 下载并安装 webpack

    // 全局安装
    npm install webpack webpack-cli -g
    // 安装到开发环境
    npm install webpack webpack-cli -D
    

编译打包应用

  1. 创建文件

  2. 运行指令

    // 开发环境
    webpack src/js/index.js -o build/js/built.js --mode=development
    

    功能:webpack 能够编译打包 js 和 json 文件,并且能将 es6 的模块化语法转换成 浏览器能识别的语法。

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

    功能:在开发配置功能上多一个功能,压缩代码。

  3. 效果

    webpack 能够编译打包 js 和 json 文件。 能将 es6 的模块化语法转换成浏览器能识别的语法。 能压缩代码。

  4. 问题

    不能编译打包 css、img 等文件。 不能将 js 的 es6 基本语法转化为 es5 以下语法。

webpack 开发环境的基本配置

创建配置文件

  1. 创建文件 webpack.config.js

  2. 配置内容如下

    // resolve用来拼接绝对路径的方法
    const { resolve } = require('path');
    
    module.exports = {
      // 入口起点
      entry: './src/index.js',
      // 输出
      output: {
        // 输出文件名
        filename: 'built.js',
        // 输出路径
        path: resolve(__dirname, 'build'),
      },
      // 模式
      mode: 'development', // 开发模式
      // mode: 'production'
    };
    
  3. 运行指令: webpack

打包样式资源

  1. 创建 less 文件

  2. 下载安装 loader 包

    npm install css-loader style-loader less-loader --save-dev
    
  3. 修改配置文件

    // resolve用来拼接绝对路径的方法
    const { resolve } = require('path');
    
    module.exports = {
      // 入口起点
      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'
    };
    
  4. 运行指令: webpack

打包 HTML 资源

  1. 创建 HTML 文件

  2. 下载安装 plugin 包

    npm install --save-dev html-webpack-plugin
    
  3. 修改配置文件,html-webpack-plugin 的详细配置见 Github

    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',
    };
    
  4. 运行指令: webpack

打包图片资源

  1. 创建图片文件

  2. 下载安装 loader 包,在 webpack5 中,增加了资源模块用来处理资源文件而无需加载 loader,下载的 html-loader 只是将图片 require 到 js 目录以进行打包。

    npm install --save-dev html-loader
    
  3. 修改配置文件,其中

    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: [
          // css-loader 会对 @import 和 url() 进行处理(导入)
          {
            test: /\.less$/,
            use: ['style-loader', 'css-loader', 'less-loader'],
          },
          // 将HTML中的可加载属性导入到js进行打包
          {
            test: /\.html$/,
            loader: 'html-loader',
          },
          // 处理import的图片路径问题,实际上使用 Asset Modules 可以接收并加载任何文件,然后将其输出到构建目录
          {
            test: /\.(png|svg|jpg|jpeg|gif)$/i,
            type: 'asset', // 自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
            parser: {
              dataUrlCondition: {
                maxSize: 8 * 1024, // 8kb 是默认值
              },
            },
          },
        ],
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
        }),
      ],
      mode: 'development',
    };
    

打包其他资源

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)$/,
        type: 'asset', // 自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 8kb 是默认值
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
  mode: 'development',
};

source map

当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。可以通过一定设置来追踪错误文件。在配置中加入如下属性:

devtool: 'inline-source-map',

开发工具

在每次编译代码时,手动运行 npm run build 会显得很麻烦。webpack 提供几种可选方式,帮助你在代码发生变化后自动编译代码:

  1. webpack’s Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

但一般使用 webpack-dev-server,其具有 live reloading(实时重新加载) 功能,webpack-dev-middleware 是 webpack-dev-server 的中间件。配置如下:

devServer: {
  // 项目构建后路径
  contentBase: resolve(__dirname, 'build'),
  // 启动gzip压缩
  compress: true,
  // 端口号
  port: 3000,
  // 自动打开浏览器
  open: true
}

运行应使用webpack serve 而不是 webpack-dev-server,见issues

若要更改代码时浏览器自动刷新可做如下设置:

module.exports = {
  target: 'web',
};

webpack 生产环境的基本配置

生产环境下默认使用 TerserPlugin

启用 source map

在生产环境中启用 source map,因为它们对 debug(调试源码) 和运行 benchmark tests(基准测试) 很有帮助。

devtool: 'eval-source-map',

提取所有的 CSS 到一个文件中

使用插件 mini-css-extract-plugin,同时将 Loader style-loader 换成 MiniCssExtractPlugin.loader

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// "style-loader"换成如下配置,其中publicPath为当前文件处理后的公共路径
{
  loader: MiniCssExtractPlugin.loader,
  options: {
    publicPath: "../",
  },
},
// 增加 mini-css-extract-plugin 插件
plugins: [
  new MiniCssExtractPlugin({
    filename: '[name].css',
  }),
],

css 兼容性处理

使用 postcss-loader 和 postcss-preset-env 两个 loader。

npm install --save-dev postcss-loader postcss postcss-preset-env

创建配置文件 postcss.config.js

module.exports = {
  plugins: [
    [
      'postcss-preset-env',
      {
        // 其他选项
      },
    ],
  ],
};

webpack.config.js 中添加 postcss-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
    ],
  },
};

在 package.json 中增加如下配置

{
  "browserslist": ["> 1%", "not dead", "not op_mini all"]
}

压缩 css

使用插件 CssMinimizerWebpackPlugin,安装:

npm install css-minimizer-webpack-plugin --save-dev

接着在 webpack 配置中加入该插件,与此同时 html 也被压缩了(貌似是默认压缩?)。

optimization: {
  minimize: true,
  minimizer: [
    new CssMinimizerPlugin(),
  ],
},

js 语法检查 Eslint

为了解决 eslint-loader 的问题,eslint-loader 被弃用,改用EslintWebpackPlugin插件。

安装:

npm install eslint-webpack-plugin --save-dev
// 需要用到 eslint
npm install eslint --save-dev

使用 airbnb 的 js 语法检查设置

npm install eslint-config-airbnb-base eslint-plugin-import --save-dev

在 package.json 中添加配置

{
  "eslinkConfig": {
    "extends": "airbnb-base"
  }
}

注意:可能有非常多的错误警告,建议搭配 vs code 插件 ESlint 做格式化使用。

调试过程中会有 console 语句,可以在 console 语句前加入如下注释用来忽略对 console 的检查。

// eslint-disable-next-line no-console
console.log('no-console');

js 兼容性处理

在之前使用 @babel/polyfill 来处理 js 兼容性问题,但 babel 官网显示已被弃用,建议直接用 core-js/stableregenerator-runtime/runtime

🚨 As of Babel 7.4.0, this package has been deprecated in favor of directly including core-js/stable (to polyfill ECMAScript features) and regenerator-runtime/runtime (needed to use transpiled generator functions):

import 'core-js/stable';
import 'regenerator-runtime/runtime';

安装

npm i --save core-js regenerator-runtime

在 js 中引入 core-js/stableregenerator-runtime/runtime

import 'core-js/stable';
import 'regenerator-runtime/runtime';

拓展

@babel/preset-env 可以做到按需加载,具体见 链接,没看明白怎么用,暂时不做学习,同时 webpack 官网建议 “不加选择地和同步地加载所有 polyfill/shim,尽管这会导致额外的 bundle 体积成本。”,因为 pyolyfill 被弃用,所以我们要不加选择地加载core-js/stableregenerator-runtime/runtime,因为有些浏览器并没有很好地支持 ECMAScript 的新特性。取舍问题建议查看 webpack 官网描述

js 压缩

官网有如下内容:

如果你使用的是 webpack v5 或以上版本,你不需要安装这个插件。webpack v5 自带最新的 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本。

生产环境下自动压缩,但通过以上的配置流程之后,我的 js 并没有被压缩,甚至还达到了 427kb 的大小,以至于报出错误警告说文件过大(限制 244kb),所以自行配置。

添加到开发依赖项:

npm install terser-webpack-plugin --save-dev

配置 webpack.config.js

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

性能优化/功能提升

模块热替换(hot module replacement)

模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。更多介绍见 模块热替换

设置方法:在 [开发工具](### 开发工具) 中添加如下设置:

hot: true,

可以看到此时 css 和 js 都热更新了,但 html 并没有热更新,所以要修改 webpack 配置,在 entry 入口中添加上 html 文件。

entry: ['./src/index.js', './src/index.html'],

但是,没那么简单,单页面的流行使得 html 不需要更改,因为都是 js 生成的虚拟 dom 树再添加到页面中的,而且 html 页面的变化使得所有其他的页面都要去变化,设置 html 热加载还不如不设置热加载。

生产环境和开发环境配置分离

development(开发环境)production(生产环境) 这两个环境下的构建目标存在着巨大差异。在开发环境中,我们需要:强大的 source map 和一个有着 live reloading(实时重新加载) 或 hot module replacement(热模块替换) 能力的 localhost server。而生产环境目标则转移至其他方面,关注点在于压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置

虽然,以上我们将 生产环境开发环境 做了细微区分,但是,请注意,我们还是会遵循不重复原则(Don’t repeat yourself - DRY),保留一个 “common(通用)” 配置。为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。此工具会引用 “common” 配置,因此我们不必再在环境特定(environment-specific)的配置中编写重复代码。

我们先从安装 webpack-merge 开始,并将之前指南中已经成型的那些代码进行分离:

npm install --save-dev webpack-merge

webpack.dev.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist',
  },
});

webpack.prod.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
});

package.json

"scripts": {
  "build": "webpack --config webpack.prod.js",
  "dev-build": "webpack serve --open --config webpack.dev.js"
},

eslint 检测依赖问题,在文件开头添加注释

/* eslint-disable import/no-extraneous-dependencies */

source-map 详解

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

格式:[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 加入

生产环境:eval-source-map / eval-cheap-module-souce-map

开发环境:source-map / cheap-module-souce-map

oneOf

在前面我们设置了许多 loader 规则,但是过多的 loader 会使得打包速度变慢,所以我们需要一种规则使得打包的时候只进行一次匹配处理,这个规则就是 oneOf,配置方法如下:

module: {
  rules: [
    {
      oneOf: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader',
            'postcss-loader',
          ],
        },
        {
          test: /\.less$/,
          use: [
            'style-loader',
            'css-loader',
            'less-loader',
            'postcss-loader',
          ],
        },
      ],
    },
  ],
},

Tree Shaking

得益于 es6 模块的静态分析,可以在打包时直接去掉不适用的代码。

条件:1. 必须使用 ES6 模块化 2. 开启 production 环境

作用:减少代码体积

在 package.json 中配置:"sideEffects": false 表示所有代码都没有副作用(都可以进行 tree shaking)

这样会导致的问题:可能会把 css / @babel/polyfill 文件干掉(副作用)

所以可以配置:"sideEffects": ["*.css", "*.less"] 不会对 css/less 文件 tree shaking 处理。

代码分割

多入口模式

在设置中可以设置 entry 为一个对象,实现多入口,此时可以从但入口中去除掉其他入口的 import。

entry: {
  index: './src/js/index.js',
  test: './src/js/test.js'
},

SplitChunksPlugin

开箱即用的 SplitChunksPlugin 对于大部分用户来说非常友好。

默认情况下,它只会影响到按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。

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

同时输出文件名需要添加上 [chunkhash:10] 此类字段,否则输出文件名相同而报错。

因为默认最小文件为 20kb 所以可以使用 import 动态导入语法来使得引入的文件强制单独打包。

更多配置可以参见官网描述。

其他见 代码分离

懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

懒加载同时是上一小节“代码分离”的一部分,但因时常被单独提及,这里另加一小节。

当涉及到动态代码拆分时,虽然提供了两种技术,但require.ensure 已经是不建议使用的了,所以我们用符合 ECMAScript 提案import() 语法 来实现动态导入,因为返回的是 Promise,所以需要使用像 es6-promise 或者 promise-polyfill 这样 polyfill 库,来预先填充(shim) Promise 环境。更好的方法还是使用[js 兼容性处理](### js 兼容性处理) 的方案。

示例:

import('./test').then(({ mul }) => {
  console.log(mul(2, 5));
});

可以使用 魔法注释 来设置文件名、加载方式、预获取/预加载模块

渐进式网络应用程序

渐进式网络应用程序(progressive web application - PWA),是一种可以提供类似于 native app(原生应用程序) 体验的 web app(网络应用程序)。

添加 Workbox

添加 workbox-webpack-plugin 插件,然后调整配置文件

npm install workbox-webpack-plugin --save-dev
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
};

注册 Service Worker

index.js

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('./service-worker.js')
      .then((registration) => {
        // eslint-disable-next-line no-console
        console.log('SW registered: ', registration);
      })
      .catch((registrationError) => {
        // eslint-disable-next-line no-console
        console.log('SW registration failed: ', registrationError);
      });
  });
}

多进程打包

需要安装 thread-loader 来对 babel 新开进程,从名字可以看出,这是一个 loader,可以添加到 use 中使得打包时新开进程,另外还可以设置进程数量来优化打包速度,需要注意的是,进程启动大概需要 600ms,进程通信需要消耗时间,所以加上该 loader 并不一定会有正向优化,应该视具体情况而定。

{
  loader: 'thread-loader',
  options: {
    workers: 2 // 设置进程数为2个
  }
}

外部扩展(externals)

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。官网提供多种方法,以下介绍几个 web 常用的。

方法 1:字符串

module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

将 jquery 模块替换为全局变量 jQuery。

方法 2:函数

function ({ context, request, contextInfo, getResolve }, callback) > function ({ context, request, contextInfo, getResolve }) => promise 5.15.0+

函数接收两个入参:

  • ctx (object):包含文件详情的对象。

    ctx.context (string): 包含引用的文件目录。

    ctc.request (string): 被请求引入的路径。

    ctx.contextInfo (string): 包含 issuer 的信息(如,layer)

    ctx.getResolve 5.15.0+: 获取当前解析器选项的解析函数。

  • callback (function (err, result, type)): 用于指明模块如何被外部化的回调函数

回调函数接收三个入参:

  • err (Error): 被用于表明在外部外引用的时候是否会产生错误。如果有错误,这将会是唯一被用到的参数。

  • result (string [string] object): 描述外部化的模块。可以接受形如 ${type} ${path} 格式的字符串,或者其它标准化外部化模块格式,(string, [string],或 object)。

  • type (string): 可选的参数,用于指明模块的类型(如果它没在 result 参数中被指明)。

externals: [
  function ({ request }, callback) {
    if (/^jquery$/.test(request)) {
      // 使用 request 路径,将一个 commonjs 模块外部化
      return callback(null, 'root jQuery');
    }
    // 继续下一步且不外部化引用
    callback();
  },
],

以上设置表明只对 /^jquery$/ 匹配的模块做处理,其中 callback 的参数含义为可以通过一个全局变量访问 library(例如,通过 script 标签)且全局变量为 jQuery。

以上两种方法都需要在 index.html 中通过 cdn 引入:

<script src='https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js'></script>

DLL

DllPluginDllReferencePlugin 用某种方法实现了拆分 bundles,同时还大幅度提升了构建的速度。“DLL” 一词代表微软最初引入的动态链接库。换句话说就是让某些库单独打包,后续再直接引入到 build 中。

注意:DLL 拆分出来 bundles 是通过单独的配置文件来单独执行 webpack 打包这一过程实现的,不可与项目配置文件混淆。

webpack.dll.js 配置:(将 jquery 单独打包)

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

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery] --> 要打包的库是jquery
    jquery: ['jquery'],
  },
  output: {
    // 输出出口指定
    filename: '[name].js', // name就是jquery
    path: resolve(__dirname, 'dll'), // 打包到dll目录下
    library: '[name]_[hash]', // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个manifest.json --> 提供jquery的映射关系(告诉webpack:jquery之后不需要再打包和暴露内容的名称)
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json'), // 输出文件路径
      entryOnly: true, // 则仅暴露入口,确保 DLL 中的 tree shaking 正常工作
    }),
  ],
  mode: 'production',
};

webpack.config.js 配置:(告诉 webpack 不需要再打包 jquery,并将之前打包好的 jquery 跟其他打包好的资源一同输出到 build 目录下)

// 引入插件
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

// plugins中配置:
plugins: [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  }),
  // 告诉webpack哪些库不参与打包,同时使用时的名称也得变
  new webpack.DllReferencePlugin({
    manifest: resolve(__dirname, 'dll/manifest.json')
  }),
  // 将某个文件打包输出到build目录下,并在html中自动引入该资源
  new AddAssetHtmlWebpackPlugin({
    filepath: resolve(__dirname, 'dll/jquery.js')
  })
],

提升

enrty

entry: 入口起点

  1. string –> ‘./src/index.js’,单入口

    打包形成一个 chunk。 输出一个 bundle 文件。此时 chunk 的名称默认是 main

  2. array –> [’./src/index.js’, ‘./src/add.js’],多入口

    所有入口文件最终只会形成一个 chunk,输出出去只有一个 bundle 文件。

    (一般只用在 HMR 功能中让 html 热更新生效)

  3. object,多入口

    有几个入口文件就形成几个 chunk,输出几个 bundle 文件,此时 chunk 的名称是 key 值

output

output: {
  // 文件名称(指定名称+目录)
  filename: 'js/[name].js',
  // 输出文件目录(将来所有资源输出的公共目录)
  path: resolve(__dirname, 'build'),
  // 所有资源引入公共路径前缀(可以做cdn) --> 'imgs/a.jpg' --> '/imgs/a.jpg'
  publicPath: '/',
  chunkFilename: 'js/[name]_chunk.js', // 指定非入口chunk的名称
  library: '[name]', // 打包整个库后向外暴露的变量名
  libraryTarget: 'window' // 变量名添加到哪个上 browser:window
  // libraryTarget: 'global' // node:global
  // libraryTarget: 'commonjs' // conmmonjs模块 exports
},

resolve

// 解析模块的规则
resolve: {
  // 配置解析模块路径别名: 优点:当目录层级很复杂时,简写路径;缺点:路径不会提示
  alias: {
    $css: resolve(__dirname, 'src/css')
  },
  // 配置省略文件路径的后缀名(引入时就可以不写文件后缀名了)
  extensions: ['.js', '.json', '.jsx', '.css'],
  // 告诉 webpack 解析模块应该去找哪个目录
  modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}

这样配置后,引入文件就可以这样简写:import '$css/index';

dev server

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': ''
      }
    }
  }
}

optimization

contenthash 缓存会导致一个问题:修改 a 文件导致 b 文件 contenthash 变化。 因为在 index.js 中引入 a.js,打包后 index.js 中记录了 a.js 的 hash 值,而 a.js 改变,其重新打包后的 hash 改变,导致 index.js 文件内容中记录的 a.js 的 hash 也改变,从而重新打包后 index.js 的 hash 值也会变,这样就会使缓存失效。(改变的是 a.js 文件但是 index.js 文件的 hash 值也改变了) 解决办法:runtimeChunk –> 将当前模块记录其他模块的 hash 单独打包为一个文件 runtime,这样 a.js 的 hash 改变只会影响 runtime 文件,不会影响到 index.js 文件

output: {
  filename: 'js/[name].[contenthash:10].js',
  path: resolve(__dirname, 'build'),
  chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口文件的其他chunk的名字加_chunk
},
optimization: {
  splitChunks: {
    chunks: 'all',
    /* 以下都是splitChunks默认配置,可以不写
    miniSize: 30 * 1024, // 分割的chunk最小为30kb(大于30kb的才分割)
    maxSize: 0, // 最大没有限制
    minChunks: 1, // 要提取的chunk最少被引用1次
    maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量为5
    maxInitialRequests: 3, // 入口js文件最大并行请求数量
    automaticNameDelimiter: '~', // 名称连接符
    name: true, // 可以使用命名规则
    cacheGroups: { // 分割chunk的组
      vendors: {
        // node_modules中的文件会被打包到vendors组的chunk中,--> vendors~xxx.js
        // 满足上面的公共规则,大小超过30kb、至少被引用一次
        test: /[\\/]node_modules[\\/]/,
        // 优先级
        priority: -10
      },
      default: {
        // 要提取的chunk最少被引用2次
        minChunks: 2,
        prority: -20,
        // 如果当前要打包的模块和之前已经被提取的模块是同一个,就会复用,而不是重新打包
        reuseExistingChunk: true
      }
    } */
  },
  // 将index.js记录的a.js的hash值单独打包到runtime文件中
  runtimeChunk: {
    name: entrypoint => `runtime-${entrypoint.name}`
  },
  minimizer: [
    // 配置生产环境的压缩方案:js/css
    new TerserWebpackPlugin({
      // 开启缓存
      cache: true,
      // 开启多进程打包
      parallel: true,
      // 启用sourceMap(否则会被压缩掉)
      sourceMap: true
    })
  ]
}

webpack 中的默认配置

entry: "./src/index.js"
output.path: path.resolve(__dirname, "dist")
output.filename: "[name].js"

题外话

webpack 东西太多了!!!这学的脑瓜子难受,感觉受到了 dos 攻击,我大脑都要拒绝服务(拒绝思考)了,不过学的时候看弹幕其他人都是先用 vue 后学 webpack,我这直接上来就 webpack,真的头大。还有,这笔记记录的只不过 webpack 的冰山一角,看看官方文档的目录就直接跪了,更不要提写自己的 loader 和 plugin 了,学无止境,webpack 先到此为止吧,啥时候有时间再啃文档,真尼玛多。