# Webpack5 核心基础、入口 entry 与 出口 output

TIP

本章节我们来学习最新的 Webpack5 相关的内容,我们会从以下几个方面来展开讲解

Webpack5 入门知识

  • Webpack 是什么
  • Webpack 初体验
  • mode 配置项

webpack 的入口(entry)配置

  • context 基础目录
  • entry 值的不同形式

webpack 的出口(output)配置

  • output.path
  • output.filename
  • output.publicPath
  • output.chunkFilename
  • has、fullhash、chunkhash、contenthash 的区别

# 一、Webpack5 基础入门

TIP

深入浅出 最新 Webpack5 核心基础,初体验,核心配置项 等

# 1、Webpack 是什么

TIP

本质上,Webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具,当 Webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles(包),它们均为静态资源,用于展示你的内容。

详细查阅 Webpack5 中文文档教程 (opens new window)

  • 模块:是 Webpack 可以处理的最小单位,一切文件都可以当成一个模块,比如 JS、CSS、图片、图标字体等
  • 静态:通俗点说,就是开发过程中存在于本地的 JS、CSS、图标字体等文件就是静态的模块,当然最终都要上传到服务器。

在开发过程中有些资源你是放在本地,使用相对地址来引用,有些是放在服务器,需要通过远程地址(绝对地址)来引用。这些存在本地的就是静态的,而那些需要从远程服务器获取的就是动态的。

所以你可以理解为,使用相对路径来引用的资源,可以看作是静态的模块。

<!--静态的-->
<img src="./logo.png" />
<!--动态的-->
<img src="https://www.xxx.com/images/logo.png" />

对于动态的内容,webpack 是没有办法处理的。

打包

打包简单理解就是将多个模块组合成一个模块。当然这多个模块之间有一定的依赖关系,根据他们的依赖关系来组合成一个模块。

如下:

src目录下有 3 个 JS 文件,分别是为index.js、a.js、b.js

// index.js 模块
import {name,age} from './a.js'
function()
console.log(name,age)

// a.js 模块
import {age} from "./b.js"
const name="icoding";
export {name,age}

// b.js内容
export const age=33

如果我们以a.js作为打包的入口文件,最终打包后会生成一个新的 JS 文件,这个新的 JS 文件我们称为出口文件,内容如下

(() => {
  "use strict";
  console.log("icoding", 33);
})();

注:

当然,webpack 在打包时,可以是多入口的,可以将index.jsa.js都作为打包的入口,这个时候对应的也会生成两个新的 JS 文件,这两个新的 JS 文件我们称为 出口文件

# 2、Webpack 初体验

TIP

接下来我们就利用 Webpack 来完成我们上面所说的打包工作。这里我们重点关注打包的整个流程,关于更多的细节,后面会单独来讲。

打包具体流程如下:

  • 创建项目目录,并初始化 npm 的package.json文件
  • 准备好需要打包的 JS 文件(index.js、a.js、b.js)
  • 安装 webpack 需要的包
  • 执行打包命令,完成打包。

以上打包流程可以参考 webpack 官网指南 (opens new window),主要考虑后续升级有可能会有部分细节与现在讲的不太一样。

所以大家后期还是要以官方提供的指南为主。

# 2.1、创建项目,并初始化 npm

TIP

首先创建项目目录文件夹icoding,然后进入当前目录,在当前目录初始化 npm

# 第一步:创建icoding文件夹,作为项目的目录文件夹
md icoding

# 第二步:进入icoding文件夹目录
cd icoding

# 第三步:初始化npm,生成package.json文件
npm init -y

完成以上命令后,会在当前目录创建icoding文件夹,并在 icoding 文件夹生成了package.json文件

# 2.2、准备好需要打包的 JS 文件

TIP

接下来,我们将在icoding文件夹下,分别新src文件夹和dist文件夹

md src
md dist
  • src 文件夹:用于存放源代码(需要打包的文件),我们把把index.js,a.js,b.js放到这个目录下
  • dist 文件夹:用于存放打包后的代码,利用 webpack 打包生成后的文件会添加到这个目录下

创建好的项目录录结构如下:

icoding
├─ dist
├─ package.json
└─ src
   ├─ index.js
   ├─ b.js
   └─ c.js

index.js、a.js、b.js 的内容如下

// index.js 模块
import { name, age } from "./a.js";
console.log(name, age);

// a.js 模块
import { age } from "./b.js";
const name = "icoding";
export { name, age };

// b.js内容
export const age = 33;

# 2.3、安装 webpack 需要的包

TIP

执行以下命令,本地安装Webpack 所需要的包,添加到开发依赖

