Skip to content

Allow user-supplied jsc.experimental options #1034

Description

@sderrow

Summary

@swc-node/core's transformOption constructs jsc.experimental as a hardcoded { keepImportAttributes: true } literal with no way for callers to extend it (see here). This makes it effectively impossible to enable SWC plugins (e.g. @swc-contrib/mut-cjs-exports) when going through @swc-node/core or @swc-node/jest without re-implementing the entire transformOption body.

This is especially painful with @swc-node/jest: the only knob exposed by its public API is the Options argument, but Options has no experimental field, and overriding via swc: { jsc: {...} } clobbers every other tsconfig-derived jsc setting (target, paths, parser, transform, etc). See also #700.

Motivation

The most common reason users want to pass a plugin through @swc-node/jest is to make jest.mock / jest.requireActual / Sinon stubs work on modules with circular imports (exactly the use case in #713). The recommended fix in the SWC ecosystem is the @swc-contrib/mut-cjs-exports plugin, which turns ESM export const bindings (compiled to CJS getters by SWC) into plain assignments so they can be mocked.

There is currently no clean way to wire that plugin into @swc-node/jest. Users either:

  1. Drop @swc-node/jest and switch to @swc/jest + .swcrc (loses tsconfig integration), or
  2. Write a custom Jest transformer that calls core.transformJest with a manually-rebuilt jsc (~70 lines of code that re-implements transformOption), or
  3. Patch node_modules/@swc-node/core/lib/index.js (what I'm currently doing via patch-package).

Proposed change

Allow callers to pass an experimental field on Options and merge it into the constructed jsc.experimental. One spread, no behavior change for existing callers.

packages/core/index.ts: extend the Options interface:

 export interface Options {
   // ...existing fields...
   swc?: SwcOptions
   ignoreDynamic?: boolean
+  experimental?: {
+    plugins?: Array<[string, Record<string, unknown>]>
+    keepImportAttributes?: boolean
+  }
 }

packages/core/index.ts: merge inside transformOption:

                 baseUrl: opts.baseUrl,
                 experimental: {
                   keepImportAttributes: true,
+                  ...(opts.experimental ?? {}),
                 },

After this change, @swc-node/jest users get plugin support for free, with no other changes needed in @swc-node/jest:

// jest.config.js
module.exports = {
  transform: {
    "^.+\\.[tj]sx?$": [
      "@swc-node/jest",
      {
        experimental: {
          plugins: [["@swc-contrib/mut-cjs-exports", {}]],
        },
      },
    ],
  },
};

Backwards compatibility

  • New optional field; all existing call sites unaffected.
  • keepImportAttributes: true remains the default; user can opt out by passing experimental: { keepImportAttributes: false }.
  • No changes to @swc-node/jest's public API, it already spreads its second-arg options into core.transformJest.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions