Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ enum AtRuleParserValue<'a, T> {

struct BundleStyleSheet<'i, T> {
stylesheet: Option<StyleSheet<'i, T>>,
source_map_source_index: Option<u32>,
dependencies: Vec<Dependency>,
css_modules_deps: Vec<u32>,
parent_source_index: u32,
Expand Down Expand Up @@ -316,6 +317,16 @@ where

let mut stylesheet = StyleSheet::new(sources, CssRuleList(rules), self.options.clone());

if self.source_map.is_some() {
stylesheet.source_map_source_indices = self
.stylesheets
.get_mut()
.unwrap()
.iter()
.map(|s| s.source_map_source_index)
.collect();
}

stylesheet.source_map_urls = self
.stylesheets
.get_mut()
Expand Down Expand Up @@ -415,6 +426,7 @@ where

stylesheets.push(BundleStyleSheet {
stylesheet: None,
source_map_source_index: None,
layer: rule.layer.clone(),
media: rule.media.clone(),
supports: rule.supports.clone(),
Expand Down Expand Up @@ -455,14 +467,15 @@ where
StyleSheet::<T::AtRule>::parse_with(code, opts, at_rule_parser)?
};

let mut source_map_source_index = None;
if let Some(source_map) = &self.source_map {
// Only add source if we don't have an input source map.
// If we do, this will be handled by the printer when remapping locations.
let sm = stylesheet.source_map_url(0);
if sm.is_none() || !sm.unwrap().starts_with("data") {
// Only omit the CSS source when the input source map can actually be read.
// Otherwise, the printer needs this index as a fallback for generated mappings.
if stylesheet.source_map(0).is_none() {
let mut source_map = source_map.lock().unwrap();
let source_index = source_map.add_source(filename);
let _ = source_map.set_source_content(source_index as usize, code);
source_map_source_index = Some(source_index);
}
}

Expand Down Expand Up @@ -606,6 +619,7 @@ where

let entry = &mut self.stylesheets.lock().unwrap()[source_index as usize];
entry.stylesheet = Some(stylesheet);
entry.source_map_source_index = source_map_source_index;
entry.dependencies = dependencies?;
entry.css_modules_deps = css_modules_deps?;

Expand Down
11 changes: 10 additions & 1 deletion src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ pub struct Printer<'a, 'c, W> {
#[cfg(feature = "sourcemap")]
#[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
pub(crate) source_maps: Vec<Option<SourceMap>>,
#[cfg(feature = "sourcemap")]
pub(crate) source_map_source_indices: &'c [Option<u32>],
pub(crate) loc: Location,
indent: u8,
line: u32,
Expand Down Expand Up @@ -99,6 +101,8 @@ impl<'a, 'c, W: std::fmt::Write + Sized> Printer<'a, 'c, W> {
source_map: options.source_map,
#[cfg(feature = "sourcemap")]
source_maps: Vec::new(),
#[cfg(feature = "sourcemap")]
source_map_source_indices: &[],
loc: Location {
source_index: 0,
line: 0,
Expand Down Expand Up @@ -249,7 +253,12 @@ impl<'a, 'c, W: std::fmt::Write + Sized> Printer<'a, 'c, W> {
let mut original = OriginalLocation {
original_line: loc.line,
original_column: loc.column - 1,
source: loc.source_index,
source: self
.source_map_source_indices
.get(loc.source_index as usize)
.copied()
.flatten()
.unwrap_or(loc.source_index),
name: None,
};

Expand Down
9 changes: 9 additions & 0 deletions src/stylesheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ pub struct StyleSheet<'i, T = DefaultAtRule> {
pub sources: Vec<String>,
/// The source map URL extracted from the original style sheet.
pub source_map_urls: Vec<Option<String>>,
/// Output source map indices corresponding to the bundler's source indices.
#[cfg(feature = "sourcemap")]
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) source_map_source_indices: Vec<Option<u32>>,
/// The license comments that appeared at the start of the file.
pub license_comments: Vec<CowArcStr<'i>>,
/// A list of content hashes for all source files included within the style sheet.
Expand Down Expand Up @@ -135,6 +139,8 @@ where
StyleSheet {
sources,
source_map_urls: Vec::new(),
#[cfg(feature = "sourcemap")]
source_map_source_indices: Vec::new(),
license_comments: Vec::new(),
content_hashes: None,
rules,
Expand Down Expand Up @@ -202,6 +208,8 @@ where
Ok(StyleSheet {
sources: vec![options.filename.clone()],
source_map_urls: vec![parser.current_source_map_url().map(|s| s.to_owned())],
#[cfg(feature = "sourcemap")]
source_map_source_indices: Vec::new(),
content_hashes,
rules,
license_comments,
Expand Down Expand Up @@ -280,6 +288,7 @@ where
#[cfg(feature = "sourcemap")]
if printer.source_map.is_some() {
printer.source_maps = self.sources.iter().enumerate().map(|(i, _)| self.source_map(i)).collect();
printer.source_map_source_indices = &self.source_map_source_indices;
}

for comment in &self.license_comments {
Expand Down
58 changes: 58 additions & 0 deletions tests/test_parcel_bundler_source_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![cfg(all(feature = "bundler", feature = "sourcemap"))]

use parcel_sourcemap::SourceMap;
use rspack_lightningcss::{
bundler::{Bundler, ResolveResult, SourceProvider},
stylesheet::{ParserOptions, PrinterOptions},
};
use std::{io, path::Path};

struct SingleFile(&'static str);

impl SourceProvider for SingleFile {
type Error = io::Error;

fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {
if file == Path::new("/entry.css") {
Ok(self.0)
} else {
Err(io::ErrorKind::NotFound.into())
}
}

fn resolve(&self, _: &str, _: &Path) -> Result<ResolveResult, Self::Error> {
Err(io::ErrorKind::NotFound.into())
}
}

fn assert_bundled_source(css: &'static str) {
let fs = SingleFile(css);
let mut map = SourceMap::new("/");
let existing_index = map.add_source("/existing.css");
map.set_source_content(existing_index as usize, ".existing {}").unwrap();

let mut bundler = Bundler::new(&fs, Some(&mut map), ParserOptions::default());
let stylesheet = bundler.bundle(Path::new("/entry.css")).unwrap();
stylesheet
.to_css(PrinterOptions {
source_map: Some(&mut map),
..PrinterOptions::default()
})
.unwrap();

let source_index = map.find_closest_mapping(0, 0).unwrap().original.unwrap().source;
assert_ne!(source_index, existing_index);
assert_eq!(map.get_source(source_index).unwrap(), "entry.css");
assert_eq!(map.get_source_content(source_index).unwrap(), css);
assert_eq!(map.get_source_content(existing_index).unwrap(), ".existing {}");
}

#[test]
fn uses_the_output_maps_source_index() {
assert_bundled_source(".entry { color: red }");
}

#[test]
fn registers_css_when_the_inline_map_is_unreadable() {
assert_bundled_source(".entry { color: red }\n/*# sourceMappingURL=data:application/json,not-a-map */");
}
Loading