背景:开发webpack项目时,发现动态import导入的模块没有分包,这和webpack官方介绍的“动态导入实现动态代码分离”不一致,在排查该问题的过程中学习到了webpack/babel处理模块的方式,从而有了这篇文章。
babel是如何处理模块的
vue-cli的babel预设@vue/cli/plugin-babel/preset
背景项目是一个vue-cli项目,根目录下的babel.config.js
文件中配置的babel的presets(预设)是@vue/cli-plugin-babel/preset
。
@vue/cli/plugin-babel/preset中包含了@babel/preset-env。
babel的预设@babel/preset-env
@babel/preset-env是babel官方提供的preset(预设)
测试使用的版本是v7.25.3
@babel/core版本是v7.25.2
core-js版本是v3.37.1
预设@babel/preset-env的modules参数
modules的可选值"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
, 默认是 "auto"
.
modules用于配置将ES模块语法转化为另一种模块类型。
-
设置为false将保留ES模块
设置为false的时候,babel会保留import()语句,不转换。
babel会使用@babel/plugin-syntax-dynamic-import插件处理。
以下是babel给出的一个提示:
/repl.js: @babel/plugin-transform-dynamic-import depends on a modules transform plugin. Supported plugins are: - @babel/plugin-transform-modules-commonjs ^7.4.0 - @babel/plugin-transform-modules-amd ^7.4.0 - @babel/plugin-transform-modules-systemjs ^7.4.0 If you are using Webpack or Rollup and thus don't want Babel to transpile your imports and exports, you can use the @babel/plugin-syntax-dynamic-import plugin and let your bundler handle dynamic imports.
-
设置为umd
设置umd时,会提示“动态导入只能在将ES模块转换为AMD、CommonJS或SystemJS时转换”
- 设置为commonjs
import("jquery").then($ => {});
Promise.resolve()
.then(() => _interopRequireWildcard(require("jquery")))
.then(($) => {});
- 设置为amd
import("jquery").then($ => {});
define(["require"], function (_require) {
new Promise((_resolve, _reject) =>
_require(
["jquery"],
(imported) => _resolve(_interopRequireWildcard(imported)),
_reject
)
).then(($) => {});
});
插件 @babel/plugin-transform-dynamic-import
- 设置为amd,commonjs,systemjs时,babel会使用@babel/plugin-transform-dynamic-import插件将import语句转换
此插件用于将import()语句转换成非ESM模块的格式。
如果你使用了一个打包工具(bundler),比如Webpack,Rollup或者Parcal,你可以不使用这个插件,而是让打包工具(bundler)去处理import()语句。
此插件配合@babel/plugin-transform-modules-commonjs可以将ESM模块转换成CommonJS模块;
配合@babel/plugin-transform-modules-amd可以将ESM模块转换成AMD模块;
配合@babel/plugin-transform-modules-systemjs可以将ESM模块转换成SystemJs模块。
webpack 转换模块
babel转换后,webpack会将上面各种类型(amd,commonjs,umd,esm,systemjs)的模块都统一转化成webpack的模块。
webpack官网介绍的模块
webpack2支持原生的ES6模块语法,意味着你无需额外引入babel这样的工具,就可以使用import和export。但是注意,如果使用其他的ES6+特性,仍然需要引入babel。webpack支持以下的方法:
Webpack处理ES6的import()
动态的加载模块。调用import的之处,被视为分割点,意思是,被请求的模块和它引用的所有子模块,会分割到一个单独的chunk中。
Webpack处理AMD的require
与require.ensure类似,Webpack将其对应的导入文件拆分到一个单独的bundle中,此bundle会被异步加载。
综上所述:**babel预设的modules设置为false,auto和amd时import()会分包;设置为commonjs不分包。**
Webpack处理各种模块的源码
Webpack源码lib/dependencies目录下有下面这些处理不同模块语句的js文件
Webpack是如何处理异步加载的
Webpack是通过JSONP实现异步加载的;具体实现可以看__webpack_require__.e
Webpack如何转换AMD require和import
源码路径lib/RuntimeTemplate.js下的blockPromise方法:
AMDRequireDependency中调用blockPromise
问题项目动态import未分包的原因
在上面的“问题项目”,将modules设置为false,import()语句还是会被转化为commonjs模块, commonjs模块不支持异步加载,这就导致转换后的模块脚本丢失了动态加载的特征,最终webpack转换后的脚本也没有了动态加载,也就不分包了
经过排查,导致这个的原因是在babel中增加了下面这段配置:
'env': {
'development': {
'plugin': ['dynamic-import-node']
}
}
babel插件@babel-plugin-dynamic-import-node
这个插件用于将import()语句转换成延迟的require()。
Webpack >=2 已经支持了import()转换,Webpack1你才需要使用babel-plugin- dynamic-import-webpack。
上面问题项目用的是Webpack4,所以是不需要加上这个插件的。