Skip to content

Commit

Permalink
fix(format/cjs): support cjs-module-lexer for `export * from 'exter…
Browse files Browse the repository at this point in the history
…nal'` (#1854)

<!-- Thank you for contributing! -->

### Description

<!-- Please insert your description here and provide especially info
about the "what" this PR is solving -->
  • Loading branch information
hyf0 authored Aug 3, 2024
1 parent 4200e64 commit de1f5f5
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 81 deletions.
31 changes: 15 additions & 16 deletions crates/rolldown/src/ecmascript/format/cjs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use itertools::Itertools;
use rolldown_common::{ChunkKind, ExportsKind, Module, WrapKind};
use rolldown_error::DiagnosableResult;
use rolldown_sourcemap::{ConcatSource, RawSource};
Expand Down Expand Up @@ -40,23 +39,23 @@ pub fn render_cjs(
if let ChunkKind::EntryPoint { module: entry_id, .. } = ctx.chunk.kind {
if let Module::Ecma(entry_module) = &ctx.link_output.module_table.modules[entry_id] {
if matches!(entry_module.exports_kind, ExportsKind::Esm) {
entry_module.star_export_module_ids().filter_map(|importee| {
let importee = &ctx.link_output.module_table.modules[importee];
match importee {
Module::External(ext) => Some(&ext.name),
Module::Ecma(_) => {None}
}
}).dedup().for_each(|ext_name| {
let import_stmt =
let meta = &ctx.link_output.metas[entry_id];
meta.require_bindings_for_star_exports.iter().for_each(|(importee_idx, binding_ref)| {
let importee = &ctx.link_output.module_table.modules[*importee_idx];
let binding_ref_name =
ctx.link_output.symbols.canonical_name_for(*binding_ref, &ctx.chunk.canonical_names);
let import_stmt =
"Object.keys($NAME).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return $NAME[k]; }
});
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return $NAME[k]; }
});
});
".replace("$NAME", &format!("require(\"{}\")", &ext_name));
concat_source.add_source(Box::new(RawSource::new(import_stmt)));
});
".replace("$NAME", binding_ref_name);
concat_source.add_source(Box::new(RawSource::new(format!("var {} = require(\"{}\");", binding_ref_name,&importee.stable_id()))));
concat_source.add_source(Box::new(RawSource::new(import_stmt)));

});
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions crates/rolldown/src/stages/link_stage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,28 @@ impl<'a> LinkStage<'a> {
{
self.metas[importer.idx].wrap_kind = WrapKind::Cjs;
}

// TODO: should have a better place to put this
if is_entry && matches!(self.options.format, OutputFormat::Cjs) {
importer.star_exports.iter().for_each(|rec_idx| {
let rec = &importer.import_records[*rec_idx];
match &self.module_table.modules[rec.resolved_module] {
Module::Ecma(_) => {}
Module::External(ext) => {
self.metas[importer.idx]
.require_bindings_for_star_exports
.entry(rec.resolved_module)
.or_insert_with(|| {
// Created `SymbolRef` is only join the de-conflict process to avoid conflict with other symbols.
self.symbols.create_symbol(
importer.idx,
legitimize_identifier_name(&ext.name).into_owned().into(),
)
});
}
}
});
};
});
}

Expand Down
2 changes: 2 additions & 0 deletions crates/rolldown/src/types/linking_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub struct LinkingMetadata {
pub dependencies: Vec<ModuleIdx>,
// `None` the member expression resolve to a ambiguous export.
pub resolved_member_expr_refs: FxHashMap<Span, Option<(SymbolRef, Vec<CompactStr>)>>,
// We need to generate `const ext = require('ext')` for `export * from 'ext'` in cjs output
pub require_bindings_for_star_exports: FxHashMap<ModuleIdx, SymbolRef>,
}

impl LinkingMetadata {
Expand Down
12 changes: 11 additions & 1 deletion crates/rolldown/src/utils/chunk/deconflict_chunk_symbols.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow;

use crate::{stages::link_stage::LinkStageOutput, utils::renamer::Renamer};
use rolldown_common::Chunk;
use rolldown_common::{Chunk, ChunkKind};
use rolldown_rstr::ToRstr;

#[tracing::instrument(level = "trace", skip_all)]
Expand Down Expand Up @@ -31,6 +31,16 @@ pub fn deconflict_chunk_symbols(chunk: &mut Chunk, link_output: &LinkStageOutput
chunk.require_binding_names_for_other_chunks.values_mut().for_each(|name_hint| {
*name_hint = renamer.create_conflictless_top_level_name(&format!("require_{name_hint}"));
});
match chunk.kind {
ChunkKind::EntryPoint { module, .. } => {
let meta = &link_output.metas[module];
meta
.require_bindings_for_star_exports
.iter()
.for_each(|(_module, binding_ref)| renamer.add_top_level_symbol(*binding_ref));
}
ChunkKind::Common => {}
}

chunk
.modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ source: crates/rolldown_testing/src/integration_test.rs

```js
"use strict";
Object.keys(require("foo")).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return require("foo")[k]; }
});
var foo = require("foo");
Object.keys(foo).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return foo[k]; }
});
});
require("foo");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,29 @@ source: crates/rolldown_testing/src/integration_test.rs

```js
"use strict";
Object.keys(require("node:fs")).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return require("node:fs")[k]; }
});
var node_fs = require("node:fs");
Object.keys(node_fs).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return node_fs[k]; }
});
});
const require_main = require('./main.cjs');
```
## entry2.cjs

```js
"use strict";
Object.keys(require("node:fs")).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return require("node:fs")[k]; }
});
var node_fs = require("node:fs");
Object.keys(node_fs).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return node_fs[k]; }
});
});
const require_main = require('./main.cjs');
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ source: crates/rolldown_testing/src/integration_test.rs

```js
"use strict";
Object.keys(require("node:fs")).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return require("node:fs")[k]; }
});
var node_fs = require("node:fs");
Object.keys(node_fs).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return node_fs[k]; }
});
});
require("node:fs");
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
source: crates/rolldown_testing/src/integration_test.rs
---
# Assets

## main.cjs

```js
"use strict";
var node_path = require("node:path");
Object.keys(node_path).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return node_path[k]; }
});
});
var node_fs = require("node:fs");
Object.keys(node_fs).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return node_fs[k]; }
});
});
require("node:fs");
require("node:path");
//#region main.js
var main_ns = {};
__reExport(main_ns, require("node:fs"));
__reExport(main_ns, require("node:path"));
//#endregion
```
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ expression: "snapshot_outputs.join(\"\\n\")"

# tests/esbuild/import_star/re_export_star_external_common_js

- entry_js-!~{000}~.cjs => entry_js-vbOU9bsn.cjs
- entry_js-!~{000}~.cjs => entry_js-thBqv2Ua.cjs

# tests/esbuild/import_star/re_export_star_external_es6

Expand Down Expand Up @@ -1819,8 +1819,8 @@ expression: "snapshot_outputs.join(\"\\n\")"

# tests/rolldown/semantic/export_star_from_external_as_shared_entries_cjs

- entry-!~{000}~.cjs => entry--edQlK0T.cjs
- entry2-!~{001}~.cjs => entry2-_Ecr96ES.cjs
- entry-!~{000}~.cjs => entry-jtLYNAmt.cjs
- entry2-!~{001}~.cjs => entry2-TRBE9xZU.cjs
- main-!~{002}~.cjs => main-j1Gp3SFm.cjs

# tests/rolldown/semantic/export_star_from_external_as_wrapped_entry
Expand All @@ -1829,7 +1829,11 @@ expression: "snapshot_outputs.join(\"\\n\")"

# tests/rolldown/semantic/export_star_from_external_as_wrapped_entry_cjs

- entry-!~{000}~.cjs => entry-xjPBN9yi.cjs
- entry-!~{000}~.cjs => entry-kDEnEDWl.cjs

# tests/rolldown/topics/cjs_module_lexer_compat/export_star_from_external

- main-!~{000}~.cjs => main-CghQHSuN.cjs

# tests/rolldown/topics/cjs_module_lexer_compat/exports

Expand Down

0 comments on commit de1f5f5

Please sign in to comment.