# webpack 是webpack的核心包
# webpack-cli 是命令行运行webpack所需要的包
# --save-dev 安装为开发依赖,因为webpack只是用来帮我们打包文件,项目上线是不需要用到他
# 以下安装并没有指定版本,安装最新的长期支持版本
npm install webpack webpack-cli --save-dev

# 以下是全局安装,不过我们一般会选择本地安装(主要考虑不同的项目对webpack版本的依赖不同)
npm install webpack webpack-cli -D -g

安装后在package.json中添加了如下字段

"devDependencies": {
    "webpack": "^5.76.2",
    "webpack-cli": "^5.0.1"
  }

# 2.4、执行 webpack 命令打包

TIP

在当前目录下执行以下命令,完成打包工作

npx webpack

打包后在dist目录下,生成了main.js文件,文件内容如下:

(() => {
  "use strict";
  console.log("icoding", 33);
})();

解析:

当我们执行npx webpack时,默认会把当前目录下的src/index.js文件作为打包的入口文件,打包后默认生成main.js出口文件,并输出到dist目录。

# 2.5、npm scripts

TIP

我们也可以在package.json中,配置以下信息,然后执行npm run webpack 来完成打包工作

"scripts": {
    "webpack":"webpack"
  },

注:

如果我们想要指定打包的入口和出口,我们需要通过 Webpack 的配置文件来配置。

# 2.6、Webpack 配置文件

TIP

如果不配置,Webpack 怎么知道

  • 你想用 Webpack 来打包哪些文件呢?
  • 打包后的文件要放在哪里呢?
  • 打包过程中需要做哪些处理? 比如打包时要不要把代码压缩下,需不需要把 ES6 转换成 ES5 等此类的操作。都需要在配置文件中说明。

配置文件的名称为 webpack.config.js (在当前项目的根目录下自己新建)。

配置文件也是一个模块,后面我们需要将配置的内容导出去,供外部使用。

需要注意的是

咱们这里的模块导出就不要使用 ES6 的模块了,咱们这里的 Webpack 是要在 Node 平台上运行的,所以我们需要使用 Node 中导入模块的方法。

// 不需要导出的内容,就写在这里就行

// 导出模块
module.exports = {};

接下来,通过 Webpack 的配置文件来指定打包的入口和出口。

现在我们在项目的根目录下新建webpack.config.js文件,配置以下信息

// webpack是基于node的,所以这里可以使用node.js语法
// path模块为node中的内置模块,用来解决路径问题
const path = require("path");
// 模块导出
module.exports = {
  // 指定打包的入口文件,单入口
  entry: "./src/index.js",

  // 指定打包的出口
  output: {
    // 打包后文件存放的目录,__dirname为node的全局变量,
    // 为当前文件所在的绝对路径 C:\Users\EDY\Desktop\test\icoding
    // path.resolve()用来将两个参数拼接成当一个完整的路径
    path: path.resolve(__dirname, "dist"),
    // 打包后文件的名字
    filename: "bundle.js",
  },
};

以上配置中的

  • entry 表示打包的入口文件,Webpack 打包时,会以这个文件作为入口,根据他的关系依赖图,来完成打包。
  • output 表示打包后资源的出口配置项,他是一个对象,对象中的
  • path表示打包后资源的输出路径,他是一个绝对值
  • filename表示打包后输出的出口文件名
  • filename中的值与path中的值拼接后,得到一个完整的绝对地址,告诉 Webpack,打包后输出的出口文件名和位置。

然后运行npx webpack完成打包,在 dist 目录中生成了bundle.js文件。

文件内容如下:

(() => {
  "use strict";
  console.log("icoding", 33);
})();

注:

在我们执行npx webpack命令的时候,Webpack 会自动寻找 Webpack 的配置文件并使用配置信息进行打包,如果找不到该文件就会使用默认配置打包。

# 3、总结:Webpack 打包流程

TIP

Webpack 打包具体流程如下:

  • 创建项目目录,并在当前目录下初始化 npm(创建package.json
  • 准备好打包的入口文件和出口目录 (在当前目录下创建 src 文件夹和 dist 文件夹,把需要打包的 JS 文件放入 src 目录)
  • 执行npm i webpack webpack-cli -D命令,本地安装 Webpack,并添加到开发依赖
  • 在当前目录下新建webpack.config.js文件,配置打包的入口和出口
  • 最后执行npx webpack完成打包工作,或在package.json中添加以下配置,然后在命令终端执行npm run webpack完成打包工作
"scripts": {
    "webpack":"webpack",

     // 也可以在打包时,指定webpack的配置文件
     // "webpack":"webpack --config 指定的配置文件"
     //  "webpack": "webpack --config webpack.config.js"
  },

# 4、mode 配置项

TIP

在 Webpack 打包时,我们可以通过 mode 配置选项,告诉 Webpack 使用相应模式来打包。

点击 查阅官方文档 (opens new window)

mode 属性有三个值:

属性值 说明
production 默认值,表示给生产环境打包
development 表示给开发环境打包
none 表示不使用任何的默认优化选项

注:

在不同的模式下打包,Webpack 会做不同的优化处理,例如:production 模式下会对打包后的代码进行压缩。而 none 模式表示对打包后的代码不做任何的处理。

想要在 Webpack 打包时,根据对应的模式来打包,需要先将 mode 属性设置为对应值。

设置方式有两种:

  • 方式一:在webpack.config.js文件的配置对象上添加 mode 选项
module.exports = {
  mode: "development", // 打包模式,还可以是 production  或 none
};
  • 方式二:从 CLI(命令行)参数中传递
npx webpack --mode=development

注:

如果没有设置对应的 mode 选项,则默认使用production模式打包。如果以上两种方式同时设置,则优先以 CLI 命令行参数中传递的模式为主。

# 4.1 根据 mode 变量更改打包行为

TIP

如果想根据 CLI 命令行参数中的 mode 值来改变打包的行为,则需要将webpack.config.js中的配置导出为函数,而不是对象。

具体如下:

const path = require("path");
// 配置对象
const config = {
  entry: "./src/a.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
};

// argv 表示命令行传过来的参数对象(参数以键值分别为argv对象的属性和值)
// env 用来接受环境变量
module.exports = (env, argv) => {
  console.log(env);
  console.log(arg);
  if (argv.mode === "development") {
    // 更多配置,可以在这里更改config对象中属性的值实现
    console.log("开发模式打包");
    // 开发模式下把打包入口改为a.js
    config.entry = "./src/a.js";
  }

  if (argv.mode === "production") {
    // 更多配置,可以在这里更改config对象中属性的值实现
    console.log("生产模式打包");
    config.entry = "./src/b.js";
  }
  return config;
};

配置解析

  • 如果执行命令时传入的--mode=development则为开发模式打包,打包入口文件为./src/a.js
  • 如果执行命令时传入的--mode=production则为生产模式打包,打包入口文件为./src/b.js

当我们在命令行执行 npx webpack --mode=development --env global=local时,表示以开发模式打包,打包入口为./src/a.js文件,箭头函数中的 env 与 argv 对应的值如下

// evn对应的对象
{ WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, global: 'local' }

// argv对应的对象
{
  mode: 'development',
  env: { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, global: 'local' }
}

# 二、Webpack 的入口(entry)配置

TIP

前面我们已经学习了简单的 Webpack 资源入口与出口的配置,接下来我们将深入学习 Webpack 的资源入口 entry 的配置。

我们以下面这个 Webpack 配置文件来展开讲解

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  mode: "none",
};

注:

上面配置表示从当前根目录下的./src/index.js文件开始打包,打包后生成的bundle.js文件放在根目录下的dist文件夹下。

上面的entry表示打包入口,我们看到他是一个相对路径。实际上上面配置还省略了一个置配参数context,Webpack 官方称为基础目录。

# 1、context 基础目录

TIP

  • context表示入口的基础目录,他的值是一个字符串,表示一个绝对路径,表示资源入口entry是以那个目录为起点的。
  • 在实际开发中,通常不会设置context,不设置时,他的默认值为当前工作目录的绝对路径。因为我们打包时,都是在当前工作目录下开始打包的,所以默认值并不会造成地址出错。
  • context的值与entry的值拼接起来,就是 Webpack 的打包入口文件了

以下配置表示从工程目录的src文件夹下的index.js文件开始打包

// 导入path模块
const path = require("path");
module.exports = {
  // 以下可以理解为context的默认值(当前工程目录的绝对路径),所以也可以省略不写
  context: path.resolve(__dirname, ""),
  entry: "./src/index.js",
};

注:

打包后,默认生成打包后的出口文件main.js,保存在当前目录的dist目录下,如果当前目录下没有dist目录,则会自动创建dist目录

# 2、入口 entry

TIP

  • entry 表示 Webpack 资源入口文件,也就是打包的入口文件,入口文件的路径需要用相对路径来表示。
  • entry 的值可以是一个字符串,也可是数组、对象、函数、描述符形式。当然不同的形式也有不同的含义,接下来我们就来 一 一展开讲解。

# 2.1 入口 entry 的值是字符串

TIP

入口 entry 是字符串形式,我们前面使用过了,表示的就是打包的入口 JS 文件,所以就不再重复讲解。

# 2.2、入口 entry 的值是数组

TIP

如果 entry 的值是一个数组,那数组中的最后一个文件为资源的入口文件,数组的其余文件会被预先构建到入口文件中。

基本用法

在当前目录下的src文件目录下有三个文件,分别为a.jsb.jsindex.js,文件内容如下

// a.js
console.log("a.js中内容");

// b.js
console.log("b.js中内容");

// index.js
console.log("index.js中内容");

以下是 webpack 的配置文件,下面配置相当于在index.js文件中先用import导入a.jsb.js,然后再把index.js文件作为入口文件打包

// webpack.config.js
const path = require("path");
module.exports = {
  entry: ["./src/a.js", "./src/b.js", "./src/index.js"],
};

上面,如同以下写法

// index.js
import "./a.js";
import "./b.js";
console.log("index.js中内容");
// webpack.config.js
const path = require("path");
module.exports = {
  entry: "./src/index.js",
};

注:

打包后,默认会生成出口文件main.js,并保存在当前目录的dist目录下。

如果当前目录下没有dist目录,则会自动创建dist目录

# 2.3、入口 entry 的值是对象

TIP

  • 如果一个项目有多个入口,我们通常把entry的值设置为一个对象,表示多入口配置。
  • 对象有多少个属性,就表示有多少个入口。
  • 打包后,对象的属性名会作为生成后对应的出口文件的名称。

前面我们讲的字符串和数组形式的值,都是属于单入口,打包后只会生成一个 JS 文件。而多入口打包后会生成多个 JS 文件。

基本用法

在当前目录下的src文件目录下有三个文件,分别为a.jsb.jsindex.jslogin.js

以下是 Webpack 的配置文件,表示多入口打包,entry 对应的对象有多少个属性,就有多少个入口

const path = require("path");
module.exports = {
  entry: {
    app: ["./src/a.js", "./src/b.js", "./src/index.js"],
    login: "./src/login.js",
  },
};

上面配置,表示有两个入口

  • 第一个入口是./src/index.js文件,打包前会先将a.jsb.js预先构建到index.js
  • 第二个入口是./src/login.js文件。

打包后,对象的属性名会作为生成后对应的出口文件的名称。

  • entryapp属性对应的入口文件打包后生成的出口文件名为app.js
  • entrylogin属性对应的入口文件打包后生成的出口文件名为login.js

所以以上配置打包后会,默认在当前目录的dist目录中生成两个出口文件,分别为app.jslogin.js

# 2.4、入口 entry 值是函数

TIP

  • entry 的值是一个函数,则 Webpack 的入口entry的值最终为这个函数的返回值。
  • 函数的返回值可以是字符串、数组、对象

当我们在设置 entry 的值时,如果需要处理一些额外的逻辑,就可以把entry值设为函数

基本用法

const path = require("path");
module.exports = {
  // 函数的返回值作为 entry的值
  entry() {
    return {
      app: "./src/index.js",
      login: "./src/login.js",
    };
  },
};

以上写法等同于以下写法

const path = require("path");
module.exports = {
  entry: {
    app: "./src/index.js",
    login: "./src/login.js",
  },
};

关于 entry 入口是描述符形式,相对有些复杂,入门阶段暂时不需要了解

# 3、Webpack 入口总结

TIP

Webpack 的入口,表示 Webpack 从那个 JS 文件开始打包。Webpack 打包时,通过contextentry这两个参数的值找到需要打包的入口文件。

  • context是一个基础目录,他的值是一个字符串,表示一个绝对路径(默认值为前工程目录的绝对路径),表示资源入口 entry 是以那个目录为起点的。
  • entry是一个相对路径,它的值与context的值拼接起来,就是Webpack打包的入口文件了。

单入口与多入口

Webpack 的的打包入口,可以是单一入口,也可以是多入口,可以通过 entry 的值来设置。

entry 的值的不同形式

entry 的值类型 描述
字符串 表示单入口打包,打包后,默认生成main.js文件,保存在当前目录的dist目录
数组 表示单入口打包,数组的最后一个文件为打包的入口文件,在打包前会先把数组的其余文件预先加载到入口文件中
打包后,默认生成main.js文件,保存在当前目录的dist目录
对象 如果是多入口打包,可以将值设为对象类型。对象的每个属性对应一个入口。
打包后,默认情况下,对象的属性名会是出口文件名,最后这些出口文件统一保存在当前目录的dist目录
函数 如果入口文件的设置涉及到一些逻辑处理,可以把值设为函数。
函数的返回值可以是字符串、数组、对象中的任意一种形式。最后打包的结果与上面三种形式单独写的效果是致的。

# 三、Webpack 的出口(output)配置

TIP

前面我们在学习 Webpack 的入口配置时,并没有设置对应的出口配置,而是采用的默认值。接下来我们就来深入学习 Webpack 的出口(output)配置。

我们以下面这个 Webpack 配置文件来展开讲解。

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  mode: "none",
};

注:

以上配置文件告诉 Webpack,以当前目录下的src/index.js作为打包的入口文件,打包后,生成bundle.js文件,并将该文件保存在当前目录下的dist目录中

以上配置文件中的output就是资源的出口配置项。output是一个对象,他身上有以下几个重要的属性,也是我们接下来学习的重心。

output 对象的属性

属性 描述
path 资源打包后的输出位置
filename 表示打包后生成的出口文件名,可以是一个文件名,也可以是一个相对地址
publicPath 表示打包后生成的资源的访问路径
chunkFilename 用来设置入口文件打包过程中生成的其它的模块名

注:

output 对象身上的更多属性,参考官方文档 (opens new window)

# 1、output.path

TIP

output.path:表示资源打包后的输出位置,该位置需要一个绝对路径

如果不设置,默认值为当前目录下的dist目录。

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    // 以下值相当于是path的默认值,如果输出地址与默认地址一样,也可以省略不写
    // path:path.resolve(__dirname,"dist"),
    filename: "bundle.js",
  },
};

# 2、output.filename

TIP

  • output.filename:表示打包后生成的出口文件名。
  • filename的值可以是一个文件名,如:bundle.js,也可以是一个相对地址,如:"./js/bundle.js"
  • 这些出口文件最终会被保存到output.path选项指定的目录下。
const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    // filename:"./js/bundle.js"
  },
};

配置解析

以是配置表示以当前目录下的./src/index.js文件作为打包入口,生成bundle.js文件,保存在当前目录的dist目录下

  • 如果针对的是多入口打包,我们需要为每一个出口文件设置一个唯一的名称,可以使用以下写法
const path = require("path");
module.exports = {
  entry: {
    app: "./src/index.js",
    login: "./src/login.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
    // 也支持以下写法
    //  filename:"[name]/index.js"
  },
};

配置解析

  • 上面配置项filename:"[name].js" 中的方括号[]代表占位符,里面的name表示特定的动态值,与entry对象中的属性名一一对应,用来表示打包生成后出口的文件名。
  • 上面配置表示有两个打包入口,打包后,对应生成两个出口文件,分别为app.jslogin.js,最终这两个文件会保存在当前目录的dist目录下。
  • 如果采用的是单入口打包,那filename:"[name].js" 中的 name 的值是main
const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
};

配置解析:

以上配置表示以当前目录下的./src/index.js文件作为打包入口文件,打包后生成main.js文件,保存在当前目录的dist目录下。

温馨提示

上面 Wepack 配置中,filename值中的方括号[]中的特定动态值除了name,还有id、hash、fullhash、chunkhash、contenthash,分别表示不同的动态值,他们有什么区别,后面我们单独来介绍

# 3、output.publicPath

TIP

output.publicPath表示的是资源的访问路径。

注意区分:output.path 与 output.publicPath

output.path表示的打包后的资源输出位置,也就是资源存放在磁盘中的位置。资源存放在磁盘后,如果我们想要通过浏览器来访问该资源,这时候就需要指定该资源的访问路径,这个访问路径就是用output.publicPath来指定。

在实际的开发中,我们通常需要设置output.publicPath的值。他的值可以是函数与字符串两种形式,通常我们使用字符串形式的值,所以我们这里也只讲字符串类型的值。

output.publicPath可以设置成以下几种类型的值

具体参考官方文档 (opens new window)

说明
"auto" 默认值,表示资源调用者与被调用者在同一目录下
相对 URL 地址(relative URL) 对于当前浏览的 HTML 页面解析
相对于服务器 URL(Server-relative URL) 值以“/"开头,表示访问的资源以当前页面的服务器地址根目录作为基础路径。
绝对 URL(absolute URL) 值以"http"协议开始,代表一个 HTTP 协议的绝对地址
相对于协义的 URL(protocol-relative URL) 相对 HTTP 协议地址,以 // 开头。

接下来,我们按不同的值来分别解释说明。

# 3.1 publicPath 值为"auto“

TIP

output.publicPath 的默认值为"auto",表示资源调用者与被调用者在同一目录下。

案例演示

根据以下截图,来创建项目(以下为项目根目录文件夹中文件)

image-20230321212329440

  • 在当前根目录下,执行npm init -y初始化 package.json 文件
  • 在当前根目录下,执行npm i webpack@5.76.2 webpack-cli@5.0.1 -D 安装 webpack
  • src文件夹,中有a.jsindex.js两个文件,文件内容如下:
   "webpack": "^5.76.2",
    "webpack-cli": "^5.0.1"
// a.js
console.log("a.js中内容")

// index.js
import ('./a.js');
console.log("index.js")
  • webpack.config.js配置文件,内容如下
const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
};

// 以 src/index.js 为打包入口文件,打包后生成bundle.js文件,文件保存在当前目录的dist目录下,当前没有dist目录,在打包后会自动创建。
  • index.html文件内容如下
<body>
  <!-- 导入打包后dist目录下的bundle.js文件-->
  <script src="./dist/bundle.js"></script>
