Skip to content

Commit 0866596

Browse files
authored
feat: add bytes support for asset modules (#12548)
* feat: add bytes support for asset modules * docs
1 parent cf170a2 commit 0866596

File tree

18 files changed

+156
-12
lines changed

18 files changed

+156
-12
lines changed

crates/rspack_core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ pub enum ModuleType {
196196
AssetInline,
197197
AssetResource,
198198
AssetSource,
199+
AssetBytes,
199200
Asset,
200201
Runtime,
201202
Remote,
@@ -261,6 +262,7 @@ impl ModuleType {
261262
ModuleType::AssetSource => "asset/source",
262263
ModuleType::AssetResource => "asset/resource",
263264
ModuleType::AssetInline => "asset/inline",
265+
ModuleType::AssetBytes => "asset/bytes",
264266
ModuleType::Runtime => "runtime",
265267
ModuleType::Remote => "remote-module",
266268
ModuleType::Fallback => "fallback-module",
@@ -301,6 +303,7 @@ impl From<&str> for ModuleType {
301303
"asset/resource" => Self::AssetResource,
302304
"asset/source" => Self::AssetSource,
303305
"asset/inline" => Self::AssetInline,
306+
"asset/bytes" => Self::AssetBytes,
304307

305308
custom => Self::Custom(custom.into()),
306309
}

crates/rspack_core/src/runtime_globals.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,12 @@ bitflags! {
260260
const AMD_DEFINE = 1 << 67;
261261
const AMD_OPTIONS = 1 << 68;
262262

263+
const TO_BINARY = 1 << 69;
264+
263265
// defer import support
264-
const ASYNC_MODULE_EXPORT_SYMBOL = 1 << 69;
265-
const MAKE_DEFERRED_NAMESPACE_OBJECT = 1 << 70;
266-
const MAKE_DEFERRED_NAMESPACE_OBJECT_SYMBOL = 1 << 71;
266+
const ASYNC_MODULE_EXPORT_SYMBOL = 1 << 70;
267+
const MAKE_DEFERRED_NAMESPACE_OBJECT = 1 << 71;
268+
const MAKE_DEFERRED_NAMESPACE_OBJECT_SYMBOL = 1 << 72;
267269
}
268270
}
269271

@@ -355,6 +357,7 @@ pub fn runtime_globals_to_string(
355357
RuntimeGlobals::HAS_CSS_MODULES => "has css modules".to_string(),
356358

357359
RuntimeGlobals::HAS_FETCH_PRIORITY => "has fetch priority".to_string(),
360+
RuntimeGlobals::TO_BINARY => format!("{scope_name}.tb"),
358361
_ => unreachable!(),
359362
}
360363
}

crates/rspack_plugin_asset/src/lib.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const DEFAULT_ENCODING: &str = "base64";
4848
enum DataUrlOptions {
4949
Inline(bool),
5050
Source,
51+
Bytes,
5152
Auto(Option<AssetParserDataUrl>),
5253
}
5354

@@ -60,6 +61,7 @@ const ASSET_RESOURCE: bool = false;
6061
#[derive(Debug, Clone)]
6162
pub enum CanonicalizedDataUrlOption {
6263
Source,
64+
Bytes,
6365
Asset(IsInline),
6466
}
6567

@@ -68,6 +70,10 @@ impl CanonicalizedDataUrlOption {
6870
matches!(self, CanonicalizedDataUrlOption::Source)
6971
}
7072

73+
pub fn is_bytes(&self) -> bool {
74+
matches!(self, CanonicalizedDataUrlOption::Bytes)
75+
}
76+
7177
pub fn is_inline(&self) -> bool {
7278
matches!(self, CanonicalizedDataUrlOption::Asset(ASSET_INLINE))
7379
}
@@ -118,6 +124,14 @@ impl AssetParserAndGenerator {
118124
}
119125
}
120126

