LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

浏览器模块加载与 Webpack 打包原理

zhenglin
2026年3月4日 17:9 本文热度 88

一、原生 ESM:浏览器如何加载模块

1.1 三阶段流程

浏览器处理 ESM 严格按照三个阶段顺序执行:

① 构建(Construction)   → 静态分析依赖,下载所有模块,构建模块依赖图

② 实例化(Linking)      → 为所有导出变量分配内存空间(此时无值)

③ 求值(Evaluation)     → 按依赖顺序执行模块代码,填入真实值

关键特性:

  • 遇到 <script type="module"> 等同于 defer,不阻塞 HTML 解析

  • 同一模块只执行一次,多次 import 返回同一实例(幂等性)

  • 模块下载并行,但执行顺序遵循依赖拓扑排序


1.2 Live Binding(动态绑定)

ESM 的 import 不是值拷贝,而是对原始变量内存地址的引用绑定

// counter.js

export let count = 0;

export function increment() { count++; }


// main.js

import { count, increment } from './counter.js';

increment();

console.log(count); // 1,而不是 0

count 始终指向 counter.js 中那块内存,值变化会实时反映。


1.3 浏览器网络层细节

  • 每个模块对应一个独立 HTTP 请求

  • 响应头必须是合法的 JS MIME 类型(application/javascript

  • 跨域模块受 CORS 限制,需服务器配置 Access-Control-Allow-Origin

  • 相同 URL 的模块全局只加载一次(浏览器 Module Map 缓存)


二、静态 vs 动态:import 的两种形态

理解这个区别是理解整个模块系统的核心。

2.1 什么是"编译时"与"运行时"

浏览器没有离线的编译阶段,这里的区分是相对时序

 

2.2 静态 import 语句

// ✅ 合法:路径是字面量,解析 AST 时就能确定

import { foo } from './foo.js'


// ❌ 非法:路径是表达式,解析阶段无法求值

import { foo } from './' + name + '.js'

import 语句在解析 AST 时就被提取,此时代码尚未执行,所以路径必须是静态字符串。这保证了浏览器能在执行任何代码前构建完整的依赖图。


2.3 动态 import() 函数

代码高亮:

// 执行到这一行时,才发起请求

const mod = await import('./foo.js')

// 路径可以是任意表达式

const mod = await import(`./locales/${lang}.js`)

import() 本质是一个运行时函数调用,返回 Promise,适合按需加载、懒加载场景。


2.4 总结对比

一句话:ESM 的依赖关系是静态的,但变量的值是运行时的。


 

三、ESM vs CommonJS

 


四、Webpack:将模块编译为函数

Webpack 在构建时将所有模块编译成函数,自行实现一套模块加载系统,不依赖浏览器原生 ESM。

4.1 核心结构

(function(modules) {


  var installedModules = {}; // 模块缓存


  function __webpack_require__(moduleId) {

    // 命中缓存直接返回,保证每个模块只执行一次

    if (installedModules[moduleId]) {

      return installedModules[moduleId].exports;

    }


    // 创建模块对象并缓存

    var module = installedModules[moduleId] = { exports: {} };


    // 执行模块函数,注入 module、exports、require

    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);


    return module.exports;

  }


  return __webpack_require__('./src/index.js'); // 从入口启动


})({

  './src/index.js': function(module, exports, __webpack_require__) {

    const foo = __webpack_require__('./src/foo.js');

    console.log(foo);

  },

  './src/foo.js': function(module, exports) {

    exports.default = 'hello';

  }

});

4.2 Live Binding 的模拟

Webpack 模拟的是 CJS 语义,导出本质是值拷贝。但为了兼容 ESM 的 Live Binding,Webpack 用 Object.defineProperty 的 getter 做了补丁:

// 你写的 ESM

export let count = 0;


// Webpack 编译后

Object.defineProperty(exports, 'count', {

  get: function() { return count; } // 每次读取都重新取值,模拟引用

});

4.3 缓存机制对比

 

4.4 HMR 的本质

Hot Module Replacement 正是利用了这套缓存机制:

检测到文件变更

    ↓

删除 installedModules 中对应模块的缓存

    ↓

注入新的模块函数

    ↓

重新执行该模块 → 更新界面

不需要刷新页面,只替换变更的那块缓存。


五、Webpack 异步加载(动态 import)

当你写 import() 时,Webpack 会把对应模块拆成独立的 chunk 文件,运行时按需加载。

5.1 核心机制:JSONP

代码高亮:

// 你写的

const mod = await import('./foo.js')


// Webpack 编译后

__webpack_require__.e('chunk-foo')           // 异步加载 chunk

  .then(() => __webpack_require__('./src/foo.js')) // 从缓存同步取模块

5.2 webpack_require.e 的实现

__webpack_require__.e = function(chunkId) {

  // 已加载,直接返回

  if (installedChunks[chunkId] === 0) return Promise.resolve();


  // 加载中,返回同一个 Promise(防止重复请求)

  if (installedChunks[chunkId]) return installedChunks[chunkId][2];


  // 首次加载:创建 Promise + 动态插入 <script>

  var promise = new Promise((resolve, reject) => {

    installedChunks[chunkId] = [resolve, reject];

  });

  installedChunks[chunkId][2] = promise;


  var script = document.createElement('script');

  script.src = chunkId + '.bundle.js';

  document.head.appendChild(script);


  return promise;

};

5.3 chunk 文件结构

// chunk-foo.bundle.js

(self["webpackChunk"] = self["webpackChunk"] || []).push([

  ['chunk-foo'],

  {

    './src/foo.js': function(module, exports) {

      exports.default = 'hello'

    }

  }

]);

主 bundle 中拦截了 webpackChunk.push,chunk 文件执行时自动触发:

self["webpackChunk"].push = function([chunkIds, modules]) {

  Object.assign(__webpack_modules__, modules); // 注册新模块

  chunkIds.forEach(id => {

    installedChunks[id] = 0;    // 标记已加载

    installedChunks[id][0]();   // resolve Promise

  });

};


5.4 完整时序

代码高亮:

import('./foo.js')

    ↓

__webpack_require__.e('chunk-foo')

    ↓

installedChunks 无缓存 → 创建 Promise + 插入 <script> 标签

    ↓

浏览器下载 chunk-foo.bundle.js

    ↓

chunk 执行 → webpackChunk.push() 被拦截

    ↓

模块注册进 __webpack_modules__ → resolve Promise

    ↓

.then(() => __webpack_require__('./src/foo.js'))

    ↓

从 installedModules 缓存同步取出 → 返回模块


5.5 其他细节

  • 防重复请求:同一 chunk 并发多次 import(),共享同一个 Promise

  • 预加载/* webpackPrefetch: true */ 会生成 <link rel="prefetch"> 提前加载资源

  • 错误处理script.onerror 触发时 reject Promise,可被 try/catch 捕获


总结


原生 ESM

  静态分析依赖 → 并行下载 → 分配内存 → 顺序执行

  Live Binding = 真实内存引用

  import()     = 运行时动态请求


Webpack

  编译阶段:所有模块 → 函数 + installedModules 缓存

  同步加载:__webpack_require__ + 缓存命中

  异步加载:动态插入 <script> + JSONP 回调 + Promise

  HMR:     删缓存 → 注入新函数 → 重执行


参考文章:原文链接


该文章在 2026/3/4 17:09:07 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved