Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions crates/rolldown/src/utils/parse_to_ecma_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::{borrow::Cow, path::Path};
use json_escape_simd::escape;
use oxc::{semantic::Scoping, span::SourceType as OxcSourceType};
use rolldown_common::{
ModuleType, NormalizedBundlerOptions, RUNTIME_MODULE_KEY, StrOrBytes, json_value_to_ecma_ast,
ModuleDefFormat, ModuleType, NormalizedBundlerOptions, RUNTIME_MODULE_KEY, StrOrBytes,
json_value_to_ecma_ast,
};
use rolldown_ecmascript::{EcmaAst, EcmaCompiler};
use rolldown_error::{BuildDiagnostic, BuildResult};
Expand All @@ -16,14 +17,22 @@ use super::pre_process_ecma_ast::PreProcessEcmaAst;
use crate::types::{module_factory::CreateModuleContext, oxc_parse_type::OxcParseType};

#[inline]
fn pure_esm_js_oxc_source_type() -> OxcSourceType {
let pure_esm_js = OxcSourceType::default().with_module(true);
debug_assert!(pure_esm_js.is_javascript());
debug_assert!(!pure_esm_js.is_jsx());
debug_assert!(pure_esm_js.is_module());
debug_assert!(pure_esm_js.is_strict());

pure_esm_js
fn pure_esm_js_oxc_source_type(module_def_format: ModuleDefFormat) -> OxcSourceType {
let default_source_type = OxcSourceType::default();
debug_assert!(default_source_type.is_javascript());
debug_assert!(!default_source_type.is_jsx());
match module_def_format {
ModuleDefFormat::Cjs | ModuleDefFormat::Cts | ModuleDefFormat::CjsPackageJson => {
default_source_type.with_commonjs(true)
}
ModuleDefFormat::EsmMjs | ModuleDefFormat::EsmMts | ModuleDefFormat::EsmPackageJson => {
default_source_type.with_module(true)
}
ModuleDefFormat::Unknown => {
// treat unknown format as ESM for now: https://github.com/rolldown/rolldown/issues/7009
default_source_type.with_module(true)
}
}
}

pub struct ParseToEcmaAstResult {
Expand Down Expand Up @@ -57,7 +66,7 @@ pub async fn parse_to_ecma_ast(
pre_process_source(path, source, module_type, options)?;

let oxc_source_type = {
let default = pure_esm_js_oxc_source_type();
let default = pure_esm_js_oxc_source_type(resolved_id.module_def_format);
match parsed_type {
OxcParseType::Js => default,
OxcParseType::Jsx => default.with_jsx(!options.transform_options.is_jsx_disabled()),
Expand Down
1 change: 1 addition & 0 deletions crates/rolldown/tests/rolldown/issues/4289/_config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"config": {
"platform": "node",
"external": ["node:assert"]
},
"configVariants": [
Expand Down
25 changes: 16 additions & 9 deletions crates/rolldown/tests/rolldown/issues/4289/artifacts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ var require_lib = /* @__PURE__ */ __commonJSMin(((exports) => {
//#endregion
export default require_lib();

export { };
```

## main.js

```js
import { createRequire } from "node:module";
import nodeAssert from "node:assert";

// HIDDEN [\0rolldown/runtime.js]
Expand All @@ -47,11 +49,12 @@ main();
//#endregion
//#region node-mode-by-cjs-extension.cjs
var require_node_mode_by_cjs_extension = /* @__PURE__ */ __commonJSMin(((exports, module) => {
const nodeAssert$1 = __require("node:assert");
async function main() {
const exports$1 = await import("./lib.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
nodeAssert.deepEqual(Object.keys(exports$1).sort(), ["default", "parse"]);
nodeAssert.strictEqual(exports$1.parse, "parse", "Expected export exists and is correct");
nodeAssert.strictEqual(exports$1.default.parse, "parse", "Target has __esModule, but this file triggered node compat mode");
nodeAssert$1.deepEqual(Object.keys(exports$1).sort(), ["default", "parse"]);
nodeAssert$1.strictEqual(exports$1.parse, "parse", "Expected export exists and is correct");
nodeAssert$1.strictEqual(exports$1.default.parse, "parse", "Target has __esModule, but this file triggered node compat mode");
}
main();
module.exports = {};
Expand Down Expand Up @@ -119,11 +122,12 @@ main();
//#endregion
//#region node-mode-by-cjs-extension.cjs
var require_node_mode_by_cjs_extension = /* @__PURE__ */ __commonJSMin(((exports, module) => {
const nodeAssert = require("node:assert");
async function main() {
const exports$1 = await Promise.resolve().then(() => /* @__PURE__ */ __toESM(require("./lib.js").default, 1));
node_assert.default.deepEqual(Object.keys(exports$1).sort(), ["default", "parse"]);
node_assert.default.strictEqual(exports$1.parse, "parse", "Expected export exists and is correct");
node_assert.default.strictEqual(exports$1.default.parse, "parse", "Target has __esModule, but this file triggered node compat mode");
nodeAssert.deepEqual(Object.keys(exports$1).sort(), ["default", "parse"]);
nodeAssert.strictEqual(exports$1.parse, "parse", "Expected export exists and is correct");
nodeAssert.strictEqual(exports$1.default.parse, "parse", "Target has __esModule, but this file triggered node compat mode");
}
main();
module.exports = {};
Expand All @@ -144,6 +148,7 @@ exports.__commonJSMin = __commonJSMin;
### main.js

```js
import { createRequire } from "node:module";
import nodeAssert from "node:assert";

// HIDDEN [\0rolldown/runtime.js]
Expand Down Expand Up @@ -176,11 +181,12 @@ main();
//#endregion
//#region node-mode-by-cjs-extension.cjs
var require_node_mode_by_cjs_extension = /* @__PURE__ */ __commonJSMin(((exports, module) => {
const nodeAssert$1 = __require("node:assert");
async function main() {
const exports$1 = await Promise.resolve().then(() => /* @__PURE__ */ __toESM(require_lib(), 1));
nodeAssert.deepEqual(Object.keys(exports$1).sort(), ["default", "parse"]);
nodeAssert.strictEqual(exports$1.parse, "parse", "Expected export exists and is correct");
nodeAssert.strictEqual(exports$1.default.parse, "parse", "Target has __esModule, but this file triggered node compat mode");
nodeAssert$1.deepEqual(Object.keys(exports$1).sort(), ["default", "parse"]);
nodeAssert$1.strictEqual(exports$1.parse, "parse", "Expected export exists and is correct");
nodeAssert$1.strictEqual(exports$1.default.parse, "parse", "Target has __esModule, but this file triggered node compat mode");
}
main();
module.exports = {};
Expand All @@ -191,4 +197,5 @@ var require_node_mode_by_cjs_extension = /* @__PURE__ */ __commonJSMin(((exports
var import_node_mode_by_cjs_extension = require_node_mode_by_cjs_extension();

//#endregion
export { };
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import nodeAssert from 'node:assert';
const nodeAssert = require('node:assert');

async function main() {
const exports = await import('./lib.js');
Expand Down
12 changes: 12 additions & 0 deletions crates/rolldown/tests/rolldown/issues/7009/cjs/_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"config": {
"input": [
{
"name": "main",
"import": "./index.cjs"
}
],
"format": "cjs"
},
"expectExecuted": false
}
17 changes: 17 additions & 0 deletions crates/rolldown/tests/rolldown/issues/7009/cjs/artifacts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: crates/rolldown_testing/src/integration_test.rs
---
# Assets

## main.js

```js

//#region index.cjs
exports.foo = function a() {
return "foo";
};
arguments = 1;

//#endregion
```
5 changes: 5 additions & 0 deletions crates/rolldown/tests/rolldown/issues/7009/cjs/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exports.foo = function a() {
return 'foo';
};

arguments = 1;
12 changes: 12 additions & 0 deletions crates/rolldown/tests/rolldown/issues/7009/cts/_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"config": {
"input": [
{
"name": "main",
"import": "./index.cts"
}
],
"format": "cjs"
},
"expectExecuted": false
}
17 changes: 17 additions & 0 deletions crates/rolldown/tests/rolldown/issues/7009/cts/artifacts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: crates/rolldown_testing/src/integration_test.rs
---
# Assets

## main.js

```js

//#region index.cts
exports.foo = function a() {
return "foo";
};
arguments = 1;

//#endregion
```
5 changes: 5 additions & 0 deletions crates/rolldown/tests/rolldown/issues/7009/cts/index.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exports.foo = function a() {
return 'foo';
};

arguments = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"config": {
"input": [
{
"name": "main",
"import": "./index.js"
}
],
"format": "cjs"
},
"expectExecuted": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: crates/rolldown_testing/src/integration_test.rs
---
# Assets

## main.js

```js

//#region index.js
exports.foo = function a() {
return "foo";
};
arguments = 1;

//#endregion
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exports.foo = function a() {
return 'foo';
};

arguments = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"name": "index"
}
],
"platform": "node",
"keepNames": true
},
"expectExecuted": false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ source: crates/rolldown_testing/src/integration_test.rs
## index.js

```js
import assert from "node:assert";
import { createRequire } from "node:module";

// HIDDEN [\0rolldown/runtime.js]
//#region bar.cjs
//#region bar.mjs
var bar_exports = /* @__PURE__ */ __exportAll({ test: () => test$1 });
var test$1;
var init_bar = __esmMin((() => {
test$1 = /* @__PURE__ */ __name(() => {
Expand All @@ -18,7 +19,8 @@ var init_bar = __esmMin((() => {
}));

//#endregion
//#region foo.cjs
//#region foo.mjs
var foo_exports = /* @__PURE__ */ __exportAll({ default: () => test });
function test() {
return { test: "foo" };
}
Expand All @@ -27,13 +29,15 @@ var init_foo = __esmMin((() => {}));
//#endregion
//#region index.cjs
var require_src = /* @__PURE__ */ __commonJSMin((() => {
init_bar();
init_foo();
assert.strictEqual(test$1.name, "test");
const assert = __require("node:assert");
const { test: barTest } = (init_bar(), __toCommonJS(bar_exports));
const { default: test } = (init_foo(), __toCommonJS(foo_exports));
assert.strictEqual(barTest.name, "test");
assert.strictEqual(test.name, "test");
}));

//#endregion
export default require_src();

export { };
```
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert';
import { test as barTest } from './bar.cjs';
import test from './foo.cjs';
const assert = require('node:assert');
const { test: barTest } = require('./bar.mjs');
const { default: test } = require('./foo.mjs');

assert.strictEqual(barTest.name, 'test');
assert.strictEqual(test.name, 'test');
Original file line number Diff line number Diff line change
Expand Up @@ -4660,8 +4660,8 @@ expression: output

# tests/rolldown/issues/4289

- main-!~{000}~.js => main-d6x-NxY7.js
- lib-!~{001}~.js => lib-jm3gYqNp.js
- main-!~{000}~.js => main-ckJZUoDu.js
- lib-!~{001}~.js => lib-BpjDadci.js

# tests/rolldown/issues/4324

Expand Down Expand Up @@ -4882,6 +4882,18 @@ expression: output
- _virtual/_rolldown/runtime-!~{001}~.js => _virtual/_rolldown/runtime-Fau4GV7D.js
- server/index-!~{002}~.js => server/index-Da8_eDWm.js

# tests/rolldown/issues/7009/cjs

- main-!~{000}~.js => main-CgjitbJ5.js

# tests/rolldown/issues/7009/cts

- main-!~{000}~.js => main-B4u3m95-.js

# tests/rolldown/issues/7009/js_type_commonjs

- main-!~{000}~.js => main-CO96sClh.js

# tests/rolldown/issues/7021

- main-!~{000}~.js => main-BuqYlu74.js
Expand Down Expand Up @@ -5956,7 +5968,7 @@ expression: output

# tests/rolldown/topics/keep_names/issue_7507/src

- index-!~{000}~.js => index-EsnM9fHZ.js
- index-!~{000}~.js => index-BPcqbtIz.js

# tests/rolldown/topics/keep_names/issue_7852

Expand Down
6 changes: 6 additions & 0 deletions docs/in-depth/bundling-cjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ If you find an issue that seems to be caused by this incompatibility, try using

If the heuristic is not working for you, you can use the code in the section above that handles both interpretations. If the import is in a dependency, we recommend to raise an issue to the dependency. In the meantime, you can use [`patch-package`](https://github.com/ds300/patch-package) or [`pnpm patch`](https://pnpm.io/cli/patch) or alternatives as an escape hatch.

### Strict Mode Applied to `.js` files without `"type": "commonjs"` in `package.json`

For files ending with `.js` that do not have `"type": "commonjs"` in the closest `package.json`, Rolldown incorrectly parses the file as ESM ([#7009](https://github.com/rolldown/rolldown/issues/7009)). This means that syntaxes only allowed in non-strict mode (sloppy mode) will be rejected.

For now, you can change the file extension to `.cjs` or add `"type": "commonjs"` to the closest `package.json` as a workaround.

## Future Plans

Rolldown's first-class support for CommonJS modules enables several potential optimizations:
Expand Down
Loading