Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cjs/mjs extensions are stripped from output files #9800

Open
JacobLey opened this issue Dec 16, 2024 · 1 comment
Open

cjs/mjs extensions are stripped from output files #9800

JacobLey opened this issue Dec 16, 2024 · 1 comment
Labels

Comments

@JacobLey
Copy link

Describe the bug

When building a directory: swc ./src -d ./dist --copy-files --config-file .swcrc --strip-leading-paths

There are files are explicitly using the .cts and .mts extensions (meaning commonjs + ESM, respectively).

When building these files (e.g. via tsc) it is expected to map to a .cjs and .mjs extend files.

However when building with SWC, all files are emitted with .js extensions.

Not only does this break existing resolution paths, it will change how the code is executed which is guaranteed to break (you cannot have both commonjs and ESM valid syntax in .js files)

If you have multiple files that only differ by extension, it will also omit some of the files as they all cannot be built under the same extension. In my experience so far, the written file is a non-deterministic race condition.

Input code

// index.ts
import bar from './esm.mjs';

export const foo = bar;

// index.cts
export const foo = 'bar';

// esm.mts
export default 'bar'

Config

{
  "$schema": "https://swc.rs/schema.json",
  "isModule": true,
  "jsc": {
    "target": "esnext",
    "parser": {
      "syntax": "typescript",
    }
  },
  "module": {
    "type": "nodenext"
  }
}

Playground link (or link to the minimal reproduction)

https://github.com/JacobLey/issue-recreator/tree/swc-extensions

SWC Info output

Operating System:
Platform: linux
Arch: arm64
Machine Type: aarch64
Version: #60 SMP Mon Nov 18 05:48:29 UTC 2024
CPU: (14 cores)
Models: unknown

Binaries:
    Node: 22.12.0
    npm: N/A
    Yarn: N/A
    pnpm: 9.13.2

Relevant Packages:
    @swc/core: 1.10.1
    @swc/helpers: 0.5.15
    @swc/types: N/A
    typescript: 5.6.3

SWC Config:
    output: N/A
    .swcrc path: N/A

Next.js info:
    output: N/A

Expected behavior

From the input code, three files should be built:

// index.js
import bar from './esm.mjs';

export const foo = bar;
// index.cts
module.exports = { foo: 'bar' };
// esm.mjs
export default 'bar'

Actual behavior

The index.cjs file will not be built at all. It will be written as the index.js file (if not overwritten by the true index.js file)

The esm.mjs file will instead be emitted as esm.js

And the index.js file will fail to execute, since it tries to load from a file that doesn't exist (if not overwritten by the index.cts)

Version

@swc/cli: 0.5.2 @swc/core: 1.10.1

Additional context

I believe this is coming from the --out-file-extension option. Despite the fact that I have not set it, it comes with a default of js.

Trying to build both ESM and CJS at the same time will also end up running into this issue: #9790
Currently SWC tries to build the entire directory as ESM or CJS, it never infer the proper output based on extension.

@JacobLey
Copy link
Author

My workaround is to invoke SWC three times, once for each extension:

  • swc ./src -d ./dist --copy-files --config-file .swcrc --strip-leading-paths --only '**/*.ts?(x)'
  • swc ./src -d ./dist --config-file .swcrc --strip-leading-paths --only '**/*.mts?(x)' --out-file-extension mjs
  • swc ./src -d ./dist --config-file .swcrc --strip-leading-paths -C module.type=commonjs -C module.ignoreDynamic=true --only '**/*.cts?(x)' --out-file-extension cjs

Be careful to disable module.resolveFully as well, because when enabled, SWC will start doing things like rewriting paths to index.cjs -> index.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

1 participant