Webpack核心原理

Webpack

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。

webpack 的作用是将源代码编译(构建、打包)成最终代码

简单理解,就是把一堆玩具零件组装成一个完整的玩具。 Webpack又是如何把这一堆零件组成一个完整的东西呢?

一、Webpack打包内容分析

Webpack会帮助我们把一堆js文件进行打包,那打包的结果是什么呢?它与我们写的js文件有什么不同?

  • Webpack打包的结果肯定是js文件,这是不容置疑的。

Webpack打包后的js文件与我们写的js文件有什么不同

我们写的js文件如下

js 复制代码
/* index.js */
const a = require('./a.js');
const b = require('./b.js');
console.log(a+b)
    
/* a.js */
module.exports = 20;

/* b.js */
module.exports = 7;

执行webpack命令后,打包结果如下:(简化了Webpack的打包结果)

js 复制代码
(function (modules) {
  var installedModules = {};
  function require(moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = installedModules[moduleId] = {
      exports: {}
    };
    modules[moduleId].call(module.exports, module, module.exports, require);
    return module.exports;
  }
  return require("./src/index.js");
})
  ({
    "./src/a.js":
      (function (module, exports) {
        module.exports = 20;
      }),
    "./src/b.js":
      (function (module, exports) {
        module.exports = 7;
      }),
    "./src/index.js":
      (function (module, exports, require) {
        const a = require('./src/a.js');
        const b = require('./src/b.js');
        console.log(a + b)
      })
  });

我们发现webpack打包后的结果本质是一个立即执行函数 ,该函数接收一个对象作为实参,使用modules形参接收。

下面我们将逐步解析这个立即执行函数做了什么

1.先看modules形参里有什么?

js 复制代码
{ 
    "./src/a.js": (function (module, exports) { 
        module.exports = 20; 
        }), 
    "./src/b.js": (function (module, exports) {
        module.exports = 7;
        }),
    "./src/index.js": (function (module, exports, require) { 
        const a = require('./src/a.js'); 
        const b = require('./src/b.js'); 
        console.log(a + b) 
        }) 
}

这是一个对象,该对象的键为我们写的文件路径,值为一个函数,该函数内容为我们写的js代码(在Webpack中,我们写的js代码为做为字符串放在eval()函数中执行,这里方便理解,放在函数体中)

2.然后我们看这一段代码,它把require函数传入一个"./src/index.js"实参调用后的结果作为返回值

js 复制代码
15 return require("./src/index.js"); 

3.再看require函数里面做了什么事

webpack打包结果中,require函数为__webpack__require 在这里为了简写

js 复制代码
    // 7-11行 
    var module = installedModules[moduleId] = {
      exports: {}
    };
    // modules[moduleId]: 获取到对应模块的函数,首先是获取到的"./src/index.js"对应的函数
    // 然后使用call来改变this指向,并且传入三个参数
    modules[moduleId].call(module.exports, module, module.exports, require);
    return module.exports;
    

以上代码就是把该模块的this指向module.exports这个对象,module和module.exports作为模块的导出,把require这个函数作为模块的导入,最后把这个模块的导出结果作为返回值

  1. 如果我们导入了多个相同的模块 ,该模块的代码是不是要执行多次 ?所以使用了installedModules这个变量来保存模块的导出结果。
js 复制代码
    // 2行
    var installedModules = {};
    ...
    // 4-6行
    // 判断该模块是否缓存了导出结果
    if (installedModules[moduleId]) {
        return installedModules[moduleId].exports;
    }
    // 7-11行 
    // 定义该模块的导出
    var module = installedModules[moduleId] = {
      exports: {}
    };
    

以上代码就完成了对模块的导出内容的缓存。

  • 当没有缓存时,就执行该模块代码,然后缓存。
  • 当有缓存时,直接取出缓存并返回,无需执行该模块代码。

总结: Webpack打包后的本质就是一个立即执行函数,把我们需要的模块转换为key:value的键值对保存在一个对象里,然后作为参数传入到立即执行函数。Webpack写了一个require函数和module对象用来模拟CommonJS模块化完成功能的实现。

二、Webpack是如何工作的?

目录

  1. 初始化
  2. 编译
  3. 打包

1.初始化

初始化就是把命令行参数和webpack.config.js文件内容的配置进行合并,命令行参数会覆盖配置文件参数。

2.编译

Webpack在编译的过程中其实就做了一件事,就是把依赖收集,形成一个moudles参数

  • 依赖收集流程图

Webpack会根据一个入口文件index.js进行依赖收集,首先,根据文件ID(文件ID就是文件路径)检查这个模块是否被收集,如果被收集就不做任何处理,没有被收集就读取文件内容,进行抽象语法树分析,检查该模块依赖了哪些其他模块,把这些依赖模块存放到数组里,然后把require替换为__webpack__require,再把这个模块就添加到chunk里面。最后把依赖模块数组的每一项传入到入口文件中进行递归调用。

  • modules(chunk)

把编译好的模块存在在这里面

3.打包

打包阶段其实就很简单了,先生成一个js文件,文件内容为一个立即执行函数,然后把chunk生成一个对象,键为文件ID,值为一个函数,函数体里包裹着该模块代码,最后chunk作为参数传入到立即执行函数里面,这个立即执行函数里面提供了一个变量和一个函数,变量用来存储每个模块的导出结果,函数用来模拟require导入函数。

总结

在Webpack中,每个阶段还有很多事要做,比如编译阶段会对每个模块生成一个hash,然后对总的chunk也生成一个hash,我只是大致描述了一下Webpack的核心

三、关于loader和plugin

复制代码
可能有人会问Webpack中最强大的loader和plugin在这里为什么没有提?
loader和plugin这些东西并不是Webpack的核心,它们只是一些工具和插件,没有它们Webpack照样能够运行,有了它们会增强Webpack某一方面的功能。
比如六味地黄丸,只是对某一方面增强,并不会影响本身。

完结

我是一个小菜,希望我们大家能够友好沟通,一起完善对Webpack知识点的掌握。

也希望大佬们能够指出该文章的缺点和错误,还有您宝贵的意见,希望我们能够一起进步!

谢谢大家!

相关推荐
guzicheng19902 天前
webpack 安装笔记:全局 vs 本地,以及一个 PATH 环境变量的坑
前端·webpack
liangshanbo121511 天前
面试题:Webpack 的 publicPath 有什么作用?
前端·webpack·node.js
liangshanbo121513 天前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
liangshanbo121514 天前
面试题:AI 对话上下文超限怎么办?
面试·webpack·devops
liangshanbo121514 天前
Webpack 文件指纹面试题整理
前端·webpack·node.js
liangshanbo121514 天前
Webpack 常见 Plugin 面试题
前端·webpack·node.js
liangshanbo121514 天前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
陪我一起学编程14 天前
vue3笔记
前端·webpack·vue3·vite·脚手架·vue3笔记·组件式
liangshanbo121515 天前
Webpack的分包策略面试题
前端·webpack·node.js
liangshanbo121516 天前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js