Nodejs n.e is not a function 问题记录

发布于 1周前 作者 phonegap100 来自 nodejs/Nestjs

Nodejs n.e is not a function 问题记录

image

🔗 原文链接: https://github.com/xiaoxiaojx/blog/issues/43

问题简述

TypeError: n.e is not a function 

a 同学说我写的 npm 包 @xxfe/pkg 在 x 项目使用时发布到测试环境报了如上的错误, 但是开发环境没有报错。接着我看了一下 node_modules 中这个包的代码, 这个 Promise 都没 await 咋会被 catch 且还真的被捕获打印了错误日志 🤯, 这是什么瞎猫碰见死耗子的操作 ...

// node_modules/[@xxfe](/user/xxfe)/pkg

try { this.aesUtilPromise = import(’…/aes-util’) } catch (e) { console.info(‘123 ========’, e); }

仅看 node_modules 中的代码并未发现明显的错误, 其实我们应该看的是 @xxfe/pkg 打包后的代码

// dist/static/js/xxx.js

try { this.aesUtilPromise=n.e(3) } catch® { console.info(“123 ========”,r) }

打包后的代码就发现了错误的源头 n.e

熟悉 webpack 的同学就知道动态 import 函数打包后会被 webpack_require.e 函数给替换, 其原理就是通过动态创建一个 script 标签来加载一个 js, 如下即函数的代码

/******/  __webpack_require__.e = function requireEnsure(chunkId) {
/******/   var promises = [];
/******/
/******/
/******/   // JSONP chunk loading for javascript
/******/
/******/   var installedChunkData = installedChunks[chunkId];
/******/   if(installedChunkData !== 0) { // 0 means "already installed".
/******/
/******/    // a Promise means "currently loading".
/******/    if(installedChunkData) {
/******/     promises.push(installedChunkData[2]);
/******/    } else {
/******/     // setup Promise in chunk cache
/******/     var promise = new Promise(function(resolve, reject) {
/******/      installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/     });
/******/     promises.push(installedChunkData[2] = promise);
/******/
/******/     // start chunk loading
/******/     var script = document.createElement('script');
/******/     var onScriptComplete;
/******/
/******/     script.charset = 'utf-8';
/******/     script.timeout = 120;
/******/     if (__webpack_require__.nc) {
/******/      script.setAttribute("nonce", __webpack_require__.nc);
/******/     }
/******/     script.src = jsonpScriptSrc(chunkId);
/******/     if (script.src.indexOf(window.location.origin + '/') !== 0) {
/******/      script.crossOrigin = "anonymous";
/******/     }
/******/     // create error before stack unwound to get useful stacktrace later
/******/     var error = new Error();
/******/     onScriptComplete = function (event) {
/******/     // ...
/******/     };
/******/     var timeout = setTimeout(function(){
/******/      onScriptComplete({ type: 'timeout', target: script });
/******/     }, 120000);
/******/     script.onerror = script.onload = onScriptComplete;
/******/     document.head.appendChild(script);
/******/    }
/******/   }
/******/   return Promise.all(promises);
/******/  };

问题排查

那么为什么代码中用到了 import 函数, webpack 却没有注入 webpack_require.e 函数的实现了 ?

此时我们只能看 webpack 的代码实现, 可以发现当 Object.keys(chunkMaps.hash).length 条件为 true 时, 才会注入 ${this.requireFn}.e 函数

// webpack/lib/MainTemplate.js