</body>

最后在当前根目录下,执行npx webpack命令完成打包。打包后的项目结果如下。

image-20230321213647853

分析打包后结果

  • 打包后,在根目的dist目录中多了两个 JS 文件:bundle.js85.bundle.js
  • bundle.js 是入口"./src/index.js"文件打包后生成的output.filename指定的输出文件。
  • 85.bundle.js./src/index.js文件中通过import ('./a.js')加载的异步资源文件,被单独打包成了85.bundle.js文件。

最后,我们在通过 VSCode 在浏览器中打开index.html文件,在控制台输出以下结果

image-20230321214352846

我们切换到控制台的NetWork选项,如下图

image-20230321214922224

注:

可以看到bundle.js85.bundle.js在同一访问目录下。这是因为output:publicPath的默认值是”auto“,Webpack 自行决定其访问路径,默认(资源调用者与被调用者在同一目录下)。

index.html页面加bundle.js,在解析bundle.js时,发起请求加载85.bundle.js文件。这里资源调用者是bundle.js,被调用者是85.bundle.js

# 3.2、publicPath 值为"相对 URL"

TIP

output.publicPath的值以"./"、”./js"、“../"等开头,表示的是相对 URL,其相对于当前浏览的 HTML 页面解析

接下来,我们通过案例来演示publicPath值为相对 URL(relative URL)时的情况

代码演示

在上面案例的基础上,我们把配置文件中设置publicPath的值改为./js/,具体如下

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    publicPath: "./js/",
  },
};

重新执行npx webpack打包,发现项目的目录和刚才的一样,并没有任何改变,不过这一次,我们在浏览器中打开index.html文件时,控制台抛出了 404 错误,具体如下图:

image-20230321220753006

我们切换到控制台的NetWork选项,如下图

image-20230321231651340

注:

观察上图,index.html 页面的地址为http://127.0.0.1:5500/index.html,而85.bundle.js文件的访问地址是相对于 index.html 页面,所以其访问地址为http://127.0.0.1:5500/js/85.bundle.js因为我们项目中没有js这个文件夹,所以找不到对应的文件。

# 3.3、相对于服务器 URL(Server-relative URL)

TIP

output.publicPath的值以“/"开头,表示访问的资源以当前页面的服务器地址根目录作为基础路径。

代码演示

在上面案例的基础上,把output.publicPath的值,改成"/"开头,代码如下:

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    publicPath: "/",
  },
};

重新执行npx webpack打包,打包后在浏览器中打开index.html页面。发现控制报了 404 错误。

切换到控制台的NetWork选项,如下图

image-20230321231300107

注:

观察上图,index.html页面的访问地址是http://127.0.0.1:5500/index.html85.bundle.js文件地址是相对于 index.html 页面,所以访问地址为http://127.0.0.1:5500/85.bundle.js。因为当前项目根目录下没有``文件,所发抛出 404 错误。

当我们把85.bundle.js文件从dist文件夹下剪切到根目录下时,刷新http://127.0.0.1:5500/index.html页面时,报错消失了,控制台正确输出了内容。

# 3.4、绝对 URL(absolute URL)

TIP

output.publicPath的值是以 HTTP 协议名开始的地址,我们称为绝对 URL。web 中常见的协议名有HTTPHTTPS

如果我们静态资源是分布在两台服务器上,这个时候我们就需要把output.publicPath的值设置为以 HTTP 协议开头的绝对地址。

这样做的目的是加快资源的下载速度,因为浏览器只能为每一个域名维护了 6 个 TCP 连接,也就一次性最多支持 6 个并发请求。

如果资源放在多个服务器上,比如 3 个,那相当于我一次性可以并行发送 18 个请求来获取资源。相当于之前只有一个帮你干活,现在有 3 个人,你说快吗 ?

代码演示

在上面案例的基础上,把output.publicPath的值,改成以 http 协义开头的绝对地址,代码如下:

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    publicPath: "https://web.arryblog.com/",
  },
};

重新执行npx webpack打包,打包后在浏览器中打开index.html页面。发现控制报了 404 错误。

切换到控制台的NetWork选项,如下图

image-20230321230820915

注:

观察上图,我们发现,85.bundle.js文件的访问地址为https://web.arryblog.com/85.bundle.js。因为服务器上没有这个文件,所以抛出 404 错误。接下来我把打包好的85.bundle.js文件上传到服务器根目录下,重新刷新页面,报错消失了,资源被正常加载了。

# 3.5、相对于协议的 URL(protocol-relative URL)

TIP

在 web 端相对于协议的 URL,通常指的是相对于HTTP协议地址以 // 开头的地址。与绝对的 HTTP 协议地址相比,它省略了前面的https:http:

