Skip to main content

turbopack_ecmascript/
bytes_source_transform.rs

1use std::io::Read;
2
3use anyhow::{Result, bail};
4use turbo_tasks::Vc;
5use turbo_tasks_fs::{File, FileContent};
6use turbopack_core::{
7    asset::{Asset, AssetContent},
8    context::AssetContext,
9    source::Source,
10    source_transform::SourceTransform,
11    virtual_source::VirtualSource,
12};
13
14use crate::utils::{StringifyJs, inline_source_map_comment};
15
16/// A source transform that converts any file into an ES module that exports
17/// the file's content as a default Uint8Array export.
18///
19/// This is used for `import bytes from './file.bin' with { type: 'bytes' }`.
20#[turbo_tasks::value]
21pub struct BytesSourceTransform;
22
23#[turbo_tasks::value_impl]
24impl BytesSourceTransform {
25    #[turbo_tasks::function]
26    pub fn new() -> Vc<Self> {
27        BytesSourceTransform.cell()
28    }
29}
30
31#[turbo_tasks::value_impl]
32impl SourceTransform for BytesSourceTransform {
33    #[turbo_tasks::function]
34    async fn transform(
35        self: Vc<Self>,
36        source: Vc<Box<dyn Source>>,
37        _asset_context: Vc<Box<dyn AssetContext>>,
38    ) -> Result<Vc<Box<dyn Source>>> {
39        let ident = source.ident().owned().await?;
40        let content = source.content().file_content().await?;
41        let bytes = match &*content {
42            FileContent::Content(data) => {
43                data.read().bytes().collect::<std::io::Result<Vec<u8>>>()?
44            }
45            FileContent::NotFound => {
46                bail!("File not found: {:?}", ident.path);
47            }
48        };
49
50        let encoded = data_encoding::BASE64_NOPAD.encode(&bytes);
51
52        // Generate ES module that decodes base64 to Uint8Array with inline source map.
53        let code = format!(
54            r#""use turbopack no side effects";
55import {{ base64Decode }} from '@turbopack/base64';
56export default base64Decode({});
57{}"#,
58            StringifyJs(&encoded),
59            // For binary files, we use an empty string as sourcesContent since the
60            // original content isn't meaningful text.
61            inline_source_map_comment(&ident.path.path, "")
62        );
63
64        // Rename to .mjs so module rules recognize it as ESM.
65        // The inline source map ensures debuggers show the original file.
66
67        let new_ident = ident.rename_as("*.[bytes].mjs").into_vc();
68
69        Ok(Vc::upcast(VirtualSource::new_with_ident(
70            new_ident,
71            AssetContent::file(FileContent::Content(File::from(code)).cell()),
72        )))
73    }
74}