关于JS模块系统,Webpack做了什么

在实际JS开发工作中,我们通常会以模块化的方式进行开发,所以我们用到了babel-loader, ts-loader, 以及webpack,在代码中,经常会用到ES,CommonJS,AMD等多种模块系统混用,所以有必要知晓打包器为我们做了什么让最终代码能够在浏览器等不支持原有模块系统的环境下正常工作。这里因为AMD有自己特有的模块库不做讨论,主要讨论ESCommonJS模块系统。

CommonJS 多模块Webpack打包

首先写两个模块,以及一个入口文件引入模块 moduleA

app

然后使用webpack进行打包,得到单一文件,对这个文件内的代码进行分析,整体代码是多个IIFE,主要为了模块作用域隔离,其中首先定义了一个自己的模块加载函数:

require

这里webpack实现的模块系统是CommonJS,为了适用浏览器环境,对不支持的module全局变量提供了自有实现,同时对模块加载做了缓存处理,避免重复加载。

webpack实现module这个可能在有些环境不存在的对象之后,CommonJS模块在打包后其实变化不大。
moduleA-packed

ES模块Webpack打包

moduleA-es

app-es

这里全部改用了ES的模块导出和导入方式,其中模块A除了定义默认导出,还有其他额外导出。

打包后的代码,其中模块A部分示例如下:

moduleA-packed

webpack为每个模块定义了导出函数,因为导入模块A的方式是import A from "./moduleA即默认导入,所以同样的这里的模块定义并没有对其他非默认导出进行暴露,而只对默认导出以属性__webpack_exports__.a的方式进行了暴露。如果将导入模块A的方式改成import * as A from "./moduleA",模块A的导出函数定义将变成如下结果:

moduleA-packed2

为了暴露所有导出,webpack将默认导出以__webpack_exports__.default方式暴露,其他则以__webpack_exports__.[exportName]方式暴露。

执行打包后的代码,其结果输出为

{ say: [Function: say], default: 'module A' }
module B  

即调用模块A的默认导出要使用A.default了。

Webpack打包UMD

当设置webpack配置的output.libraryTargetumd时,打包文件又有所区别。但是UMD不能作为CommonJSES模块等的同级,它只是多种模块系统的环境兼容解决方案,会根据具体环境,选择执行某一种模块系统。其中最重要的一部分,即为这个环境监测适配模块系统的函数,webpack中的如下: webpack-umd

其中factory参数即是原来的非UMD打包的整个IIFE函数,最终挂载到合适的模块系统变量之上。