You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// webpack/lib/TemplatedPathPlugin.jsconstreplacePathVariables=(path,data,assetInfo)=>{constchunk=data.chunk;constchunkId=chunk&&chunk.id;constchunkName=chunk&&(chunk.name||chunk.id);constchunkHash=chunk&&(chunk.renderedHash||chunk.hash);constchunkHashWithLength=chunk&&chunk.hashWithLength;constcontentHashType=data.contentHashType;constcontentHash=(chunk&&chunk.contentHash&&chunk.contentHash[contentHashType])||data.contentHash;return(path.replace(REGEXP_HASH,withHashLength(getReplacer(data.hash),data.hashWithLength,assetInfo)).replace(REGEXP_CHUNKHASH,withHashLength(getReplacer(chunkHash),chunkHashWithLength,assetInfo)).replace(REGEXP_CONTENTHASH,withHashLength(getReplacer(contentHash),contentHashWithLength,assetInfo)).replace(REGEXP_MODULEHASH,withHashLength(getReplacer(moduleHash),moduleHashWithLength,assetInfo)).replace(REGEXP_ID,getReplacer(chunkId)).replace(REGEXP_MODULEID,getReplacer(moduleId)).replace(REGEXP_NAME,getReplacer(chunkName)).replace(REGEXP_FILE,getReplacer(data.filename)).replace(REGEXP_FILEBASE,getReplacer(data.basename))// query is optional, it's OK if it's in a path but there's nothing to replace it with.replace(REGEXP_QUERY,getReplacer(data.query,true))// only available in sourceMappingURLComment.replace(REGEXP_URL,getReplacer(data.url)).replace(/\[\\(\\*[\w:]+\\*)\\\]/gi,"[$1]"));};
importReact,{useEffect,useState}from'react'import{reportError}from'../pmm'exportfunctionAsyncCaptcha(props){// state 里不能存放 fn,React 无法区分是否需要调用var_a=useState(),Module=_a[0],setModule=_a[1]useEffect(function(){if(!props.isBApp){import('@xxxx/captcha-prompt-mobile').then(function(r){returnsetModule(r)}).catch(function(e){returnreportError(e)})}},[props.isBApp])if(Module)returnReact.createElement(Module.default)returnnull}
// webpack/lib/buildChunkGraph.jsconstfilterFn=(dep)=>{constdepChunkGroup=dep.chunkGroup// TODO is this needed?if(blocksWithNestedBlocks.has(dep.block))returntrueif(areModulesAvailable(depChunkGroup,resultingAvailableModules)){returnfalse// break all modules are already available}returntrue}// For all deps, check if chunk groups need to be connectedfor(const[chunkGroup,deps]ofchunkDependencies){if(deps.length===0)continue// 1. Get info from chunk group info mapconstinfo=chunkGroupInfoMap.get(chunkGroup)resultingAvailableModules=info.resultingAvailableModules// 2. Foreach edgefor(leti=0;i<deps.length;i++){constdep=deps[i]// Filter inline, rather than creating a new array from `.filter()`// TODO check if inlining filterFn makes sense hereif(!filterFn(dep)){continue}constdepChunkGroup=dep.chunkGroupconstdepBlock=dep.block// 5. Connect block with chunkGraphHelpers.connectDependenciesBlockAndChunkGroup(depBlock,depChunkGroup)// 6. Connect chunk with parentGraphHelpers.connectChunkGroupParentAndChild(chunkGroup,depChunkGroup)}}
// webpack/lib/buildChunkGraph.jsconstfilterFn=(dep)=>{constdepChunkGroup=dep.chunkGroup// TODO is this needed?if(blocksWithNestedBlocks.has(dep.block))returntrueif(areModulesAvailable(depChunkGroup,resultingAvailableModules)){returnfalse// break all modules are already available}returntrue}// For all deps, check if chunk groups need to be connectedfor(const[chunkGroup,deps]ofchunkDependencies){if(deps.length===0)continue// 1. Get info from chunk group info mapconstinfo=chunkGroupInfoMap.get(chunkGroup)resultingAvailableModules=info.resultingAvailableModules// 2. Foreach edgefor(leti=0;i<deps.length;i++){constdep=deps[i]// Filter inline, rather than creating a new array from `.filter()`// TODO check if inlining filterFn makes sense hereif(!filterFn(dep)){continue}constdepChunkGroup=dep.chunkGroupconstdepBlock=dep.block// 5. Connect block with chunkGraphHelpers.connectDependenciesBlockAndChunkGroup(depBlock,depChunkGroup)// 6. Connect chunk with parentGraphHelpers.connectChunkGroupParentAndChild(chunkGroup,depChunkGroup)}}
问题背景
b 同学反馈他负责的项目在测试环境有两个 js 文件 404 了, 一看文件名又很诡异, 即下图的
xx.undefined.chunk.xx.js
查看 webpackConfig 中配置的文件名的生成规则 chunkFilename 字段, 知道了原本应该是长度为 8 的 chunkhash 字符串变成了 undefined 导致了本次问题
问题排查
可能性1: 生成 chunkhash 的算法出了问题, 返回了 undefined
首先我们找到把 [chunkhash:8] 这个占位符替换为计算后真实值的代码处, 接着拦截一下该函数运算的所有结果, 发现没有一个文件名字符串中包含 undefined, 故排除了该可能性
其他可能性
本地复现后从错误堆栈发现是如下打包后的代码
__webpack_require__.e(10)
调用后产生的错误__webpack_require__.e
函数是 webpack 对于动态 import 函数的实现, 如上对应的打包前的代码是接着我们查看
__webpack_require__.e
函数的实现, 知道了传入的参数 10 与 53 是 chunkId, 但是不存在下面这个对象的 key 中, 所以{ 55: 'aec66236', 57: '833f5543', 59: '6b297fa3', 62: 'd7940dbd' }[chunkId]
取值是 undefined, 这就解释了为什么文件名中出现了 undefined, 原来完整的文件名是在这里拼接而成下面我们看看
{ 55: 'aec66236', 57: '833f5543', 59: '6b297fa3', 62: 'd7940dbd' }
对象的生成逻辑, 为什么漏掉了 10 这个 key?从代码可以看出 55, 57, 59, 62 这些 key 值其实是
this.getAllAsyncChunks()
函数返回的 chunk 的 id, 从函数名getAllAsyncChunks
可以知道返回值是该页面所有动态加载的 AsyncChunk, 而 chunk.id 为 10 对应的'@xxxx/captcha-prompt-mobile'
模块确实也是动态加载的, 那么是什么原因 webpack 漏掉了import('@xxxx/captcha-prompt-mobile')
继续阅读代码后发现
import('@xxxx/captcha-prompt-mobile')
是没有走到第 6 步与父 chunkGroup 连接起来 (文件之间的引用关系可以用数据结构 Graph 来描叙, 没有连接则表示后续父 chunkGroup 通过 Graph 查询不到与之的依赖关系), 所以 getAllAsyncChunks 函数不包含该 chunk。原因是被如下的 filterFn 函数给过滤了, 被过滤的原因是import('@xxxx/captcha-prompt-mobile')
对应的模块出现在了resultingAvailableModules 中, 继续查看依赖关系发现是有一个 npm 包同步import '@xxxx/captcha-prompt-mobile'
, 原来一切是因为还同时存在一个同步的 import 导致 webpack 认为它不是 AsyncChunk按理来说同步 import 与动态 import 引用这两者都引用同一个文件虽然少见, 但也不应该造成 bug 。难道是该项目是多页应用还有其他未知的秘密?于是我们删除了其他页面, 仅留当前页面进行测试
最后发现只留下一个页面是正常的, 与多个页面打包后的代码不同的是
__webpack_require__.e(10)
变成了Promise.resolve(/* import() */)
于是查看这块的 blockPromise 函数实现发现是第一个 if 条件为 true, 所以返回的是
Promise.resolve(/* import() */)
。 此时 block.chunkGroup 为 undefined, 这也对应了上面说的是被 filterFn 函数过滤了, 所以没有走到为 block.chunkGroup 赋值的代码处接下来我们需要排查为啥多页时返回的是
__webpack_require__.e
, 按照上面的分析多页的 block.chunkGroup 应该也为 undefined 才对。再次回到 buildChunkGraph 文件代码, 发现是import('@xxxx/captcha-prompt-mobile')
对应的模块被另一个页面仅动态 import 引用了, 所以如下的 for 循环有一次走到了第 6 步连接上了这个页面对应的 chunkGroup, 而第 5 就给该 block 设置了 chunkGroup, 即多页时 block.chunkGroup 是存在的, 所以上面的 blockPromise 函数返回的是__webpack_require__.e
问题解决
把当前 webpack 版本 v4.39.0 升级到小版本最新的 v4.46.0 发现问题已经不存在, 于是查看 Release 日志找到是 v4.44.2 版本进行的修复
The text was updated successfully, but these errors were encountered: