You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

CJS Serverless项目运行时动态加载ESM模块@gsys/properties-node失败的排障求助

CJS Serverless项目运行时动态加载ESM模块@gsys/properties-node失败的排障求助

大家好,我目前在维护一个基于Serverless框架的项目,用serverless-bundle做代码打包,现在遇到了一个棘手的问题:运行时动态加载ESM模块@gsys/properties-node时,总是抛出模块找不到的错误。下面是我的项目配置、相关代码和错误信息,麻烦各位帮忙看看问题出在哪?

一、Serverless打包配置(serverless.yml片段)

我在serverless配置里针对@gsys/properties-node做了这些特殊处理:

sourcemaps: true
disableForkTsChecker: true
externals:
  - newrelic # Keeps newrelic untouched in node modules. Not compatible with webpack/treeshaking
  - '@js-temporal/polyfill'
  - '@gsys/common-events'
forceInclude:
  - '@gsys/properties-node'
copyFiles:
  - from: 'node_modules/@gsys/outer-limits-client/resources/limit-files/social-media-limits.yml'
    to: './src/main/ts/resources/limit-files'
  - from: 'node_modules/@gsys/kafka-clients-node/dist/resources/buckets.yml'
    to: './src/main/ts/resources/'
custom:
  esbuild:
    bundle: true
    external:
      - '@gsys/properties-node'
plugins:
  - serverless-bundle

因为这个模块是纯ESM格式的,我需要在运行时动态加载,所以把它加入了forceInclude确保被打包进去,同时在esbuild配置里设为external避免打包时的CJS/ESM互操作报错。

二、Handler核心代码

我的Lambda handler是一个异步函数,核心逻辑就是动态加载目标模块并初始化处理程序:

async function dynamicPropertiesHandler(event: FlyoverAlbEvent, awsContext: Context){
  try {
    const correlationID = getCorrelationIdFromHeaders(event.headers ?? {});
    log.setMetadata({ ...awsContext, correlationID });
    log.info(`Incoming ${event.httpMethod} request for dynamic properties handler, PATH: ${event.path}`);

    const propertyService: any = await dynamicPropertyService.getService();
    // 动态加载ESM模块
    const { PropertiesHandler } = await loadEsm('@gsys/properties-node');
    const propertiesHandler = new PropertiesHandler(propertyService).buildHandler();
    const response: ALBResult | undefined = await propertiesHandler(event);

    log.info(`DynamicPropertiesHandler response: ${JSON.stringify(response)}`);
    if (!response) {
      throw new NotFoundError(PROPERTY_NOT_FOUND);
    }
    return response;
  } catch (error) {
    logError(log, error, 'error occured in dynamic property handler');
    return generateALBError(error);
  }
}

其中loadEsm是我封装的工具函数,内部用标准的import()语法实现ESM模块加载,本地测试时是正常的。

三、TypeScript配置(tsconfig.json)

项目的TS配置如下,我特意设置了module: Node16来支持ESM和CJS的互操作:

{
  "extends": "@some-other-library",
  "compilerOptions": {
    "rootDir": "./",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "module": "Node16",
    "target": "ES2022",
    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
    "strictNullChecks": true /* Enable strict null checks. */,
    "strictFunctionTypes": true /* Enable strict checking of function types. */,
    "strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
    "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
    "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
    "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
    "pretty": true,
    /* Additional Checks */
    "noUnusedLocals": true /* Report errors on unused locals. */,
    "noUnusedParameters": true /* Report errors on unused parameters. */,
    "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
    "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
    "outDir": "dist", // Required for serverless
    "sourceMap": true,
    "useUnknownInCatchVariables": false,
    "lib": ["es2016", "es2017", "esnext", "dom"]
  }
}

四、Package.json打包脚本片段

项目的打包脚本会先编译TS,再复制依赖配置并安装生产依赖:

{
  "name": "my-project",
  "version": "1.0.0",
  "license": "ISC",
  "scripts": {
    "bundle": "rm -rf dist && mkdir dist && tsc --project tsconfig.bundle.json && cp package*.json ./dist && cp -r gsys-properties ./dist && cd ./dist && npm ci --no-progress --omit=dev && rm -f ../project.zip && zip -qr ../project.zip .",
    // 其他脚本省略...
  }
}

五、运行时报错信息

部署到AWS Lambda后,请求触发时抛出的错误:

{"message":"Cannot find module '@gsys/properties-node'","code":"MODULE_NOT_FOUND","status":500,"contextId":null}

我已经做的排查(还没解决问题)

  1. 检查了部署用的zip包,确认node_modules/@gsys/properties-node目录完整存在;
  2. 尝试过去掉esbuild配置里的external,结果打包时直接因为ESM/CJS互操作的语法问题报错;
  3. 本地用Lambda模拟环境测试时,能正常加载模块并运行,只有部署到线上才出问题;
  4. 确认了@gsys/properties-node的package.json里type字段是module,确实是ESM模块。

有没有朋友遇到过类似的Serverless+ESM动态加载的问题?或者能帮我分析下可能的原因?感谢大家!

内容来源于stack exchange

火山引擎 最新活动