Skip to content

Commit

Permalink
feat: using dojang to render runtime_template (#9422)
Browse files Browse the repository at this point in the history
* feat: using dojang to render runtime_template

* fix: normalize line endings in runtime template rendering

* refactor: use cow_replace for line ending normalization

* chore: add comment explaining line ending normalization

* fix: update variable names to use camelCase in auto public path template
  • Loading branch information
GiveMe-A-Name authored Feb 25, 2025
1 parent cf4d882 commit 7df06cf
Show file tree
Hide file tree
Showing 19 changed files with 126 additions and 132 deletions.
3 changes: 3 additions & 0 deletions crates/rspack_core/src/runtime_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
sync::{LazyLock, Mutex},
};

use cow_utils::CowUtils;
use itertools::Itertools;
use rspack_dojang::{Context, Dojang, Operand};
use rspack_error::{miette, Result};
Expand Down Expand Up @@ -119,6 +120,8 @@ impl RuntimeTemplate {
file_content,
&mut Mutex::new(HashMap::new()),
)
// Replace Windows-style line endings (\r\n) with Unix-style (\n) to ensure consistent runtime templates across platforms
.map(|render| render.cow_replace("\r\n", "\n").to_string())
.map_err(|err| {
miette::Error::msg(format!(
"Runtime module: failed to render template {key} from: {err}"
Expand Down
65 changes: 24 additions & 41 deletions crates/rspack_plugin_runtime/src/runtime_module/auto_public_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rspack_core::{
get_js_chunk_filename_template, get_undo_path, impl_runtime_module,
rspack_sources::{BoxSource, RawStringSource, SourceExt},
ChunkUkey, Compilation, OutputOptions, PathData, RuntimeGlobals, RuntimeModule,
RuntimeModuleStage, SourceType,
RuntimeModuleStage, RuntimeTemplate, SourceType,
};

#[impl_runtime_module]
Expand Down Expand Up @@ -32,6 +32,13 @@ impl RuntimeModule for AutoPublicPathRuntimeModule {
RuntimeModuleStage::Attach
}

fn template(&self) -> Vec<(String, String)> {
vec![(
self.id.to_string(),
include_str!("runtime/auto_public_path.ejs").to_string(),
)]
}

fn generate(&self, compilation: &Compilation) -> rspack_error::Result<BoxSource> {
let chunk = self.chunk.expect("The chunk should be attached");
let chunk = compilation.chunk_by_ukey.expect_get(&chunk);
Expand Down Expand Up @@ -61,15 +68,22 @@ impl RuntimeModule for AutoPublicPathRuntimeModule {
)?;
Ok(
RawStringSource::from(auto_public_path_template(
&compilation.runtime_template,
&self.id,
&filename,
&compilation.options.output,
))
)?)
.boxed(),
)
}
}

fn auto_public_path_template(filename: &str, output: &OutputOptions) -> String {
fn auto_public_path_template(
runtime_template: &RuntimeTemplate,
id: &str,
filename: &str,
output: &OutputOptions,
) -> rspack_error::Result<String> {
let output_path = output.path.as_str().to_string();
let undo_path = get_undo_path(filename, output_path, false);
let assign = if undo_path.is_empty() {
Expand All @@ -80,45 +94,14 @@ fn auto_public_path_template(filename: &str, output: &OutputOptions) -> String {
RuntimeGlobals::PUBLIC_PATH
)
};
let global = RuntimeGlobals::GLOBAL.name();
let import_meta_name = output.import_meta_name.clone();

let script_url_template = if output.script_type.eq("module") {
format!(
r#"var scriptUrl;
if (typeof {import_meta_name}.url === "string") scriptUrl = {import_meta_name}.url
"#
)
.to_string()
} else {
format!(
r#"var scriptUrl;
if ({global}.importScripts) scriptUrl = {global}.location + "";
var document = {global}.document;
if (!scriptUrl && document) {{
// Technically we could use `document.currentScript instanceof window.HTMLScriptElement`,
// but an attacker could try to inject `<script>HTMLScriptElement = HTMLImageElement</script>`
// and use `<img name="currentScript" src="https://attacker.controlled.server/"></img>`
if (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') scriptUrl = document.currentScript.src;
if (!scriptUrl) {{
var scripts = document.getElementsByTagName("script");
if (scripts.length) {{
var i = scripts.length - 1;
while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;
}}
}}
}}
"#
)
};
format!(
r#"
{script_url_template}
// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
scriptUrl = scriptUrl.replace(/^blob:/, "").replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
{assign}
"#
runtime_template.render(
id,
Some(serde_json::json!({
"script_type": output.script_type,
"import_meta_name": import_meta_name,
"assign": assign
})),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@ impl RuntimeModule for CompatGetDefaultExportRuntimeModule {
self.id
}

fn generate(&self, _compilation: &Compilation) -> rspack_error::Result<BoxSource> {
Ok(RawStringSource::from_static(include_str!("runtime/compat_get_default_export.js")).boxed())
fn template(&self) -> Vec<(String, String)> {
vec![(
self.id.to_string(),
include_str!("runtime/compat_get_default_export.ejs").to_string(),
)]
}

fn generate(&self, compilation: &Compilation) -> rspack_error::Result<BoxSource> {
let source = compilation.runtime_template.render(&self.id, None)?;

Ok(RawStringSource::from(source).boxed())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@ impl RuntimeModule for CreateFakeNamespaceObjectRuntimeModule {
self.id
}

fn generate(&self, _compilation: &Compilation) -> rspack_error::Result<BoxSource> {
Ok(
RawStringSource::from_static(include_str!("runtime/create_fake_namespace_object.js")).boxed(),
)
fn template(&self) -> Vec<(String, String)> {
vec![(
self.id.to_string(),
include_str!("runtime/create_fake_namespace_object.ejs").to_string(),
)]
}

fn generate(&self, compilation: &Compilation) -> rspack_error::Result<BoxSource> {
let source = compilation.runtime_template.render(&self.id, None)?;

Ok(RawStringSource::from(source).boxed())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var scriptUrl;
<% if (script_type == "module") { %>
if (typeof <%- import_meta_name %>.url === "string") scriptUrl = <%- import_meta_name %>.url
<% } else { %>
if (<%- GLOBAL %>.importScripts) scriptUrl = <%- GLOBAL %>.location + "";
var document = <%- GLOBAL %>.document;
if (!scriptUrl && document) {
// Technically we could use `document.currentScript instanceof window.HTMLScriptElement`,
// but an attacker could try to inject `<script>HTMLScriptElement = HTMLImageElement</script>`
// and use `<img name="currentScript" src="https://attacker.controlled.server/"></img>`
if (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') scriptUrl = document.currentScript.src;
if (!scriptUrl) {
var scripts = document.getElementsByTagName("script");
if (scripts.length) {
var i = scripts.length - 1;
while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;
}
}
}
<% } %>
// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
scriptUrl = scriptUrl.replace(/^blob:/, "").replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
<%- assign %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// getDefaultExport function for compatibility with non-ESM modules
<%- COMPAT_GET_DEFAULT_EXPORT %> = <%- basicFunction("module") %> {
var getter = module && module.__esModule ?
<%- returningFunction("module['default']", "") %> :
<%- returningFunction("module", "") %>;
<%- DEFINE_PROPERTY_GETTERS %>(getter, { a: getter });
return getter;
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
var getProto = Object.getPrototypeOf ? function(obj) { return Object.getPrototypeOf(obj); } : function(obj) { return obj.__proto__ };
var getProto = Object.getPrototypeOf ? <%- returningFunction("Object.getPrototypeOf(obj)", "obj") %> : <%- returningFunction("obj.__proto__", "obj") %>;
var leafPrototypes;
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 16: return value when it's Promise-like
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
<%- CREATE_FAKE_NAMESPACE_OBJECT %> = function(value, mode) {
if(mode & 1) value = this(value);
if(mode & 8) return value;
if(typeof value === 'object' && value) {
if((mode & 4) && value.__esModule) return value;
if((mode & 16) && typeof value.then === 'function') return value;
}
var ns = Object.create(null);
__webpack_require__.r(ns);
<%- MAKE_NAMESPACE_OBJECT %>(ns);
var def = {};
leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];
for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {
Object.getOwnPropertyNames(current).forEach(function(key) { def[key] = function() { return value[key]; } });
Object.getOwnPropertyNames(current).forEach(<%- basicFunction("key") %> { def[key] = <%- returningFunction("value[key]", "") %> });
}
def['default'] = function() { return value };
__webpack_require__.d(ns, def);
def['default'] = <%- returningFunction("value", "") %>;
<%- DEFINE_PROPERTY_GETTERS %>(ns, def);
return ns;
};
27 changes: 9 additions & 18 deletions packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -471,17 +471,14 @@ return module.exports;
// webpack/runtime/compat_get_default_export
(() => {
// getDefaultExport function for compatibility with non-ESM modules
__webpack_require__.n = function (module) {
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
() => (module['default']) :
() => (module);
__webpack_require__.d(getter, { a: getter });
return getter;
};
})();
// webpack/runtime/define_property_getters
(() => {
Expand Down Expand Up @@ -611,17 +608,14 @@ return module.exports;
// webpack/runtime/compat_get_default_export
(() => {
// getDefaultExport function for compatibility with non-ESM modules
__webpack_require__.n = function (module) {
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
() => (module['default']) :
() => (module);
__webpack_require__.d(getter, { a: getter });
return getter;
};
})();
// webpack/runtime/define_property_getters
(() => {
Expand Down Expand Up @@ -696,17 +690,14 @@ return module.exports;
// webpack/runtime/compat_get_default_export
(() => {
// getDefaultExport function for compatibility with non-ESM modules
__webpack_require__.n = function (module) {
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
() => (module['default']) :
() => (module);
__webpack_require__.d(getter, { a: getter });
return getter;
};
})();
// webpack/runtime/define_property_getters
(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,17 +471,14 @@ return module.exports;
// webpack/runtime/compat_get_default_export
(() => {
// getDefaultExport function for compatibility with non-ESM modules
__webpack_require__.n = function (module) {
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
() => (module['default']) :
() => (module);
__webpack_require__.d(getter, { a: getter });
return getter;
};
})();
// webpack/runtime/define_property_getters
(() => {
Expand Down Expand Up @@ -611,17 +608,14 @@ return module.exports;
// webpack/runtime/compat_get_default_export
(() => {
// getDefaultExport function for compatibility with non-ESM modules
__webpack_require__.n = function (module) {
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
() => (module['default']) :
() => (module);
__webpack_require__.d(getter, { a: getter });
return getter;
};
})();
// webpack/runtime/define_property_getters
(() => {
Expand Down Expand Up @@ -696,17 +690,14 @@ return module.exports;
// webpack/runtime/compat_get_default_export
(() => {
// getDefaultExport function for compatibility with non-ESM modules
__webpack_require__.n = function (module) {
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
() => (module['default']) :
() => (module);
__webpack_require__.d(getter, { a: getter });
return getter;
};
})();
// webpack/runtime/define_property_getters
(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ runtime modules xx KiB
[no exports]
[used exports unknown]
Rspack compiled successfully (fc4b68e80ae135da)
Rspack compiled successfully (ea70de85332c1294)
`;

exports[`new code splitting stats output new code splitting stats output/builtin-swc-loader-parse-error should print correct stats for: NewCodeSplittingStatsOutput 1`] = `
Expand Down Expand Up @@ -452,7 +452,7 @@ Rspack x.x.x compiled successfully in X s
exports[`new code splitting stats output new code splitting stats output/simple-module-source should print correct stats for: NewCodeSplittingStatsOutput 1`] = `
asset bundle.js xx KiB [emitted] (name: main)
runtime modules 702 bytes 3 modules
runtime modules 661 bytes 3 modules
orphan modules 1 bytes [orphan] 1 module
cacheable modules 82 bytes
./index.js 75 bytes [built] [code generated]
Expand Down
Loading

2 comments on commit 7df06cf

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented on 7df06cf Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2025-02-25 cf4d882) Current Change
10000_big_production-mode_disable-minimize + exec 36.6 s ± 673 ms 37.5 s ± 253 ms +2.60 %
10000_development-mode + exec 1.73 s ± 14 ms 1.71 s ± 15 ms -1.39 %
10000_development-mode_hmr + exec 664 ms ± 9.3 ms 665 ms ± 9.3 ms +0.09 %
10000_production-mode + exec 2.18 s ± 57 ms 2.2 s ± 27 ms +0.75 %
10000_production-mode_persistent-cold + exec 2.35 s ± 131 ms 2.33 s ± 90 ms -0.87 %
10000_production-mode_persistent-hot + exec 1.63 s ± 24 ms 1.63 s ± 66 ms +0.05 %
arco-pro_development-mode + exec 1.79 s ± 140 ms 1.74 s ± 91 ms -2.41 %
arco-pro_development-mode_hmr + exec 375 ms ± 1.8 ms 376 ms ± 2 ms +0.32 %
arco-pro_production-mode + exec 3.51 s ± 188 ms 3.56 s ± 136 ms +1.54 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.59 s ± 114 ms 3.53 s ± 71 ms -1.69 %
arco-pro_production-mode_persistent-cold + exec 3.67 s ± 360 ms 3.61 s ± 96 ms -1.61 %
arco-pro_production-mode_persistent-hot + exec 2.22 s ± 56 ms 2.33 s ± 155 ms +4.70 %
arco-pro_production-mode_traverse-chunk-modules + exec 3.58 s ± 240 ms 3.54 s ± 157 ms -1.11 %
large-dyn-imports_development-mode + exec 1.98 s ± 45 ms 1.97 s ± 42 ms -0.47 %
large-dyn-imports_production-mode + exec 2.03 s ± 56 ms 2.04 s ± 26 ms +0.31 %
threejs_development-mode_10x + exec 1.47 s ± 94 ms 1.44 s ± 22 ms -2.48 %
threejs_development-mode_10x_hmr + exec 815 ms ± 23 ms 782 ms ± 28 ms -4.04 %
threejs_production-mode_10x + exec 5.1 s ± 309 ms 5.06 s ± 217 ms -0.82 %
threejs_production-mode_10x_persistent-cold + exec 5.15 s ± 108 ms 5.15 s ± 192 ms +0.05 %
threejs_production-mode_10x_persistent-hot + exec 4.46 s ± 298 ms 4.37 s ± 37 ms -2.00 %
10000_big_production-mode_disable-minimize + rss memory 8681 MiB ± 50.4 MiB 8679 MiB ± 81.7 MiB -0.01 %
10000_development-mode + rss memory 648 MiB ± 20.2 MiB 656 MiB ± 5.82 MiB +1.19 %
10000_development-mode_hmr + rss memory 1199 MiB ± 177 MiB 1266 MiB ± 232 MiB +5.60 %
10000_production-mode + rss memory 625 MiB ± 23.9 MiB 624 MiB ± 17.5 MiB -0.22 %
10000_production-mode_persistent-cold + rss memory 726 MiB ± 13.7 MiB 733 MiB ± 21.9 MiB +0.91 %
10000_production-mode_persistent-hot + rss memory 710 MiB ± 26.7 MiB 692 MiB ± 23 MiB -2.40 %
arco-pro_development-mode + rss memory 579 MiB ± 14.8 MiB 568 MiB ± 29 MiB -1.96 %
arco-pro_development-mode_hmr + rss memory 666 MiB ± 70.3 MiB 647 MiB ± 90.4 MiB -2.88 %
arco-pro_production-mode + rss memory 717 MiB ± 37.9 MiB 699 MiB ± 23.3 MiB -2.57 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 729 MiB ± 13.9 MiB 709 MiB ± 36.9 MiB -2.80 %
arco-pro_production-mode_persistent-cold + rss memory 802 MiB ± 38 MiB 770 MiB ± 33.8 MiB -4.09 %
arco-pro_production-mode_persistent-hot + rss memory 648 MiB ± 29.8 MiB 637 MiB ± 30.6 MiB -1.67 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 724 MiB ± 30.9 MiB 706 MiB ± 26.6 MiB -2.41 %
large-dyn-imports_development-mode + rss memory 649 MiB ± 8.1 MiB 645 MiB ± 2.74 MiB -0.65 %
large-dyn-imports_production-mode + rss memory 526 MiB ± 8.06 MiB 521 MiB ± 8.42 MiB -0.83 %
threejs_development-mode_10x + rss memory 562 MiB ± 11.2 MiB 554 MiB ± 20.5 MiB -1.42 %
threejs_development-mode_10x_hmr + rss memory 1116 MiB ± 187 MiB 1145 MiB ± 206 MiB +2.56 %
threejs_production-mode_10x + rss memory 839 MiB ± 40.9 MiB 837 MiB ± 46.8 MiB -0.32 %
threejs_production-mode_10x_persistent-cold + rss memory 964 MiB ± 72.3 MiB 951 MiB ± 69.5 MiB -1.39 %
threejs_production-mode_10x_persistent-hot + rss memory 816 MiB ± 60.2 MiB 809 MiB ± 37.1 MiB -0.76 %

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented on 7df06cf Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ecosystem CI detail: Open

suite result
modernjs ❌ failure
rspress ✅ success
rslib ✅ success
rsbuild ❌ failure
rsdoctor ❌ failure
examples ✅ success
devserver ✅ success
nuxt ✅ success

Please sign in to comment.