this.hooks.requireExtensions.tap(“MainTemplate”, (source, chunk, hash) => { const buf = []; const chunkMaps = chunk.getChunkMaps(); // Check if there are non initial chunks which need to be imported using require-ensure if (Object.keys(chunkMaps.hash).length) { buf.push("// This file contains only the entry chunk."); buf.push("// The chunk loading function for additional chunks"); buf.push(${this.requireFn}.e = function requireEnsure(chunkId) {); buf.push(Template.indent(“var promises = [];”)); buf.push( Template.indent( this.hooks.requireEnsure.call("", chunk, hash, “chunkId”) ) ); buf.push(Template.indent(“return Promise.all(promises);”)); buf.push("};"); } // … }

顺着函数调用顺序发现关键是 getAllAsyncChunks 函数返回值 chunks 集合不为空即可

// webpack/lib/Chunk.js

getAllAsyncChunks() { const queue = new Set(); const chunks = new Set();

	const initialChunks = intersect(
		Array.from(this.groupsIterable, g => new Set(g.chunks))
	);

	for (const chunkGroup of this.groupsIterable) {
		for (const child of chunkGroup.childrenIterable) {
			queue.add(child);
		}
	}

	for (const chunkGroup of queue) {
		for (const chunk of chunkGroup.chunks) {
			if (!initialChunks.has(chunk)) {
				chunks.add(chunk);
			}
		}
		for (const child of chunkGroup.childrenIterable) {
			queue.add(child);
		}
	}

	return chunks;

}

chunks 集合只有一处往集合增加数据的逻辑。initialChunks 可以理解为首屏 html 中 script 标签的 js, 通常是 main.js 及其运行前依赖的 js, 比如 main.js 需要依赖 react, react-dom 等 js 的前置运行。

if (!initialChunks.has(chunk)) {
	chunks.add(chunk);
}

因为业务项目 webpackConfig.optimizationa.splitChunks 的配置把 a 同学写的 @xxfe/pkg 包都打入到了 xxfe_vendor 文件中, 而 @xxfe/ scope 下的依赖被业务项目大量使用, 所以无疑是业务项目 main.js 前置依赖的一个 js, 故 xxfe_vendor 在首屏 html 中 script 标签的 js 中

所以 xxfe_vendor 是 initialChunks 中的其中一个, 故此处 if 为 false

xxfe_vendor: {
  name: 'xxfe_vendor',
  chunks: 'all',
  priority: 8,
  enforce: true,
  test: (module) => {
    const resource = getModulePath(module)
    if (/[@xxfe](/user/xxfe)(\\|\/)/.test(resource)) {
      return true
    }
    return false
  },
},

至此我们理清了导致问题的原因, [@xxfe](/user/xxfe)/pkg 中的 import 函数引用的 js 及其自身代码由于分包的 splitChunks 设置都被打入到了 xxfe_vendor 中, 而 xxfe_vendor 又是业务项目 main.js 前置运行依赖的 js, 故 webpack 错误的认为你不需要 webpack_require.e 函数

  • webpack 版本: 4.39.0

Q: 这个算谁的 bug ?

A: webpack 的 bug 。因为不能说用户需要加载的 js 如果在首屏其中之一, 就不注入 webpack_require.e 函数的实现。业务项目通过 splitChunks 进行分包不是 npm 包的作者所能决定, 是否注入 webpack_require.e 函数的实现应该由是否有 import 函数语法来决定。

Q: 为什么开发环境没有报错 ?

A: 业务项目的 splitChunks 设置了只在生产构建生效

问题解决

将如下的 chunks 字段由 all 改为了 initial, 表示该分包设置将不要影响到动态 import 函数异步加载的 js (该类型为 async chunks), 使得 [@xxfe](/user/xxfe)/pkg 将不会被合入 xxfe_vendor 文件中, 那么如上的 initialChunks 也将不包含 [@xxfe](/user/xxfe)/pkg, 从而 webpack 也会如约注入 webpack_require.e 函数的实现

xxfe_vendor: {
  name: 'xxfe_vendor',
  chunks: 'initial',
  priority: 8,
  enforce: true,
  test: (module) => {
    const resource = getModulePath(module)
    if (/[@xxfe](/user/xxfe)(\\|\/)/.test(resource)) {
      return true
    }
    return false
  },
},

3 回复

基操,勿 6 😄

针对你提到的“Node.js n.e is not a function”问题,这通常意味着你尝试调用的n.e并不是一个有效的函数。这个问题可能由多种原因引起,以下是一些可能的解决步骤和示例代码来帮助你排查问题:

  1. 检查对象定义: 确保n对象已正确定义,并且e确实是一个函数。可能的情况是e被误写或者未被正确赋值。

    const n = {
      e: function() {
        console.log("Function e called");
      }
    };
    
    n.e();  // 正常调用
    
  2. 检查导入和模块: 如果n是从其他模块导入的,确保导入的模块正确导出了e函数。

    // module.js
    module.exports = {
      e: function() {
        console.log("Function e from module");
      }
    };
    
    // main.js
    const n = require('./module');
    n.e();  // 调用模块中的函数
    
  3. 检查拼写和大小写: JavaScript是区分大小写的,因此n.en.E会被视为不同的属性。

  4. 运行时错误: 如果nn.e是在某些条件下才定义的,确保在调用n.e()之前这些条件已经满足。

如果以上步骤都无法解决问题,请检查你的代码上下文,特别是n对象被赋值和修改的部分。如果可能,提供更详细的代码片段将有助于进一步诊断问题。

回到顶部