Skip to content

Commit

Permalink
feat: support InputOptions#shimMissingExports (#772)
Browse files Browse the repository at this point in the history
<!-- 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 committed Apr 6, 2024
1 parent 6714dd0 commit 2c84963
Show file tree
Hide file tree
Showing 30 changed files with 226 additions and 15 deletions.
18 changes: 18 additions & 0 deletions crates/rolldown/src/finalizer/impl_visit_mut_for_finalizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,24 @@ impl<'ast, 'me: 'ast> VisitMut<'ast> for Finalizer<'me, 'ast> {
},
);

let mut shimmed_exports =
self.ctx.linking_info.shimmed_missing_exports.iter().collect::<Vec<_>>();
shimmed_exports.sort_by_key(|(name, _)| name.as_str());
shimmed_exports.into_iter().for_each(|(_name, symbol_ref)| {
debug_assert!(!self.ctx.module.stmt_infos.declared_stmts_by_symbol(symbol_ref).is_empty());
let is_included: bool = self
.ctx
.module
.stmt_infos
.declared_stmts_by_symbol(symbol_ref)
.iter()
.any(|id| self.ctx.module.stmt_infos[*id].is_included);
if is_included {
let canonical_name = self.canonical_name_for(*symbol_ref);
program.body.push(self.snippet.var_decl_stmt(canonical_name, self.snippet.void_zero()));
}
});

