diff --git a/crates/rspack_plugin_esm_library/src/link.rs b/crates/rspack_plugin_esm_library/src/link.rs index 6bed8f0c6c24..5a29f376bfee 100644 --- a/crates/rspack_plugin_esm_library/src/link.rs +++ b/crates/rspack_plugin_esm_library/src/link.rs @@ -13,9 +13,10 @@ use rspack_core::{ IdentCollector, MaybeDynamicTargetExportInfoHashKey, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, ModuleInfo, NAMESPACE_OBJECT_EXPORT, PathData, PrefetchExportsInfoMode, RuntimeGlobals, SourceType, URLStaticMode, UsageState, UsedName, UsedNameItem, escape_name, - find_new_name, get_cached_readable_identifier, get_js_chunk_filename_template, property_access, - property_name, reserved_names::RESERVED_NAMES, rspack_sources::ReplaceSource, - split_readable_identifier, to_normal_comment, + find_new_name, get_cached_readable_identifier, get_js_chunk_filename_template, + get_module_directives, get_module_hashbang, property_access, property_name, + reserved_names::RESERVED_NAMES, rspack_sources::ReplaceSource, split_readable_identifier, + to_normal_comment, }; use rspack_error::{Diagnostic, Result}; use rspack_javascript_compiler::ast::Ast; @@ -1241,6 +1242,43 @@ var {} = {{}}; let entry_module_chunk = Self::get_module_chunk(entry_module, compilation); entry_imports.entry(entry_module).or_default(); + // NOTE: Similar hashbang and directives handling logic. + // See rspack_plugin_rslib/src/plugin.rs render() for why this duplication is necessary. + let hashbang = get_module_hashbang(module_graph, &entry_module); + let directives = get_module_directives(module_graph, &entry_module); + + if let Some(hashbang) = &hashbang { + let entry_chunk_link = link.get_mut_unwrap(&entry_chunk_ukey); + entry_chunk_link.init_fragments.insert( + 0, + Box::new(rspack_core::NormalInitFragment::new( + format!("{hashbang}\n"), + rspack_core::InitFragmentStage::StageConstants, + i32::MIN, + rspack_core::InitFragmentKey::unique(), + None, + )), + ); + } + + if let Some(directives) = directives { + let entry_module_chunk_link = link.get_mut_unwrap(&entry_module_chunk); + + for (idx, directive) in directives.iter().enumerate() { + let insert_pos = if hashbang.is_some() { 1 + idx } else { idx }; + entry_module_chunk_link.init_fragments.insert( + insert_pos, + Box::new(rspack_core::NormalInitFragment::new( + format!("{directive}\n"), + rspack_core::InitFragmentStage::StageConstants, + i32::MIN + 1 + idx as i32, + rspack_core::InitFragmentKey::unique(), + None, + )), + ); + } + } + /* entry module sometimes are splitted to whatever chunk user needs, so the entry chunk maynot actually contains entry modules diff --git a/crates/rspack_plugin_esm_library/src/render.rs b/crates/rspack_plugin_esm_library/src/render.rs index b977b2324e3e..79eaa5d93082 100644 --- a/crates/rspack_plugin_esm_library/src/render.rs +++ b/crates/rspack_plugin_esm_library/src/render.rs @@ -6,7 +6,7 @@ use rspack_core::{ AssetInfo, Chunk, ChunkGraph, ChunkRenderContext, ChunkUkey, CodeGenerationDataFilename, Compilation, ConcatenatedModuleInfo, DependencyId, InitFragment, ModuleIdentifier, PathData, PathInfo, RuntimeGlobals, RuntimeVariable, SourceType, get_js_chunk_filename_template, - get_module_directives, get_module_hashbang, get_undo_path, render_init_fragments, + get_undo_path, render_init_fragments, rspack_sources::{ConcatSource, RawStringSource, ReplaceSource, Source, SourceExt}, }; use rspack_error::Result; @@ -86,48 +86,6 @@ impl EsmLibraryPlugin { let mut chunk_init_fragments: Vec + 'static>> = chunk_link.init_fragments.clone(); - // NOTE: Similar hashbang and directives handling logic. - // See rspack_plugin_rslib/src/plugin.rs render() for why this duplication is necessary. - let entry_modules = compilation.chunk_graph.get_chunk_entry_modules(chunk_ukey); - for entry_module_id in &entry_modules { - let hashbang = get_module_hashbang(module_graph, entry_module_id); - let directives = get_module_directives(module_graph, entry_module_id); - - if hashbang.is_none() && directives.is_none() { - continue; - } - - if let Some(hashbang) = &hashbang { - chunk_init_fragments.insert( - 0, - Box::new(rspack_core::NormalInitFragment::new( - format!("{hashbang}\n"), - rspack_core::InitFragmentStage::StageConstants, - i32::MIN, - rspack_core::InitFragmentKey::unique(), - None, - )), - ); - } - - if let Some(directives) = directives { - for (idx, directive) in directives.iter().enumerate() { - let insert_pos = if hashbang.is_some() { 1 + idx } else { idx }; - chunk_init_fragments.insert( - insert_pos, - Box::new(rspack_core::NormalInitFragment::new( - format!("{directive}\n"), - rspack_core::InitFragmentStage::StageConstants, - i32::MIN + 1 + idx as i32, - rspack_core::InitFragmentKey::unique(), - None, - )), - ); - } - } - break; // Only process the first entry module with hashbang/directives - } - let mut replace_auto_public_path = false; let mut replace_static_url = false; diff --git a/tests/rspack-test/esmOutputCases/basic/shebang/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/basic/shebang/__snapshots__/esm.snap.txt new file mode 100644 index 000000000000..34d45b4c2b66 --- /dev/null +++ b/tests/rspack-test/esmOutputCases/basic/shebang/__snapshots__/esm.snap.txt @@ -0,0 +1,27 @@ +```mjs title=main.mjs +#!/usr/bin/env node +import "./splitMain-index_js.mjs"; + + +``` + +```mjs title=splitMain-index_js.mjs +import {fileURLToPath as __rspack_fileURLToPath} from "node:url"; +import {dirname as __rspack_dirname} from "node:path"; +import { createRequire as __rspack_createRequire } from "node:module"; +const __rspack_createRequire_require = __rspack_createRequire(import.meta.url); +// ./index.js +var index_dirname = __rspack_dirname(__rspack_fileURLToPath(import.meta.url)); +// + + +it('should have shebang in entry chunk', () => { + const fs = __rspack_createRequire_require('fs') + const path = __rspack_createRequire_require('path') + + const code = fs.readFileSync(path.join(index_dirname, 'main.mjs'), 'utf-8') + + expect(code.startsWith('#!')).toBeTruthy() +}) + +``` \ No newline at end of file diff --git a/tests/rspack-test/esmOutputCases/basic/shebang/index.js b/tests/rspack-test/esmOutputCases/basic/shebang/index.js new file mode 100644 index 000000000000..b3015f6c25d7 --- /dev/null +++ b/tests/rspack-test/esmOutputCases/basic/shebang/index.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + + +it('should have shebang in entry chunk', () => { + const fs = __non_webpack_require__('fs') + const path = __non_webpack_require__('path') + + const code = fs.readFileSync(path.join(__dirname, 'main.mjs'), 'utf-8') + + expect(code.startsWith('#!')).toBeTruthy() +}) \ No newline at end of file diff --git a/tests/rspack-test/esmOutputCases/basic/shebang/rspack.config.js b/tests/rspack-test/esmOutputCases/basic/shebang/rspack.config.js new file mode 100644 index 000000000000..4c739fbc893f --- /dev/null +++ b/tests/rspack-test/esmOutputCases/basic/shebang/rspack.config.js @@ -0,0 +1,16 @@ +const rspack = require('@rspack/core') + +module.exports = { + optimization: { + splitChunks: { + cacheGroups: { + splitMain: { + test: /index\.js/ + } + } + } + }, + plugins: [ + new rspack.experiments.RslibPlugin() + ] +} \ No newline at end of file