Skip to content

Commit

Permalink
feat: support output.globals for iife format (#1694)
Browse files Browse the repository at this point in the history
In this PR, I addressed issues related to `output.exports`, particularly
for the `IIFE` format, and extended the solution to handle external
modules.

resolves #1623.
resolves #1569.

### Modifications

1. **Support for `output.globals` Key**: 
- Implemented support for the `{ [id: string]: string }` type in the
`globals` key.
- Note: Although Rollup supports both `{ [id: string]: string }` and
`((id: string) => string)`, this PR only includes support for the
former. Future PRs may add support for the function type.

2. **Arguments Handling for `IIFE` Format**: 
   - Adapted the handler function from `CJS`.
   - Utilized `filter_map` to identify and manage used external modules.
- Passed these modules to the `render_iife_arguments` function,
leveraging the `globals` key.
   - Open to suggestions for more elegant solutions.

### Tests

- Tests for `iife/external_modules` passed. (The actual behavior aligns
with rollup)
- Added new tests for `iife/external_modules_with_globals` and
JavaScript side tests.

---------

Co-authored-by: underfin <[email protected]>
  • Loading branch information
7086cmd and underfin authored Jul 23, 2024
1 parent 2b15a70 commit 8aad7d0
Show file tree
Hide file tree
Showing 28 changed files with 228 additions and 16 deletions.
2 changes: 1 addition & 1 deletion crates/rolldown/src/ecmascript/ecma_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl Generator for EcmaGenerator {
Err(errors) => return Ok(Err(errors)),
},
OutputFormat::App => render_app(ctx, rendered_module_sources, banner, footer),
OutputFormat::Iife => match render_iife(ctx, rendered_module_sources, banner, footer) {
OutputFormat::Iife => match render_iife(ctx, rendered_module_sources, banner, footer, true) {
Ok(concat_source) => concat_source,
Err(errors) => return Ok(Err(errors)),
},
Expand Down
89 changes: 84 additions & 5 deletions crates/rolldown/src/ecmascript/format/iife.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use crate::utils::chunk::collect_render_chunk_imports::{
collect_render_chunk_imports, RenderImportDeclarationSpecifier,
};
use crate::{
ecmascript::ecma_generator::RenderedModuleSources,
types::generator::GenerateContext,
Expand All @@ -8,12 +11,16 @@ use crate::{
use rolldown_common::{ChunkKind, OutputExports};
use rolldown_error::DiagnosableResult;
use rolldown_sourcemap::{ConcatSource, RawSource};
use rolldown_utils::ecma_script::legitimize_identifier_name;
use rustc_hash::FxHashMap;

// TODO refactor it to `wrap.rs` to reuse it for other formats (e.g. amd, umd).
pub fn render_iife(
ctx: &mut GenerateContext<'_>,
module_sources: RenderedModuleSources,
banner: Option<String>,
footer: Option<String>,
invoke: bool,
) -> DiagnosableResult<ConcatSource> {
let mut concat_source = ConcatSource::default();

Expand All @@ -36,13 +43,20 @@ pub fn render_iife(
OutputExports::Named
);

let (import_code, externals) = render_iife_chunk_imports(ctx);

let (input_args, output_args) =
render_iife_arguments(&externals, &ctx.options.globals, has_exports && named_exports);

concat_source.add_source(Box::new(RawSource::new(format!(
"{}(function({}) {{\n",
if let Some(name) = &ctx.options.name { format!("var {name} = ") } else { String::new() },
// TODO handle external imports here.
if has_exports && named_exports { "exports" } else { "" }
input_args
))));

concat_source.add_source(Box::new(RawSource::new(import_code)));

// TODO iife imports

// chunk content
Expand All @@ -65,14 +79,79 @@ pub fn render_iife(
}

// iife wrapper end
concat_source.add_source(Box::new(RawSource::new(format!(
"}})({});",
if has_exports && named_exports { "{}" } else { "" }
))));
if invoke {
concat_source.add_source(Box::new(RawSource::new(format!("}})({output_args});"))));
} else {
concat_source.add_source(Box::new(RawSource::new("})".to_string())));
}

if let Some(footer) = footer {
concat_source.add_source(Box::new(RawSource::new(footer)));
}

Ok(concat_source)
}

// Handling external imports needs to modify the arguments of the wrapper function.
fn render_iife_chunk_imports(ctx: &GenerateContext<'_>) -> (String, Vec<String>) {
let render_import_stmts =
collect_render_chunk_imports(ctx.chunk, ctx.link_output, ctx.chunk_graph);

let mut s = String::new();
let externals: Vec<String> = render_import_stmts
.iter()
.filter_map(|stmt| {
let require_path_str = &stmt.path;
match &stmt.specifiers {
RenderImportDeclarationSpecifier::ImportSpecifier(specifiers) => {
// Empty specifiers can be ignored in IIFE.
if specifiers.is_empty() {
None
} else {
let specifiers = specifiers
.iter()
.map(|specifier| {
if let Some(alias) = &specifier.alias {
format!("{}: {alias}", specifier.imported)
} else {
specifier.imported.to_string()
}
})
.collect::<Vec<_>>();
s.push_str(&format!(
"const {{ {} }} = {};\n",
specifiers.join(", "),
legitimize_identifier_name(&stmt.path)
));
Some(require_path_str.to_string())
}
}
RenderImportDeclarationSpecifier::ImportStarSpecifier(alias) => {
s.push_str(&format!("const {alias} = {};\n", legitimize_identifier_name(&stmt.path)));
Some(require_path_str.to_string())
}
}
})
.collect();

(s, externals)
}

fn render_iife_arguments(
externals: &[String],
globals: &FxHashMap<String, String>,
exports_key: bool,
) -> (String, String) {
let mut input_args = if exports_key { vec!["exports".to_string()] } else { vec![] };
let mut output_args = if exports_key { vec!["{}".to_string()] } else { vec![] };
externals.iter().for_each(|external| {
input_args.push(legitimize_identifier_name(external).to_string());
if let Some(global) = globals.get(external) {
output_args.push(legitimize_identifier_name(global).to_string());
} else {
// TODO add warning for missing global
output_args.push(legitimize_identifier_name(external).to_string());
}
});
(input_args.join(", "), output_args.join(", "))
}
4 changes: 4 additions & 0 deletions crates/rolldown/src/utils/normalize_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub fn normalize_options(mut raw_options: crate::BundlerOptions) -> NormalizeOpt

loaders.extend(user_defined_loaders);

let globals: FxHashMap<String, String> =
raw_options.globals.map(|globals| globals.into_iter().collect()).unwrap_or_default();

let normalized = NormalizedBundlerOptions {
input: raw_options.input.unwrap_or_default(),
cwd: raw_options
Expand All @@ -67,6 +70,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),
exports: raw_options.exports.unwrap_or(crate::OutputExports::Auto),
globals,
sourcemap: raw_options.sourcemap.unwrap_or(SourceMapType::Hidden),
sourcemap_ignore_list: raw_options.sourcemap_ignore_list,
sourcemap_path_transform: raw_options.sourcemap_path_transform,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ input_file: crates/rolldown/tests/esbuild/dce/tree_shaking_no_bundle_iife
```js
(function() {
//#region entry.js
function keep() {}
keep();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ input_file: crates/rolldown/tests/fixtures/export_mode/iife/auto/none
```js
var MyLibrary = (function() {
//#region main.js
console.log("none");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ input_file: crates/rolldown/tests/fixtures/export_mode/iife/default
(function() {
//#region mod.js
var default_mod_ns = {};
__export(default_mod_ns, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ input_file: crates/rolldown/tests/fixtures/export_mode/iife/named
var module = (function(exports) {
//#region mod.js
var named_mod_ns = {};
__export(named_mod_ns, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ input_file: crates/rolldown/tests/fixtures/format/iife-with-name
```js
var myModule = (function(exports) {
//#region foo.js
const value = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ input_file: crates/rolldown/tests/fixtures/format/iife/basic
(function() {
//#region main.js
(Promise.resolve()).then(function() {
return __toESM(require_foo());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ input_file: crates/rolldown/tests/fixtures/format/iife/external_modules
## main.mjs

```js
(function() {
(function(node_path) {
const { default: nodePath } = node_path;
//#region main.js
console.log(nodePath);
//#endregion
})();
})(node_path);
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"config": {
"format": "iife",
"external": [
"node:path"
],
"globals": {
"node:path": "path"
}
},
"expectExecuted": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: crates/rolldown_testing/src/case/case.rs
expression: content
input_file: crates/rolldown/tests/fixtures/format/iife/external_modules_with_globals
---
# Assets

## main.mjs

```js
(function(node_path) {
const { default: nodePath } = node_path;
//#region main.js
console.log(nodePath);
//#endregion
})(path);
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import nodePath from 'node:path'
console.log(nodePath)
18 changes: 11 additions & 7 deletions crates/rolldown/tests/snapshots/fixtures__filename_with_hash.snap
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ expression: "snapshot_outputs.join(\"\\n\")"

# tests/esbuild/dce/tree_shaking_no_bundle_iife

- entry_js-!~{000}~.mjs => entry_js-GGqT8yME.mjs
- entry_js-!~{000}~.mjs => entry_js-k-4tRlGF.mjs

# tests/esbuild/dce/tree_shaking_object_property

Expand Down Expand Up @@ -1471,27 +1471,31 @@ expression: "snapshot_outputs.join(\"\\n\")"

# tests/fixtures/export_mode/iife/auto/none

- main-!~{000}~.mjs => main-i3A7VQCp.mjs
- main-!~{000}~.mjs => main-MXkFYr18.mjs

# tests/fixtures/export_mode/iife/default

- main-!~{000}~.mjs => main-NX6zjtAS.mjs
- main-!~{000}~.mjs => main-BrUsoNkw.mjs

# tests/fixtures/export_mode/iife/named

- main-!~{000}~.mjs => main-4V7Vil83.mjs
- main-!~{000}~.mjs => main-jnVxPvdX.mjs

# tests/fixtures/format/iife/basic

- main-!~{000}~.mjs => main-6h1ciOdZ.mjs
- main-!~{000}~.mjs => main-Z44yy_wa.mjs

# tests/fixtures/format/iife/external_modules

- main-!~{000}~.mjs => main-_QyxBx4O.mjs
- main-!~{000}~.mjs => main-BBs_LVjj.mjs

# tests/fixtures/format/iife/external_modules_with_globals

- main-!~{000}~.mjs => main-0mbu-Y_o.mjs

# tests/fixtures/format/iife-with-name

- main-!~{000}~.mjs => main-VYJo3mZR.mjs
- main-!~{000}~.mjs => main-XwjODux8.mjs

# tests/fixtures/function/dir/should_generate_correct_relative_import_path

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::types::js_callback::MaybeAsyncJsCallback;
use std::collections::HashMap;

use super::super::types::binding_rendered_chunk::RenderedChunk;
use super::plugin::BindingPluginOrParallelJsPluginPlaceholder;
Expand Down Expand Up @@ -45,7 +46,7 @@ pub struct BindingOutputOptions {
pub format: Option<String>,
// freeze: boolean;
// generatedCode: NormalizedGeneratedCodeOptions;
// globals: GlobalsOption;
pub globals: Option<HashMap<String, String>>,
// hoistTransitiveImports: boolean;
// indent: true | string;
// inlineDynamicImports: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ pub fn normalize_binding_options(
"iife" => OutputFormat::Iife,
_ => panic!("Invalid format: {format_str}"),
}),
globals: output_options.globals,
module_types,
experimental: None,
minify: output_options.minify,
Expand Down
1 change: 1 addition & 0 deletions crates/rolldown_common/src/inner_bundler_options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub struct BundlerOptions {
pub dir: Option<String>,
pub format: Option<OutputFormat>,
pub exports: Option<OutputExports>,
pub globals: Option<HashMap<String, String>>,
pub sourcemap: Option<SourceMapType>,
#[cfg_attr(
feature = "deserialize_bundler_options",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub struct NormalizedBundlerOptions {
pub dir: String,
pub format: OutputFormat,
pub exports: OutputExports,
pub globals: FxHashMap<String, String>,
pub sourcemap: SourceMapType,
pub banner: Option<AddonOutputOption>,
pub footer: Option<AddonOutputOption>,
Expand Down
9 changes: 9 additions & 0 deletions crates/rolldown_testing/_config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@
}
]
},
"globals": {
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "string"
}
},
"input": {
"type": [
"array",
Expand Down
1 change: 1 addition & 0 deletions packages/rolldown/src/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export interface BindingOutputOptions {
exports?: 'default' | 'named' | 'none' | 'auto'
footer?: (chunk: RenderedChunk) => MaybePromise<VoidNullable<string>>
format?: 'es' | 'cjs' | 'iife'
globals?: Record<string, string>
plugins: (BindingBuiltinPlugin | BindingPluginOptions | undefined)[]
sourcemap?: 'file' | 'inline' | 'hidden'
sourcemapIgnoreList?: (source: string, sourcemapPath: string) => boolean
Expand Down
1 change: 1 addition & 0 deletions packages/rolldown/src/options/output-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const outputOptionsSchema = z.strictObject({
assetFileNames: z.string().optional(),
minify: z.boolean().optional(),
name: z.string().optional(),
globals: z.record(z.string()).optional(),
})

export type OutputOptions = z.infer<typeof outputOptionsSchema>
Expand Down
3 changes: 3 additions & 0 deletions packages/rolldown/src/utils/normalize-output-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function normalizeOutputOptions(
sourcemap,
sourcemapIgnoreList,
sourcemapPathTransform,
globals,
entryFileNames,
chunkFileNames,
assetFileNames,
Expand All @@ -32,6 +33,8 @@ export function normalizeOutputOptions(
sourcemapPathTransform,
banner: getAddon(opts, 'banner'),
footer: getAddon(opts, 'footer'),
// TODO support functions
globals: globals ?? {},
entryFileNames: entryFileNames ?? '[name].js',
chunkFileNames: chunkFileNames ?? '[name]-[hash].js',
assetFileNames: assetFileNames ?? 'assets/[name]-[hash][extname]',
Expand Down
Loading

0 comments on commit 8aad7d0

Please sign in to comment.