通常情况下,我们的网站可以同时以 http 或 https 两种协议来访问。在使用相对于协议的 URL 时,浏览器会将当前页面使用的协议名称与相对协议地址拼接得到完整的地址。得到的完整地址就是httphttps开头的绝对地址。

代码演示

在上面案例的基础上,把output.publicPath的值,改成相对于协议的 URL 地址,代码如下:

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    publicPath: "//web.arryblog.com/",
  },
};

重新执行npx webpack打包,打包后在浏览器中打开index.html页面,控制台能正常输出结果。

切换到控制台的NetWork选项,如下图

image-20230321232834843

注:

观察上图,我们发现85.bundle.js文件的访问地址前面自动中上与index.html页面相同的http协议名。

# 4、output.publicPath 配置总结

TIP

output.publicPath可以设置成以下 5 种类型的值

// 默认值 auto,可以省略,表示调用者与被调用者在同一目录下
publicPath: "auto";

// 相对URL,相对于当前浏览器访问的HTML页面
publicPath: "./js";

// 相对服务器URL,相当于当前浏览器访问的HTML页面所在的服务器地址的根目录
publicPath: "/dist";

// 绝对URL
publicPath: "http://web.arryblog.com/";

// 相对于协议的URL浏览器会自动将访问的HTML页面的协议添加到以下地址前,拼接成一个完整的绝对地址
publicPath: "//web.arryblog.com/";

# 5、output.chunkFilename

TIP

  • output.chunkFilename是用来表示打包后生成的文件名,不过他与filename是有区别的。
  • filename表示入口文件打包后生成的对应的出口文件的文件名,而output.chunkFilename表示的是入口文件打包过程中生成的其它的模块名。

比如前面例子中,index.js文件里有import('./a.js'),其中a.js就被单独打包成了85.bundle.js,这个名称就是默认的output.chunkFilename

ouput.chunkFilename的值也支持占位符,可以设置成如下:

output.chunkFilename = "[id].js";
// [] 表示占位符 id为随机生成的数字

代码演示

在上面案例的基础上,在 output 对象上添加chunkFilename属性,具体代码如下:

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    publicPath: "auto",
    chunkFilename: "[id].js",
  },
};

注:

在当前目录下执行npx webpack 打包后,在当前目录的dist文件夹下会生成bundle.js85.js

其中85.js就是index.js中通过import ('./a.js')引入的a.js文件被单独打包后形成的。他的名称就是通过chunkFilename属性来设置的。

# 6、has、fullhash、chunkhash、contenthash 的区别

TIP

在前面学习output.filename属性的配置和刚学习的output.chunkFilename属性配置时,我们提到了下面这种写法

output.filename = "[name].js";
chunkFilename: "[id].js";

注:

我们知道[]方括号表示一个占位符,nameid表示一个特定的动态值。

其实,占位符中还有hash、fullhash、chunkhash、contenthash值,这些值都是根据文件内生成的唯一的 hash 值。

这些值主要与浏览器的缓存行为有关。所以我们先来了解下浏览器的缓存

# 6.1、浏览器缓存

TIP

当浏览器访问一个 HTML 页面时,HTML 页面会加载 JS、CSS 和图片等外部资源,这需要花费一定的加载时间。

如果页面上有些外部资源长时间并没有什么变化,比如:Logo 图片,CSS 文件。那我们就可以在发送第一次请求后,把这部分资源存储在本的磁盘上,这就是缓存。

下次我们再打开这个网页时,就不需要再发送请求到服务端获取这部分资源,而是可以直接在本地的磁盘取回缓存的 Logo 图片和 CSS 文件。这样大大的提高了网站资源的加载速度。

浏览器如何知道该资源是从本地磁盘中取,还是从网络服务器请求下载呢 ?

这需要服务端在响应头中添加Cache-Control字段来设置。

# 6.2、Cache-Control 响应头

TIP

Cache-Control的常用值有以下几个,用来设置资源的可缓存性

属性值 说明
public 资源进过的任意方(如:客户端、代理服务器)都可以缓存。
private 可省略,只有发起请求的浏览器(用户端)可以缓存
no-cache 可以缓存,但每次使用前必需要发请求到服务端验证是否可能
no-store 不缓存请求和响应的任何内容
max-age 整数,单位是毫秒,表示缓存的过期时间

接下来我们通过搭建node服务,来给大家演示下缓存

代码演示

  • 新建icoding文件夹,在当前文件夹下有两个文件,分别为index.htmlserver.js
  • index.html文件夹内容如下,在访问 index.html 页面时,会发起请求加载 JS 文件
<body>
  index.html页面内容
  <script src="http://127.0.0.1:8887/js"></script>
</body>

server.js文件内容如下

