Skip to content

Commit

Permalink
feat: generate sourcemap (#425)
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin authored Feb 21, 2024
1 parent 2f7c61b commit da9db71
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 50 deletions.
53 changes: 40 additions & 13 deletions crates/rolldown/src/bundler/chunk/chunk.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use oxc::span::Atom;
use rolldown_common::{EntryPoint, EntryPointKind, ModuleId, NamedImport, Specifier, SymbolRef};
use rolldown_error::BuildError;
use rolldown_sourcemap::{collapse_sourcemaps, concat_sourcemaps, SourceMap};
use rustc_hash::FxHashMap;
use string_wizard::{Joiner, JoinerOptions};

use crate::{
bundler::{
Expand Down Expand Up @@ -56,18 +57,21 @@ impl Chunk {
}
}

#[allow(clippy::unnecessary_wraps, clippy::cast_possible_truncation)]
#[allow(clippy::unnecessary_wraps, clippy::cast_possible_truncation, clippy::type_complexity)]
pub fn render(
&self,
input_options: &InputOptions,
graph: &LinkStageOutput,
chunk_graph: &ChunkGraph,
output_options: &OutputOptions,
) -> BatchedResult<(String, FxHashMap<String, RenderedModule>)> {
) -> BatchedResult<((String, Option<SourceMap>), FxHashMap<String, RenderedModule>)> {
use rayon::prelude::*;
let mut rendered_modules = FxHashMap::default();
let mut joiner = Joiner::with_options(JoinerOptions { separator: Some("\n".to_string()) });
joiner.append(self.render_imports_for_esm(graph, chunk_graph));
let mut content_and_sourcemaps = vec![];

content_and_sourcemaps
.push((self.render_imports_for_esm(graph, chunk_graph).to_string(), None));

self
.modules
.par_iter()
Expand All @@ -94,23 +98,46 @@ impl Chunk {
.unwrap_or_default(),
},
rendered_content,
if output_options.sourcemap.is_hidden() {
None
} else {
// TODO add oxc codegen sourcemap to sourcemap chain
Some(collapse_sourcemaps(m.sourcemap_chain.clone()))
},
))
}
crate::bundler::module::Module::External(_) => None,
})
.collect::<Vec<_>>()
.into_iter()
.for_each(|(module_path, rendered_module, rendered_content)| {
if let Some(rendered_content) = rendered_content {
joiner.append(rendered_content);
}
rendered_modules.insert(module_path, rendered_module);
});
.try_for_each(
|(module_path, rendered_module, rendered_content, map)| -> Result<(), BuildError> {
if let Some(rendered_content) = rendered_content {
content_and_sourcemaps.push((
rendered_content.to_string(),
match map {
None => None,
Some(v) => v?,
},
));
}
rendered_modules.insert(module_path, rendered_module);
Ok(())
},
)?;

if let Some(exports) = self.render_exports(graph, output_options) {
joiner.append(exports);
content_and_sourcemaps.push((exports.to_string(), None));
}

if output_options.sourcemap.is_hidden() {
return Ok((
(content_and_sourcemaps.into_iter().map(|(c, _)| c).collect::<Vec<_>>().join("\n"), None),
rendered_modules,
));
}

Ok((joiner.join(), rendered_modules))
let (content, map) = concat_sourcemaps(&content_and_sourcemaps)?;
Ok(((content, Some(map)), rendered_modules))
}
}
6 changes: 6 additions & 0 deletions crates/rolldown/src/bundler/options/output_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ pub enum SourceMapType {
Hidden,
}

impl SourceMapType {
pub fn is_hidden(&self) -> bool {
matches!(self, Self::Hidden)
}
}

