|
| 1 | +use arcstr::ArcStr; |
| 2 | +use rolldown_common::{ChunkKind, OutputExports}; |
| 3 | +use rolldown_error::{BuildDiagnostic, DiagnosableResult}; |
| 4 | +use rolldown_sourcemap::{ConcatSource, RawSource}; |
| 5 | +use rolldown_utils::ecma_script::legitimize_identifier_name; |
| 6 | + |
| 7 | +use crate::{ |
| 8 | + ecmascript::{ |
| 9 | + ecma_generator::RenderedModuleSources, format::utils::namespace::generate_namespace_definition, |
| 10 | + }, |
| 11 | + types::generator::GenerateContext, |
| 12 | + utils::chunk::{ |
| 13 | + collect_render_chunk_imports::ExternalRenderImportStmt, |
| 14 | + determine_export_mode::determine_export_mode, |
| 15 | + determine_use_strict::determine_use_strict, |
| 16 | + namespace_marker::render_namespace_markers, |
| 17 | + render_chunk_exports::{get_export_items, render_chunk_exports}, |
| 18 | + }, |
| 19 | +}; |
| 20 | + |
| 21 | +use super::utils::{ |
| 22 | + namespace::render_property_access, render_chunk_external_imports, render_factory_parameters, |
| 23 | +}; |
| 24 | + |
| 25 | +pub fn render_umd( |
| 26 | + ctx: &mut GenerateContext<'_>, |
| 27 | + module_sources: RenderedModuleSources, |
| 28 | + banner: Option<String>, |
| 29 | + footer: Option<String>, |
| 30 | + intro: Option<String>, |
| 31 | + outro: Option<String>, |
| 32 | +) -> DiagnosableResult<ConcatSource> { |
| 33 | + let mut concat_source = ConcatSource::default(); |
| 34 | + |
| 35 | + if let Some(banner) = banner { |
| 36 | + concat_source.add_source(Box::new(RawSource::new(banner))); |
| 37 | + } |
| 38 | + |
| 39 | + // umd wrapper start |
| 40 | + |
| 41 | + // Analyze the export information of the chunk. |
| 42 | + let export_items = get_export_items(ctx.chunk, ctx.link_output); |
| 43 | + let has_exports = !export_items.is_empty(); |
| 44 | + let has_default_export = export_items.iter().any(|(name, _)| name.as_str() == "default"); |
| 45 | + |
| 46 | + let entry_module = match ctx.chunk.kind { |
| 47 | + ChunkKind::EntryPoint { module, .. } => { |
| 48 | + &ctx.link_output.module_table.modules[module].as_normal().expect("should be normal module") |
| 49 | + } |
| 50 | + ChunkKind::Common => unreachable!("umd should be entry point chunk"), |
| 51 | + }; |
| 52 | + |
| 53 | + // We need to transform the `OutputExports::Auto` to suitable `OutputExports`. |
| 54 | + let export_mode = determine_export_mode(ctx, entry_module, &export_items)?; |
| 55 | + |
| 56 | + let named_exports = matches!(&export_mode, OutputExports::Named); |
| 57 | + |
| 58 | + // It is similar to CJS. |
| 59 | + let (import_code, externals) = render_chunk_external_imports(ctx); |
| 60 | + |
| 61 | + // The function argument and the external imports are passed as arguments to the wrapper function. |
| 62 | + let need_global = has_exports || named_exports || !externals.is_empty(); |
| 63 | + let wrapper_parameters = if need_global { "global, factory" } else { "factory" }; |
| 64 | + let amd_dependencies = render_amd_dependencies(&externals, has_exports && named_exports); |
| 65 | + let global_argument = if need_global { "this, " } else { "" }; |
| 66 | + let factory_parameters = render_factory_parameters(ctx, &externals, has_exports && named_exports); |
| 67 | + let cjs_intro = if need_global { |
| 68 | + let cjs_export = if has_exports && !named_exports { "module.exports = " } else { "" }; |
| 69 | + let cjs_dependencies = render_cjs_dependencies(&externals, has_exports && named_exports); |
| 70 | + format!("typeof exports === 'object' && typeof module !== 'undefined' ? {cjs_export} factory({cjs_dependencies}) :",) |
| 71 | + } else { |
| 72 | + String::new() |
| 73 | + }; |
| 74 | + let iife_start = if need_global { |
| 75 | + "(global = typeof globalThis !== 'undefined' ? globalThis : global || self, " |
| 76 | + } else { |
| 77 | + "" |
| 78 | + }; |
| 79 | + let iife_end = if need_global { ")" } else { "" }; |
| 80 | + let iife_export = render_iife_export(ctx, &externals, has_exports, named_exports)?; |
| 81 | + concat_source.add_source(Box::new(RawSource::new(format!( |
| 82 | + "(function({wrapper_parameters}) {{ |
| 83 | + {cjs_intro} |
| 84 | + typeof define === 'function' && define.amd ? define([{amd_dependencies}], factory) : |
| 85 | + {iife_start}{iife_export}{iife_end}; |
| 86 | +}})({global_argument}function({factory_parameters}) {{", |
| 87 | + )))); |
| 88 | + |
| 89 | + if determine_use_strict(ctx) { |
| 90 | + concat_source.add_source(Box::new(RawSource::new("\"use strict\";".to_string()))); |
| 91 | + } |
| 92 | + |
| 93 | + if let Some(intro) = intro { |
| 94 | + concat_source.add_source(Box::new(RawSource::new(intro))); |
| 95 | + } |
| 96 | + |
| 97 | + if named_exports { |
| 98 | + if let Some(marker) = |
| 99 | + render_namespace_markers(&ctx.options.es_module, has_default_export, false) |
| 100 | + { |
| 101 | + concat_source.add_source(Box::new(RawSource::new(marker.into()))); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + concat_source.add_source(Box::new(RawSource::new(import_code))); |
| 106 | + |
| 107 | + // chunk content |
| 108 | + // TODO indent chunk content |
| 109 | + module_sources.into_iter().for_each(|(_, _, module_render_output)| { |
| 110 | + if let Some(emitted_sources) = module_render_output { |
| 111 | + for source in emitted_sources { |
| 112 | + concat_source.add_source(source); |
| 113 | + } |
| 114 | + } |
| 115 | + }); |
| 116 | + |
| 117 | + // exports |
| 118 | + if let Some(exports) = render_chunk_exports(ctx, Some(&export_mode)) { |
| 119 | + concat_source.add_source(Box::new(RawSource::new(exports))); |
| 120 | + } |
| 121 | + |
| 122 | + if let Some(outro) = outro { |
| 123 | + concat_source.add_source(Box::new(RawSource::new(outro))); |
| 124 | + } |
| 125 | + |
| 126 | + // umd wrapper end |
| 127 | + concat_source.add_source(Box::new(RawSource::new("});".to_string()))); |
| 128 | + |
| 129 | + if let Some(footer) = footer { |
| 130 | + concat_source.add_source(Box::new(RawSource::new(footer))); |
| 131 | + } |
| 132 | + |
| 133 | + Ok(concat_source) |
| 134 | +} |
| 135 | + |
| 136 | +fn render_amd_dependencies(externals: &[ExternalRenderImportStmt], has_exports: bool) -> String { |
| 137 | + let mut dependencies = Vec::with_capacity(externals.len()); |
| 138 | + if has_exports { |
| 139 | + dependencies.reserve(1); |
| 140 | + dependencies.push("exports".to_string()); |
| 141 | + } |
| 142 | + externals.iter().for_each(|external| { |
| 143 | + dependencies.push(format!("'{}'", external.path.as_str())); |
| 144 | + }); |
| 145 | + dependencies.join(", ") |
| 146 | +} |
| 147 | + |
| 148 | +fn render_cjs_dependencies(externals: &[ExternalRenderImportStmt], has_exports: bool) -> String { |
| 149 | + let mut dependencies = Vec::with_capacity(externals.len()); |
| 150 | + if has_exports { |
| 151 | + dependencies.reserve(1); |
| 152 | + dependencies.push("exports".to_string()); |
| 153 | + } |
| 154 | + externals.iter().for_each(|external| { |
| 155 | + dependencies.push(format!("require('{}')", external.path.as_str())); |
| 156 | + }); |
| 157 | + dependencies.join(", ") |
| 158 | +} |
| 159 | + |
| 160 | +fn render_iife_export( |
| 161 | + ctx: &mut GenerateContext<'_>, |
| 162 | + externals: &[ExternalRenderImportStmt], |
| 163 | + has_exports: bool, |
| 164 | + named_exports: bool, |
| 165 | +) -> DiagnosableResult<String> { |
| 166 | + if ctx.options.name.as_ref().map_or(true, String::is_empty) { |
| 167 | + return Err(vec![BuildDiagnostic::missing_name_option_for_umd_export()]); |
| 168 | + } |
| 169 | + let (stmt, namespace) = generate_namespace_definition( |
| 170 | + ctx.options.name.as_ref().expect("should have name"), |
| 171 | + "global", |
| 172 | + ",", |
| 173 | + ); |
| 174 | + let mut dependencies = Vec::with_capacity(externals.len()); |
| 175 | + externals.iter().for_each(|external| { |
| 176 | + if let Some(global) = ctx.options.globals.get(external.path.as_str()) { |
| 177 | + dependencies.push(format!( |
| 178 | + "global{}", |
| 179 | + global.split('.').map(render_property_access).collect::<String>() |
| 180 | + )); |
| 181 | + } else { |
| 182 | + let target = legitimize_identifier_name(external.path.as_str()).to_string(); |
| 183 | + ctx.warnings.push( |
| 184 | + BuildDiagnostic::missing_global_name(external.path.clone(), ArcStr::from(&target)) |
| 185 | + .with_severity_warning(), |
| 186 | + ); |
| 187 | + dependencies.push(format!("global{}", render_property_access(&target))); |
| 188 | + } |
| 189 | + }); |
| 190 | + let deps = dependencies.join(","); |
| 191 | + if has_exports { |
| 192 | + if named_exports { |
| 193 | + Ok(format!( |
| 194 | + "factory(({stmt}{namespace} = {}){})", |
| 195 | + if ctx.options.extend { format!("{namespace} || {{}}") } else { "{}".to_string() }, |
| 196 | + if dependencies.is_empty() { String::new() } else { format!(", {deps}") } |
| 197 | + )) |
| 198 | + } else { |
| 199 | + Ok(format!("({stmt}{namespace} = factory({deps}))")) |
| 200 | + } |
| 201 | + } else { |
| 202 | + Ok(format!("factory({deps})")) |
| 203 | + } |
| 204 | +} |
0 commit comments