127+
pub fn with_bytes() -> Self {
128+
Self {
129+
emit: false,
130+
data_url: DataUrlOptions::Bytes,
131+
parsed_asset_config: None,
132+
}
133+
}
134+
121135
fn decode_data_uri_content(encoding: &str, content: &str, source: &BoxSource) -> Vec<u8> {
122136
if encoding == "base64"
123137
&& let Some(cleaned) = base64::clean_base64(content)
@@ -372,7 +386,7 @@ impl ParserAndGenerator for AssetParserAndGenerator {
372386
if self
373387
.parsed_asset_config
374388
.as_ref()
375-
.is_some_and(|x| x.is_source() || x.is_inline())
389+
.is_some_and(|x| x.is_source() || x.is_inline() || x.is_bytes())
376390
|| !self.emit
377391
{
378392
if source_types.is_empty() {
@@ -420,7 +434,9 @@ impl ParserAndGenerator for AssetParserAndGenerator {
420434

421435
let parsed_size = self.parsed_asset_config.as_ref().map(|config| {
422436
match config {
423-
CanonicalizedDataUrlOption::Source => original_source_size,
437+
CanonicalizedDataUrlOption::Source | CanonicalizedDataUrlOption::Bytes => {
438+
original_source_size
439+
}
424440
CanonicalizedDataUrlOption::Asset(meta) => {
425441
match *meta {
426442
ASSET_INLINE => {
@@ -465,6 +481,7 @@ impl ParserAndGenerator for AssetParserAndGenerator {
465481

466482
self.parsed_asset_config = match &self.data_url {
467483
DataUrlOptions::Source => Some(CanonicalizedDataUrlOption::Source),
484+
DataUrlOptions::Bytes => Some(CanonicalizedDataUrlOption::Bytes),
468485
DataUrlOptions::Inline(val) => Some(CanonicalizedDataUrlOption::Asset(*val)),
469486
DataUrlOptions::Auto(option) => {
470487
let limit_size = parse_context
@@ -524,7 +541,30 @@ impl ParserAndGenerator for AssetParserAndGenerator {
524541

525542
match generate_context.requested_source_type {
526543
SourceType::JavaScript | SourceType::CssUrl => {
527-
let exported_content = if parsed_asset_config.is_inline() {
544+
let exported_content = if parsed_asset_config.is_bytes() {
545+
let mut encoded_source = base64::encode_to_string(source.buffer());
546+
if generate_context.requested_source_type == SourceType::CssUrl {
547+
encoded_source = format!("data:application/octet-stream;base64,{}", encoded_source);
548+
generate_context
549+
.data
550+
.insert(CodeGenerationDataUrl::new(encoded_source.clone()));
551+
serde_json::to_string(&encoded_source).to_rspack_result()?
552+
} else {
553+
generate_context
554+
.runtime_requirements
555+
.insert(RuntimeGlobals::REQUIRE_SCOPE);
556+
generate_context
557+
.runtime_requirements
558+
.insert(RuntimeGlobals::TO_BINARY);
559+
format!(
560+
"{}({})",
561+
compilation
562+
.runtime_template
563+
.render_runtime_globals(&RuntimeGlobals::TO_BINARY),
564+
serde_json::to_string(&encoded_source).to_rspack_result()?
565+
)
566+
}
567+
} else if parsed_asset_config.is_inline() {
528568
let resource_data: &ResourceData = normal_module.resource_resolved_data();
529569
let data_url = module_generator_options.and_then(|x| x.asset_data_url());
530570
let encoded_source: String;
@@ -899,6 +939,11 @@ impl Plugin for AssetPlugin {
899939
Box::new(move |_, _| Box::new(AssetParserAndGenerator::with_source())),
900940
);
901941

942+
ctx.register_parser_and_generator_builder(
943+
rspack_core::ModuleType::AssetBytes,
944+
Box::new(move |_, _| Box::new(AssetParserAndGenerator::with_bytes())),
945+
);
946+
902947
Ok(())
903948
}
904949
}

crates/rspack_plugin_runtime/src/runtime_module/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod runtime_id;
4242
mod startup_chunk_dependencies;
4343
mod startup_entrypoint;
4444
mod system_context;
45+
mod to_binary;
4546
mod utils;
4647
pub use amd_define::AmdDefineRuntimeModule;
4748
pub use amd_options::AmdOptionsRuntimeModule;
@@ -87,4 +88,5 @@ pub use runtime_id::RuntimeIdRuntimeModule;
8788
pub use startup_chunk_dependencies::StartupChunkDependenciesRuntimeModule;
8889
pub use startup_entrypoint::StartupEntrypointRuntimeModule;
8990
pub use system_context::SystemContextRuntimeModule;
91+
pub use to_binary::ToBinaryRuntimeModule;
9092
pub use utils::*;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// define to binary helper
2+
<%- TO_BINARY %> = <% if (_is_neutral_platform) { %>typeof Buffer !== 'undefined' ? <% } %>
3+
<% if (_is_node_platform || _is_neutral_platform) { %>
4+
<%- returningFunction("new Uint8Array(Buffer.from(base64, 'base64'))", "base64") %>
5+
<% } %>
6+
<% if (_is_neutral_platform) { %> : <% } %>
7+
<% if (_is_web_platform || _is_neutral_platform) { %>
8+
(<%- basicFunction("") %> {
9+
var table = new Uint8Array(128);
10+
for (var i = 0; i < 64; i++) table[i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i * 4 - 205] = i;
11+
return <%- basicFunction("base64") %> {
12+
var n = base64.length, bytes = new Uint8Array((n - (base64[n - 1] == '=') - (base64[n - 2] == '=')) * 3 / 4 | 0);
13+
for (var i = 0, j = 0; i < n;) {
14+
var c0 = table[base64.charCodeAt(i++)], c1 = table[base64.charCodeAt(i++)];
15+
var c2 = table[base64.charCodeAt(i++)], c3 = table[base64.charCodeAt(i++)];
16+
bytes[j++] = (c0 << 2) | (c1 >> 4);
17+
bytes[j++] = (c1 << 4) | (c2 >> 2);
18+
bytes[j++] = (c2 << 6) | c3;
19+
}
20+
return bytes;
21+
};
22+
})()
23+
<% } %>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use rspack_collections::Identifier;
2+
use rspack_core::{Compilation, RuntimeModule, RuntimeTemplate, impl_runtime_module};
3+
4+
#[impl_runtime_module]
5+
#[derive(Debug)]
6+
pub struct ToBinaryRuntimeModule {
7+
id: Identifier,
8+
}
9+
10+
impl ToBinaryRuntimeModule {
11+
pub fn new(runtime_template: &RuntimeTemplate) -> Self {
12+
Self::with_default(Identifier::from(format!(
13+
"{}to_binary",
14+
runtime_template.runtime_module_prefix()
15+
)))
16+
}
17+
}
18+
19+
#[async_trait::async_trait]
20+
impl RuntimeModule for ToBinaryRuntimeModule {
21+
fn name(&self) -> Identifier {
22+
self.id
23+
}
24+
25+
fn template(&self) -> Vec<(String, String)> {
26+
vec![(
27+
self.id.to_string(),
28+
include_str!("runtime/to_binary.ejs").to_string(),
29+
)]
30+
}
31+
32+
async fn generate(&self, compilation: &Compilation) -> rspack_error::Result<String> {
33+
let is_node_platform = compilation.platform.is_node();
34+
let is_web_platform = compilation.platform.is_web();
35+
let is_neutral_platform = compilation.platform.is_neutral();
36+
37+
let source = compilation.runtime_template.render(
38+
&self.id,
39+
Some(serde_json::json!({
40+
"_is_node_platform": is_node_platform,
41+
"_is_web_platform": is_web_platform,
42+
"_is_neutral_platform": is_neutral_platform,
43+
})),
44+
)?;
45+
46+
Ok(source)
47+
}
48+
}

crates/rspack_plugin_runtime/src/runtime_plugin.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ use crate::{
3535
LoadScriptRuntimeModule, MakeDeferredNamespaceObjectRuntimeModule,
3636
MakeNamespaceObjectRuntimeModule, NodeModuleDecoratorRuntimeModule, NonceRuntimeModule,
3737
OnChunkLoadedRuntimeModule, PublicPathRuntimeModule, RelativeUrlRuntimeModule,
38-
RuntimeIdRuntimeModule, SystemContextRuntimeModule, chunk_has_css, is_enabled_for_chunk,
38+
RuntimeIdRuntimeModule, SystemContextRuntimeModule, ToBinaryRuntimeModule, chunk_has_css,
39+
is_enabled_for_chunk,
3940
},
4041
};
4142

@@ -601,6 +602,12 @@ async fn runtime_requirements_in_tree(
601602
.boxed(),
602603
)?;
603604
}
605+
RuntimeGlobals::TO_BINARY => {
606+
compilation.add_runtime_module(
607+
chunk_ukey,
608+
ToBinaryRuntimeModule::new(&compilation.runtime_template).boxed(),
609+
)?;
610+
}
604611
_ => {}
605612
}
606613
}

packages/rspack-test-tools/src/runner/node/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export class NodeRunner implements ITestRunner {
191191
},
192192
WAITING: this.globalContext!.WAITING,
193193
process,
194+
TextDecoder,
194195
URL,
195196
Blob,
196197
Symbol,

packages/rspack/src/config/adapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ function getRawGeneratorOptions(
695695
if (
696696
[
697697
"asset/source",
698+
"asset/bytes",
698699
"javascript",
699700
"javascript/auto",
700701
"javascript/dynamic",

packages/rspack/src/config/defaults.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,10 @@ const applyModuleDefaults = (
583583
{
584584
with: { type: "text" },
585585
type: "asset/source"
586+
},
587+
{
588+
with: { type: "bytes" },
589+
type: "asset/bytes"
586590
}
587591
);
588592

0 commit comments

Comments
 (0)