const http = require("http");
const fs = require("fs");
http
  .createServer((request, response) => {
    const url = request.url;
    if (url === "/") {
      // 返回html页面
      const html = fs.readFileSync("./index.html");
      response.writeHead(200, {
        "Content-Type": "text/html;charset=utf-8",
      });
      response.end(html);
    } else if (url === "/js") {
      // 返回js代码
      response.writeHead(200, {
        "Content-Type": "text/html;charset=utf-8",
        // ---------------------------
        "Cache-Control": "no-cache",
        // ---------------------------
      });
      response.end('console.log("hello world")');
    }
  })
  .listen(8887);

console.log("Server running at http://127.0.0.1:8887");

注:

server.js代码,用于创建两个服务,分别为http://127.0.0.1:8887/http://127.0.0.1:8887/js

当访问http://127.0.0.1:8887/地址时,返回index.html页面,并在这个页面中发起请求加载http://127.0.0.1:8887/js下的 js 内容。

接下来,我们可以通过修改上面代码中的Cache-Control的值,来查看对应的缓存效果。

  • 当没有设置Cache-Control响应头或其值为"no-store",表示不缓存。
  • 当值为"no-cache""no-cache,max-age=200"时,每次刷新页面,都会响服务器发送请求来验证是否能使用缓存中内容,所以每次更新响应的内容,页面都会及时响应。

image-20230322020519061

  • 当值为"public,max-age=200""max-age=200""private,max-age=200"时,页面输出"hello world",然后更新响应的内容为”hello world 123",重启服务器,不管如何刷新页面,控制台始终输出"hello world"

image-20230322021028587

# 6.3、缓存未过期,如何更新内容

TIP

通过上面的学习我们知道,使用缓存的优势很明显。当用户再访问页面时,不用再向服务器发请求获取内容,而是直接从本地电脑的磁盘中读取内容,极大的提高了网站的加载速度。

但问题也就随之而来,我们可以把缓存的过期时间设置为几天几个月甚至几年,使该资源能长时间缓存在本地磁盘中。但是,如果我们的资源内容变化了,不想从本地缓存中读取该资源了,那我们又该如何办呢 ?

唯一的办法就是给文件重新取一个名字,这样当我们再次访问该资源的时候,因为文件的名字变了,浏览器的缓存中没有缓存该名字的文件,则就会向服务器发请求来重新获取。

所以缓存文件中的内容一但发生变化,我们就需要重新给文件取一个名字,那我们如何保证每次变动后产生的名字是唯一的,不和之前的发生重名呢?这就引出了我们要讲的 hash 知识。

# 6.4、Webpack 与 hash 算法

TIP

在使用 Webpack 的时候,Webpack 会根据所有文件内容计算出一个特殊的字符串。只要文件的内容有变化,Webpack 就会计算出一个新的特殊字符串,并保存是唯 一的。

Webpack 根据文件内容计算出特殊字符串的时候,使用的算法就是 hash 算法,这个特殊的字符串一般叫作 hash 值。

我们一般取计算出的特殊字符串的前 8 位作为文件名的一部分,因为 hash 算法计算的这个特殊字符串的前 8 位基本可以保证唯一性了。所以 filename 的值常设置为[name][fullhash:8].js

接下来我们就来回答我们开头提到的那个问题?

# 6.5、Webpack 中 hash、fullhash、chunkhash、contenthash 的区别

TIP

Webpack 通过对文件内容进行 hash 计算来获得 hash 值,那 hash、fullhash、chunkhash、contenthash 有什么区别 ?

见下表

hash 值 说明
hash 根据打包中所有文件计算出的 hash 值。
fullhash webpack5 提出的,它用来替代之前的 hash
chunkhash 根据打包的当前模块(JS)计算出来的 hash 值。
contenthash 他与 chunkhash 很像,不过他主要用于计算 CSS 文件的 hash 值

代码演示

我们还是用之前的案例代码,把webpack.config.js配置文件的内容更改为如下:

const path = require("path");
module.exports = {
  entry: {
    app: "./src/index.js",
    login: "./src/login.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name][fullhash:8].js",
    chunkFilename: "[chunkhash:8].js",
  },
};

在当前目录执行npx webpack打包后,在dist目录下生成以下文件

image-20230322164920972

观察以上图,我们发现

  • app3152888d.jslogin3152888d.js的 hash 值是一样的,都是3152888d。因为fullhash的值是通过所有打包文件计算也来的,所以是一样的
  • 5aa23035.jsf2229a8d.js的 hash 值是不一样的,因为chunkhash的值是通过当前打包的 JS 文件内容计算出来的。 5aa23035.jsb.js打包后生成的,f2229a8d.jsa.js文件打包后生成的,两者的内容不一样,所以计算得出的 hash 值也不一样。
上次更新时间: 6/8/2023, 9:23:17 PM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X