// visit children
for directive in program.directives.iter_mut() {
self.visit_directive(directive);
Expand Down
58 changes: 48 additions & 10 deletions crates/rolldown/src/stages/link_stage/bind_imports_and_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ use rolldown_common::{
SymbolRef,
};

use crate::types::{
linking_metadata::{LinkingMetadata, LinkingMetadataVec},
match_import_kind::MatchImportKind,
module_table::NormalModuleVec,
namespace_alias::NamespaceAlias,
use crate::{
types::{
linking_metadata::{LinkingMetadata, LinkingMetadataVec},
match_import_kind::MatchImportKind,
module_table::NormalModuleVec,
namespace_alias::NamespaceAlias,
symbols::Symbols,
},
SharedOptions,
};

use super::LinkStage;
Expand Down Expand Up @@ -57,7 +61,14 @@ impl<'a> LinkStage<'a> {
};
let importee = &self.module_table.normal_modules[importee_id];

match Self::match_import_with_export(importer, importee, &self.metas[importee.id], import) {
match Self::match_import_with_export(
importer,
importee,
&mut self.metas[importee.id],
import,
&mut self.symbols,
self.input_options,
) {
MatchImportKind::NotFound => panic!("info {import:#?}"),
MatchImportKind::PotentiallyAmbiguous(
symbol_ref,
Expand All @@ -67,7 +78,9 @@ impl<'a> LinkStage<'a> {
if Self::determine_ambiguous_export(
&self.module_table.normal_modules,
potentially_ambiguous_symbol_refs,
&self.metas,
&mut self.metas,
&mut self.symbols,
self.input_options,
) {
// ambiguous export
panic!("ambiguous export");
Expand Down Expand Up @@ -100,8 +113,10 @@ impl<'a> LinkStage<'a> {
pub fn match_import_with_export(
importer: &NormalModule,
importee: &NormalModule,
importee_meta: &LinkingMetadata,
importee_meta: &mut LinkingMetadata,
import: &NamedImport,
symbols: &mut Symbols,
options: &SharedOptions,
) -> MatchImportKind {
// If importee module is commonjs module, it will generate property access to namespace symbol
// The namespace symbols should be importer created local symbol.
Expand Down Expand Up @@ -141,6 +156,20 @@ impl<'a> LinkStage<'a> {
if importee_meta.has_dynamic_exports {
return MatchImportKind::Namespace(importee.namespace_symbol);
}
if options.shim_missing_exports {
match &import.imported {
Specifier::Star => unreachable!("star should always exist, no need to shim"),
Specifier::Literal(imported_name) => {
// TODO: should emit warnings for shimmed exports
let shimmed_symbol_ref =
importee_meta.shimmed_missing_exports.entry(imported_name.clone()).or_insert_with(
|| symbols.create_symbol(importee.id, imported_name.clone().to_string().into()),
);

return MatchImportKind::Found(*shimmed_symbol_ref);
}
}
}

MatchImportKind::NotFound
}
Expand All @@ -149,7 +178,9 @@ impl<'a> LinkStage<'a> {
fn determine_ambiguous_export(
modules: &NormalModuleVec,
potentially_ambiguous_symbol_refs: Vec<SymbolRef>,
metas: &LinkingMetadataVec,
metas: &mut LinkingMetadataVec,
symbols: &mut Symbols,
options: &SharedOptions,
) -> bool {
let mut results = vec![];

Expand All @@ -161,7 +192,14 @@ impl<'a> LinkStage<'a> {
continue;
};
let importee = &modules[importee_id];
results.push(Self::match_import_with_export(importer, importee, &metas[importee_id], info));
results.push(Self::match_import_with_export(
importer,
importee,
&mut metas[importee_id],
info,
symbols,
options,
));
} else {
results.push(MatchImportKind::Found(symbol_ref));
}
Expand Down
13 changes: 13 additions & 0 deletions crates/rolldown/src/stages/link_stage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ impl<'a> LinkStage<'a> {
init_entry_point_stmt_info(module, linking_info);
}

linking_info.shimmed_missing_exports.iter().for_each(|(_name, symbol_ref)| {
let stmt_info = StmtInfo {
stmt_idx: None,
declared_symbols: vec![*symbol_ref],
referenced_symbols: vec![],
side_effect: false,
is_included: false,
import_records: Vec::new(),
debug_label: None,
};
module.stmt_infos.add_stmt_info(stmt_info);
});

if matches!(module.exports_kind, ExportsKind::Esm) {
let linking_info = &self.metas[module.id];
let mut referenced_symbols = vec![];
Expand Down
1 change: 1 addition & 0 deletions crates/rolldown/src/types/linking_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub struct LinkingMetadata {
// The unknown export name will be resolved at runtime.
// esbuild add it to `ExportKind`, but the linker shouldn't mutate the module.
pub has_dynamic_exports: bool,
pub shimmed_missing_exports: FxHashMap<Rstr, SymbolRef>,
}

impl LinkingMetadata {
Expand Down
1 change: 1 addition & 0 deletions crates/rolldown/src/utils/normalize_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub fn normalize_options(mut raw_options: crate::BundlerOptions) -> NormalizeOpt
dir: raw_options.dir.unwrap_or_else(|| "dist".to_string()),
format: raw_options.format.unwrap_or(crate::OutputFormat::Esm),
sourcemap: raw_options.sourcemap.unwrap_or(SourceMapType::Hidden),
shim_missing_exports: raw_options.shim_missing_exports.unwrap_or(false),
};

NormalizeOptionsReturn { options: normalized, resolve_options: raw_resolve }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"config": {
"shimMissingExports": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import nodeAssert from 'assert'
import { missing } from './dist/main.mjs'

nodeAssert.strictEqual(missing, undefined)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: crates/rolldown/tests/common/case.rs
expression: content
input_file: crates/rolldown/tests/fixtures/function/shim_missing_exports/basic
---
# Assets

## main.mjs

```js
// foo.js
var missing = void 0;

export { missing };
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { missing } from './foo'

export { missing }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"config": {
"shimMissingExports": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import nodeAssert from 'assert'
import { missing } from './dist/main.mjs'

nodeAssert.strictEqual(missing, undefined)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: crates/rolldown/tests/common/case.rs
expression: content
input_file: crates/rolldown/tests/fixtures/function/shim_missing_exports/basic_wrapped_esm
---
# Assets

## main.mjs

```js
import { __esmMin, __toCommonJS } from "./$runtime$.mjs";

// foo.js
var foo_ns, missing;
var init_foo = __esmMin(() => {
foo_ns = {};
missing = void 0;
});

// main.js
init_foo();
init_foo(),__toCommonJS(foo_ns);

export { missing };
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { missing } from './foo'

require('./foo')

export { missing }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"config": {
"shimMissingExports": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import nodeAssert from 'assert'
import { missing } from './dist/main.mjs'

nodeAssert.strictEqual(missing, undefined)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: crates/rolldown/tests/common/case.rs
expression: content
input_file: crates/rolldown/tests/fixtures/function/shim_missing_exports/shake_unused_shimmed_exports
---
# Assets

## main.mjs

```js
// foo.js
var missing = void 0;

export { missing };
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { missing, unusedMissing } from './foo'

export { missing }
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub struct BindingInputOptions {
// /** @deprecated Use the "preserveModules" output option instead. */
// preserveModules?: boolean;
// pub preserve_symlinks: bool,
// pub shim_missing_exports: bool,
pub shim_missing_exports: Option<bool>,
// strictDeprecations?: boolean;
// pub treeshake: Option<bool>,
// watch?: WatcherOptions | false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub fn normalize_binding_options(
.map(Platform::try_from)
.transpose()
.map_err(|err| napi::Error::new(napi::Status::GenericFailure, err))?,
shim_missing_exports: input_options.shim_missing_exports,
entry_file_names: output_options.entry_file_names,
chunk_file_names: output_options.chunk_file_names,
dir: output_options.dir,
Expand Down
3 changes: 2 additions & 1 deletion crates/rolldown_common/src/inner_bundler_options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ pub mod types;
#[derive(Default, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BundlerOptions {
// --- options for output
// --- options for input
pub input: Option<Vec<InputItem>>,
pub cwd: Option<PathBuf>,
#[serde(default, deserialize_with = "deserialize_external")]
#[schemars(with = "Option<Vec<String>>")]
pub external: Option<External>,
pub treeshake: Option<bool>,
pub platform: Option<Platform>,
pub shim_missing_exports: Option<bool>,
// --- options for output
pub entry_file_names: Option<String>,
pub chunk_file_names: Option<String>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ use super::{

#[derive(Debug)]
pub struct NormalizedBundlerOptions {
// --- Input
pub input: Vec<InputItem>,
pub cwd: PathBuf,
pub external: External,
pub treeshake: bool,
pub platform: Platform,
pub shim_missing_exports: bool,
// --- Output
pub entry_file_names: FileNameTemplate,
pub chunk_file_names: FileNameTemplate,
pub dir: String,
Expand Down
13 changes: 13 additions & 0 deletions crates/rolldown_oxc_utils/src/ast_snippet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use oxc::{
allocator::{self, Allocator},
ast::ast::{self, Statement},
span::{Atom, Span, SPAN},
syntax::operator::UnaryOperator,
};

use crate::{Dummy, IntoIn};
Expand Down Expand Up @@ -324,4 +325,16 @@ impl<'ast> AstSnippet<'ast> {
.into_in(self.alloc),
)
}

// `undefined` is acting like identifier, it might be shadowed by user code.
pub fn void_zero(&self) -> ast::Expression<'ast> {
ast::Expression::UnaryExpression(
ast::UnaryExpression {
operator: UnaryOperator::Void,
argument: self.number_expr(0.0),
..Dummy::dummy(self.alloc)
}
.into_in(self.alloc),
)
}
}

0 comments on commit 2c84963

Please sign in to comment.