写在前面
最近在搞一个通过配置生成页面的项目,遇到了这样一个问题
for (let i = 0, len = pageData.selectSection.length; i < len; i++) { const sectionName = pageData.selectSection[i].section_name; import(`./template/${pageData.template_name}/${sectionName}/index`).then(item => { const module = item.default; ... }); }
我需要读取后台下发的配置文件,然后去动态import对应的组件。最终使用webpack打包后,每一个组件都会产出一个chunk。那么问题来了,我的组件引用路径是在运行时才能确定的,webpack是怎么在本地静态打包的时候,就找到我的组件的呢?
webpack的动态引用
查看webpack的文档,有以下的描述:
完全动态的语句(如import(foo)
),因为 webpack 至少需要一些文件的路径信息,而foo
可能是系统或项目中任何文件的任何路径,因此foo
将会解析失败。import()
必须至少包含模块位于何处的路径信息,所以打包应当限制在一个指定目录或一组文件中。
调用import()
时,包含在其中的动态表达式 request,会潜在的请求的每个模块。例如,import(
./locale/${language}.json)
会导致./locale
目录下的每个.json
文件,都被打包到新的 chunk 中。在运行时,当计算出变量language
时,任何文件(如english.json
或german.json
)都可能会被用到。
从文档可以看出,动态import,是会根据代码里面的给出的路径信息去查找文件的,并且会将所有满足这个路径信息的文件都打成一个chunk,以备后续使用。
对于运行时才能确定的路径,webpack到底是怎么处理的呢?
以上是在webpack打包过程中的一个对象。
可以看到,webpack其实是把
./template/${pageData.template_name}/${sectionName}/index
这个路径中的变量直接全部忽略了,直接使用了一个正则/^\.\/.*\/index$/
和request
字段来表示最终的资源路径。
这个正则是怎么生成的呢?
在webpack源码的ContextDependencyHelpers.js
里面,有下面的代码:
// ContextDependencyHelpers.js ...let prefixRaw = param.prefix && param.prefix.isString() ? param.prefix.string : "";let postfixRaw = param.postfix && param.postfix.isString() ? param.postfix.string : "";const valueRange = param.range;const { context, prefix } = splitContextFromPrefix(prefixRaw);const { postfix, query } = splitQueryFromPostfix(postfixRaw); // 重点关注这行代码。拿到ast分析后的prefix和postfix,中间加上一个字符串,生成一个正则const regExp = new RegExp( `^${quotemeta(prefix)}${options.wrappedContextRegExp.source}${quotemeta( postfix )}$`); ...
其中,wrappedContextRegExp
这个字段的值,默认是被webpack写死的,就是一个正则表达式:
// WebpackOptionDefaulter.js this.set("module.wrappedContextRegExp", /.*/);
webpack通过loader(babel)拿到js代码的AST后,会对AST进行分析,获取到动态import的路径参数后,进行上述处理,并且把本地满足上述正则的路径里面的文件全都拆出来,生成chunk。所以,为了避免打出无用的chunk,在使用动态import的时候,尽可能使用准确的静态路径,减少变量的影响范围。一般可以像下面这样使用:
import('./components/'+ ComponentName).then(module => { ... })
这里需要注意,在动态import的时候,路径不能只使用一个变量表示,这样的话,webpack没有办法找到对应的文件。
// 🚫下面代码是无效的const a = 'temp';import(a).then(module => { console.log(module.default);}); // Critical dependency: the request of a dependency is an expression
写在后面
本文对webpack的动态import进行了一定的研究,对于webpack的动态import的策略有了进一步的认识,也为以后的使用扫除了一些坑,符合预期。