nodejs / help

:sparkles: Need help with Node.js? File an Issue here. :rocket:
1.44k stars 276 forks source link

Cannot import ESM modules when modifying require.extensions #4350

Open devjiwonchoi opened 4 months ago

devjiwonchoi commented 4 months ago

TL;DR

How to require (or import) Native ESM modules while using a require hook like require.extensions.

Note: I do know that require.extensions is deprecated and is not recommended to modify, but as ts-node does, I am using this as a part of a temporary workaround.

Goal

I want to dynamic import a configuration file, jiwon.config.ts, which should import other JS/TS extensions successfully.

Restrictions

I cannot use other dependencies like webpack or esbuild to bundle and resolve the imports. The only option is to use SWC.

Work Flow

  1. importConfig()
  2. looks for jiwon.config.ts, exists
  3. REGISTER REQUIRE HOOK
  4. readFile() the config
  5. SWC transform the code
  6. writeFile() the config as .js
  7. dynamic import jiwon.config.js

REGISTER REQUIRE HOOK

Why? To not bundle and do fewer operations on resolving the config file. Especially utilizing the high performance of SWC.

import { transformSync, type Options } from '@swc/core'

const originalJsHandler = require.extensions['.js']
const transformableExtensions = ['.ts', '.cts', '.mts', '.cjs', '.mjs']
const swcOptions: Options = {
    jsc: {
      target: 'es5',
      parser: {
        syntax: 'typescript',
      },
    },
    module: {
      type: 'commonjs',
    },
    isModule: 'unknown',
  }

function registerSWCTransform(swcOptions: Options, isESM: boolean) {
  // if (isESM) {
  // TODO: handle ESM
  // }

  for (const ext of transformableExtensions) {
    require.extensions[ext] = function (m: any, originalFileName) {
      const _compile = m._compile

      m._compile = function (code: string, filename: string) {
        const swc = transformSync(code, swcOptions)
        return _compile.call(this, swc.code, filename)
      }

      return originalJsHandler(m, originalFileName)
    }
  }
}

Current Status

By doing so, I've achieved to cover 90% of the expected & edge cases of resolving the config file. The only thing that I am struggling with is:

  1. On the Native ESM project (package.json type: module), import the .js (ESM) file.
  2. On the CJS project, import the Native ESM package(also, .js).

They both have in common that the format is ESM but the extension is .js.

As I tried to overwrite the require.extensions['.js'], threw an error during the process of importing other .js files.

Requesting for Help

If you...

  1. know how to resolve requiring Native ESM .js file with the current status
  2. can suggest other ways to achieve the current goal than to register the require hook

please share your expertise and insights, will be very grateful for your help.

Node.js version

{
  node: '18.17.0',
  acorn: '8.8.2',
  ada: '2.5.0',
  ares: '1.19.1',
  brotli: '1.0.9',
  cldr: '43.0',
  icu: '73.1',
  llhttp: '6.0.11',
  modules: '108',
  napi: '9',
  nghttp2: '1.52.0',
  nghttp3: '0.7.0',
  ngtcp2: '0.8.1',
  openssl: '3.0.9+quic',
  simdutf: '3.2.12',
  tz: '2023c',
  undici: '5.22.1',
  unicode: '15.0',
  uv: '1.44.2',
  uvwasi: '0.0.18',
  v8: '10.2.154.26-node.26',
  zlib: '1.2.13.1-motley'
}

Example code

See above.

Operating system

Darwin MacBook-Pro.local 23.1.0 Darwin Kernel Version 23.1.0: Mon Oct  9 21:32:11 PDT 2023; root:xnu-10002.41.9~7/RELEASE_ARM64_T6030 arm64

Scope

Custom compiling through modifying require.extensions.

Module and version

Not applicable.