impl From<String> for SourceMapType {
fn from(value: String) -> Self {
match value.as_str() {
Expand Down
54 changes: 40 additions & 14 deletions crates/rolldown/src/bundler/stages/bundle_stage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::{borrow::Cow, hash::BuildHasherDefault};

use crate::{
bundler::{
bundle::output::OutputChunk,
Expand All @@ -10,17 +8,22 @@ use crate::{
chunk_graph::ChunkGraph,
finalizer::FinalizerContext,
module::Module,
options::{file_name_template::FileNameRenderOptions, output_options::OutputOptions},
options::{
file_name_template::FileNameRenderOptions,
output_options::{OutputOptions, SourceMapType},
},
plugin_driver::SharedPluginDriver,
stages::link_stage::LinkStageOutput,
utils::{bitset::BitSet, render_chunks::render_chunks},
},
error::BatchedResult,
InputOptions, Output, OutputFormat,
InputOptions, Output, OutputAsset, OutputFormat,
};
use index_vec::{index_vec, IndexVec};
use rolldown_common::{EntryPointKind, ExportsKind, ImportKind, ModuleId, NamedImport, SymbolRef};
use rolldown_error::BuildError;
use rustc_hash::{FxHashMap, FxHashSet};
use std::{borrow::Cow, hash::BuildHasherDefault};

pub struct BundleStage<'a> {
link_output: &'a mut LinkStageOutput,
Expand Down Expand Up @@ -85,16 +88,38 @@ impl<'a> BundleStage<'a> {
});

let chunks = chunk_graph.chunks.iter().map(|c| {
let (content, rendered_modules) =
let ((content, map), rendered_modules) =
c.render(self.input_options, self.link_output, &chunk_graph, self.output_options).unwrap();
(content, c.get_rendered_chunk_info(self.link_output, self.output_options, rendered_modules))
(
content,
map,
c.get_rendered_chunk_info(self.link_output, self.output_options, rendered_modules),
)
});

let assets = render_chunks(self.plugin_driver, chunks)
.await?
.into_iter()
.map(|(content, rendered_chunk)| {
Output::Chunk(Box::new(OutputChunk {
let mut assets = vec![];

render_chunks(self.plugin_driver, chunks).await?.into_iter().try_for_each(
|(mut content, map, rendered_chunk)| -> Result<(), BuildError> {
if let Some(mut map) = map {
match self.output_options.sourcemap {
SourceMapType::File => {
if let Some(map) = map.to_json() {
assets.push(Output::Asset(Box::new(OutputAsset {
file_name: format!("{}.map", rendered_chunk.file_name),
source: map?,
})));
}
}
SourceMapType::Inline => {
if let Some(map) = map.to_data_url() {
content.push_str(&format!("\n//# sourceMappingURL={}", map?));
}
}
SourceMapType::Hidden => {}
}
}
assets.push(Output::Chunk(Box::new(OutputChunk {
file_name: rendered_chunk.file_name,
code: content,
is_entry: rendered_chunk.is_entry,
Expand All @@ -103,9 +128,10 @@ impl<'a> BundleStage<'a> {
modules: rendered_chunk.modules,
exports: rendered_chunk.exports,
module_ids: rendered_chunk.module_ids,
}))
})
.collect::<Vec<_>>();
})));
Ok(())
},
)?;

Ok(assets)
}
Expand Down
10 changes: 6 additions & 4 deletions crates/rolldown/src/bundler/utils/render_chunks.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rolldown_sourcemap::SourceMap;
use rolldown_utils::block_on_spawn_all;

use crate::{
Expand All @@ -9,17 +10,18 @@ use crate::{
#[allow(clippy::future_not_send)]
pub async fn render_chunks<'a>(
plugin_driver: &SharedPluginDriver,
chunks: impl Iterator<Item = (String, RenderedChunk)>,
) -> Result<Vec<(String, RenderedChunk)>, BatchedErrors> {
let result = block_on_spawn_all(chunks.map(|(content, rendered_chunk)| async move {
chunks: impl Iterator<Item = (String, Option<SourceMap>, RenderedChunk)>,
) -> Result<Vec<(String, Option<SourceMap>, RenderedChunk)>, BatchedErrors> {
// TODO support `render_chunk` hook return map
let result = block_on_spawn_all(chunks.map(|(content, map, rendered_chunk)| async move {
match plugin_driver
.render_chunk(RenderChunkArgs {
code: content,
chunk: unsafe { std::mem::transmute(&rendered_chunk) },
})
.await
{
Ok(value) => Ok((value, rendered_chunk)),
Ok(value) => Ok((value, map, rendered_chunk)),
Err(e) => Err(e),
}
}));
Expand Down
10 changes: 7 additions & 3 deletions crates/rolldown_error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use oxc::span::Span;
use crate::{
diagnostic::Diagnostic,
error_kind::{
external_entry::ExternalEntry, unresolved_entry::UnresolvedEntry,
unresolved_import::UnresolvedImport, unsupported_eval::UnsupportedEval, BuildErrorLike,
NapiError,
external_entry::ExternalEntry, sourcemap_error::SourceMapError,
unresolved_entry::UnresolvedEntry, unresolved_import::UnresolvedImport,
unsupported_eval::UnsupportedEval, BuildErrorLike, NapiError,
},
};

Expand Down Expand Up @@ -96,6 +96,10 @@ impl BuildError {
Self::new_inner(UnresolvedImport { specifier: specifier.into(), importer: importer.into() })
}

pub fn sourcemap_error(reason: String) -> Self {
Self::new_inner(SourceMapError { reason })
}

// --- rolldown specific
pub fn napi_error(status: String, reason: String) -> Self {
Self::new_inner(NapiError { status, reason })
Expand Down
2 changes: 1 addition & 1 deletion crates/rolldown_error/src/error_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
// pub const PLUGIN_ERROR: &str = "PLUGIN_ERROR";
// pub const SHIMMED_EXPORT: &str = "SHIMMED_EXPORT";
// pub const SOURCEMAP_BROKEN: &str = "SOURCEMAP_BROKEN";
// pub const SOURCEMAP_ERROR: &str = "SOURCEMAP_ERROR";
pub const SOURCEMAP_ERROR: &str = "SOURCEMAP_ERROR";
// pub const SYNTHETIC_NAMED_EXPORTS_NEED_NAMESPACE_EXPORT: &str =
// "SYNTHETIC_NAMED_EXPORTS_NEED_NAMESPACE_EXPORT";
// pub const THIS_IS_UNDEFINED: &str = "THIS_IS_UNDEFINED";
Expand Down
2 changes: 1 addition & 1 deletion crates/rolldown_error/src/error_kind/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::fmt::Debug;

use crate::diagnostic::DiagnosticBuilder;

pub mod external_entry;
pub mod sourcemap_error;
pub mod unresolved_entry;
pub mod unresolved_import;
pub mod unsupported_eval;
Expand Down
16 changes: 16 additions & 0 deletions crates/rolldown_error/src/error_kind/sourcemap_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use super::BuildErrorLike;

#[derive(Debug)]
pub struct SourceMapError {
pub reason: String,
}

impl BuildErrorLike for SourceMapError {
fn code(&self) -> &'static str {
"SOURCEMAP_ERROR"
}

fn message(&self) -> String {
format!("Error when using sourcemap for reporting an error: {}", self.reason)
}
}
16 changes: 10 additions & 6 deletions crates/rolldown_sourcemap/src/concat_sourcemap.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use parcel_sourcemap::SourceMap as ParcelSourcemap;
use rolldown_error::BuildError;

use crate::SourceMap;

pub fn concat_sourcemaps(
content_and_sourcemaps: &[(String, Option<&SourceMap>)],
) -> Result<(String, SourceMap), String> {
content_and_sourcemaps: &[(String, Option<SourceMap>)],
) -> Result<(String, SourceMap), BuildError> {
let mut s = String::new();
let mut map = ParcelSourcemap::new("");
let mut line_offset = 0;
Expand All @@ -18,12 +19,15 @@ pub fn concat_sourcemaps(
if let Some(sourcemap) = sourcemap {
map
.add_sourcemap(
&mut sourcemap.get_inner().cloned().ok_or("concat sourcemap not inner sourcemap")?,
&mut sourcemap.get_inner().cloned().ok_or(BuildError::sourcemap_error(
"concat sourcemap not inner sourcemap".to_string(),
))?,
line_offset.into(),
)
.map_err(|e| e.to_string())?;
.map_err(|e| BuildError::sourcemap_error(e.to_string()))?;
}
line_offset += u32::try_from(content.lines().count() + 1).map_err(|e| e.to_string())?;
line_offset += u32::try_from(content.lines().count() + 1)
.map_err(|e| BuildError::sourcemap_error(e.to_string()))?;
}

Ok((s, map.into()))
Expand Down Expand Up @@ -51,7 +55,7 @@ mod tests {
("\nconsole.log()".to_string(), None),
(
"function sayHello(name: string) {\n console.log(`Hello, ${name}`);\n}\n".to_string(),
Some(&map),
Some(map),
),
];

Expand Down
25 changes: 17 additions & 8 deletions crates/rolldown_sourcemap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use parcel_sourcemap::SourceMap as ParcelSourcemap;
mod concat_sourcemap;

pub use concat_sourcemap::concat_sourcemaps;
use rolldown_error::BuildError;
#[derive(Debug, Default, Clone)]
pub struct SourceMap {
pub mappings: String,
Expand All @@ -23,12 +24,18 @@ impl SourceMap {
Self { mappings, names, source_root, sources, sources_content, inner: None }
}

pub fn to_json(&mut self) -> String {
self.inner.as_mut().expect("should have inner").to_json(None).expect("should success")
pub fn to_json(&mut self) -> Option<Result<String, BuildError>> {
self
.inner
.as_mut()
.map(|i| i.to_json(None).map_err(|e| BuildError::sourcemap_error(e.to_string())))
}

pub fn to_data_url(&mut self) -> String {
self.inner.as_mut().expect("should have inner").to_data_url(None).expect("should success")
pub fn to_data_url(&mut self) -> Option<Result<String, BuildError>> {
self
.inner
.as_mut()
.map(|i| i.to_data_url(None).map_err(|e| BuildError::sourcemap_error(e.to_string())))
}

pub fn get_inner(&self) -> Option<&ParcelSourcemap> {
Expand All @@ -42,7 +49,9 @@ impl From<ParcelSourcemap> for SourceMap {
}
}

pub fn collapse_sourcemaps(sourcemap_chain: Vec<SourceMap>) -> Result<Option<SourceMap>, String> {
pub fn collapse_sourcemaps(
sourcemap_chain: Vec<SourceMap>,
) -> Result<Option<SourceMap>, BuildError> {
let mut parcel_sourcemap_chain = sourcemap_chain
.into_iter()
.map(|sourcemap| {
Expand All @@ -56,16 +65,16 @@ pub fn collapse_sourcemaps(sourcemap_chain: Vec<SourceMap>) -> Result<Option<Sou
0,
0,
)
.map_err(|e| e.to_string())?;
.map_err(|e| BuildError::sourcemap_error(e.to_string()))?;
Ok(map)
})
.rev()
.collect::<Result<Vec<_>, String>>()?;
.collect::<Result<Vec<_>, BuildError>>()?;

let Some(mut result) = parcel_sourcemap_chain.pop() else { return Ok(None) };

for mut sourcemap in parcel_sourcemap_chain.into_iter().rev() {
sourcemap.extends(&mut result).map_err(|e| e.to_string())?;
sourcemap.extends(&mut result).map_err(|e| BuildError::sourcemap_error(e.to_string()))?;
result = sourcemap;
}

Expand Down

0 comments on commit da9db71

Please sign in to comment.