fix(common): Fix source map generation with inputSourceMap (#8546)

**Description:**

- This PR fixes the source map generation when `inputSourceMap` is specified.
- This PR fixes `minify()` not accepting parsed source map in the option.


**Related issue:**

 - Closes #8372.
This commit is contained in:
Donny/강동윤 2024-01-24 11:02:45 +09:00 committed by GitHub
parent bfdfd3a26b
commit 043ee85d08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 246 additions and 23 deletions

4
Cargo.lock generated
View File

@ -3963,9 +3963,12 @@ dependencies = [
name = "swc_config"
version = "0.1.9"
dependencies = [
"anyhow",
"indexmap 2.1.0",
"serde",
"serde_json",
"sourcemap",
"swc_cached",
"swc_config_macro",
]
@ -4534,7 +4537,6 @@ dependencies = [
"serde",
"serde_json",
"swc_atoms",
"swc_cached",
"swc_common",
"swc_config",
"swc_ecma_ast",

View File

@ -765,14 +765,8 @@ impl Compiler {
.source_map
.as_ref()
.map(|obj| -> Result<_, Error> {
let orig = obj
.content
.as_ref()
.map(|s| sourcemap::SourceMap::from_slice(s.as_bytes()));
let orig = match orig {
Some(v) => Some(v?),
None => None,
};
let orig = obj.content.as_ref().map(|s| s.to_sourcemap()).transpose()?;
Ok((SourceMapsConfig::Bool(true), orig))
})
.unwrap_as_option(|v| {

View File

@ -0,0 +1,23 @@
{
"compress": {
"negate_iife": false,
"sequences": 30
},
"mangle": {
"safari10": true
},
"output": {
"semicolons": false
},
"sourceMap": {
"filename": "input.js",
"url": "input.map",
"content": {
"version": 3,
"sources": ["input-preprocess.js"],
"sourcesContent": ["const a = {aób: 'ó'}\n\nconsole.log(a)"],
"names": ["console", "log", "aób"],
"mappings": "AAEAA,QAAQC,GAAG,CAFD;IAACC,UAAK;AAAG"
}
}
}

View File

@ -0,0 +1,3 @@
const a = { aób: 'ó' }
console.log(a)

View File

@ -0,0 +1 @@
let a={aób:"\xf3"};console.log(a);

View File

@ -0,0 +1,16 @@
{
"mappings": "AAEAA,IAAAA,EAAQC,CAFEC,IAAA,MAAA,EAASF,QAAAC,GAAA,CAAAE",
"names": [
"console",
"log",
"aób",
"a"
],
"sources": [
"input-preprocess.js"
],
"sourcesContent": [
"const a = {aób: 'ó'}\n\nconsole.log(a)"
],
"version": 3
}

View File

@ -0,0 +1,12 @@
{
"compress": {
"negate_iife": false,
"sequences": 30
},
"mangle": {
"safari10": true
},
"output": {
"semicolons": false
}
}

View File

@ -0,0 +1,3 @@
const a = { aób: 'ó' }
console.log(a)

View File

@ -0,0 +1 @@
let a={aób:"\xf3"};console.log(a);

View File

@ -0,0 +1,16 @@
{
"mappings": "AAAA,IAAMA,EAAI,CAAEC,IAAK,MAAI,EAErBC,QAAQC,GAAG,CAACH",
"names": [
"a",
"aób",
"console",
"log"
],
"sources": [
"$DIR/tests/minify/issue-7475/issue-8372/2/input.js"
],
"sourcesContent": [
"const a = { aób: 'ó' }\n\nconsole.log(a)"
],
"version": 3
}

View File

@ -1128,10 +1128,13 @@ fn minify(input_js: PathBuf) {
let c = Compiler::new(cm);
let fm = c.cm.load_file(&input_js).unwrap();
let mut config: JsMinifyOptions =
serde_json::from_str(&std::fs::read_to_string(&config_json_path).unwrap()).unwrap();
let config_str = std::fs::read_to_string(&config_json_path).unwrap();
let mut config: JsMinifyOptions = serde_json::from_str(&config_str).unwrap();
if config.source_map.inner().is_none() {
config.source_map = BoolOrDataConfig::from_bool(true);
}
config.source_map = BoolOrDataConfig::from_bool(true);
let output = c.minify(fm, &handler, &config).unwrap();
NormalizedOutput::from(output.code)

View File

@ -4,3 +4,4 @@
#![deny(warnings)]
pub mod regex;
pub use anyhow::Error;

View File

@ -1,6 +1,6 @@
//! Regex cache
use std::{ops::Deref, sync::Arc};
use std::{ops::Deref, str::FromStr, sync::Arc};
pub use anyhow::Error;
use anyhow::{Context, Result};
@ -77,3 +77,11 @@ impl From<&'_ str> for CachedRegex {
Self::new(s).unwrap()
}
}
impl FromStr for CachedRegex {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}

View File

@ -1269,11 +1269,13 @@ impl SourceMap {
if config.skip(&f.name) {
continue;
}
src_id = builder.add_source(&config.file_name_to_source(&f.name));
if orig.is_none() {
src_id = builder.add_source(&config.file_name_to_source(&f.name));
inline_sources_content = config.inline_sources_content(&f.name);
if inline_sources_content && orig.is_none() {
builder.set_source_contents(src_id, Some(&f.src));
inline_sources_content = config.inline_sources_content(&f.name);
if inline_sources_content && orig.is_none() {
builder.set_source_contents(src_id, Some(&f.src));
}
}
ch_state = ByteToCharPosState::default();

View File

@ -9,10 +9,13 @@ repository = "https://github.com/swc-project/swc.git"
version = "0.1.9"
[dependencies]
anyhow = "1"
indexmap = "2.0.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sourcemap = { version = "6.4", optional = true }
swc_cached = { version = "0.3.18", path = "../swc_cached" }
swc_config_macro = { version = "0.1.3", path = "../swc_config_macro" }
[lib]

View File

@ -69,6 +69,14 @@ impl<T> BoolOrDataConfig<T> {
pub fn into_inner(self) -> Option<BoolOr<T>> {
self.0
}
pub fn inner(&self) -> Option<BoolOr<&T>> {
match &self.0 {
Some(BoolOr::Data(v)) => Some(BoolOr::Data(v)),
Some(BoolOr::Bool(b)) => Some(BoolOr::Bool(*b)),
None => None,
}
}
}
impl<T> From<T> for BoolOrDataConfig<T> {

View File

@ -4,3 +4,10 @@
mod macros;
pub mod config_types;
pub mod merge;
#[cfg(feature = "sourcemap")]
mod source_map;
pub use swc_cached::{regex::CachedRegex, Error};
#[cfg(feature = "sourcemap")]
pub use crate::source_map::*;

View File

@ -0,0 +1,118 @@
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use sourcemap::{vlq::parse_vlq_segment, RawToken, SourceMap};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SourceMapContent {
Json(String),
#[serde(rename_all = "camelCase")]
Parsed {
#[serde(default)]
sources: Vec<String>,
#[serde(default)]
names: Vec<String>,
#[serde(default)]
mappings: String,
#[serde(default)]
file: Option<String>,
#[serde(default)]
source_root: Option<String>,
#[serde(default)]
sources_content: Option<Vec<Option<String>>>,
},
}
impl SourceMapContent {
pub fn to_sourcemap(&self) -> Result<SourceMap> {
match self {
SourceMapContent::Json(s) => {
SourceMap::from_slice(s.as_bytes()).context("failed to parse sourcemap")
}
SourceMapContent::Parsed {
sources,
names,
mappings,
file,
source_root,
sources_content,
} => {
let mut dst_col;
let mut src_id = 0;
let mut src_line = 0;
let mut src_col = 0;
let mut name_id = 0;
let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10;
let mut tokens = Vec::with_capacity(allocation_size);
let mut nums = Vec::with_capacity(6);
for (dst_line, line) in mappings.split(';').enumerate() {
if line.is_empty() {
continue;
}
dst_col = 0;
for segment in line.split(',') {
if segment.is_empty() {
continue;
}
nums.clear();
nums = parse_vlq_segment(segment)?;
dst_col = (i64::from(dst_col) + nums[0]) as u32;
let mut src = !0;
let mut name = !0;
if nums.len() > 1 {
if nums.len() != 4 && nums.len() != 5 {
bail!(
"invalid vlq segment size; expected 4 or 5, got {}",
nums.len()
);
}
src_id = (i64::from(src_id) + nums[1]) as u32;
if src_id >= sources.len() as u32 {
bail!("invalid source reference: {}", src_id);
}
src = src_id;
src_line = (i64::from(src_line) + nums[2]) as u32;
src_col = (i64::from(src_col) + nums[3]) as u32;
if nums.len() > 4 {
name_id = (i64::from(name_id) + nums[4]) as u32;
if name_id >= names.len() as u32 {
bail!("invalid name reference: {}", name_id);
}
name = name_id;
}
}
tokens.push(RawToken {
dst_line: dst_line as u32,
dst_col,
src_line,
src_col,
src_id: src,
name_id: name,
});
}
}
let mut map = SourceMap::new(
file.clone(),
tokens,
names.clone(),
sources.clone(),
sources_content.clone(),
);
map.set_source_root(source_root.clone());
Ok(map)
}
}
}
}

View File

@ -51,9 +51,10 @@ serde_json = "1.0.61"
tracing = "0.1.37"
swc_atoms = { version = "0.6.5", path = "../swc_atoms" }
swc_cached = { version = "0.3.18", path = "../swc_cached" }
swc_common = { version = "0.33.15", path = "../swc_common" }
swc_config = { version = "0.1.9", path = "../swc_config" }
swc_config = { version = "0.1.9", path = "../swc_config", features = [
"sourcemap",
] }
swc_ecma_ast = { version = "0.111.1", path = "../swc_ecma_ast", features = [
"serde",
] }

View File

@ -1,7 +1,7 @@
//! NOT A PUBLIC API
use serde::{Deserialize, Serialize};
use swc_config::config_types::BoolOrDataConfig;
use swc_config::{config_types::BoolOrDataConfig, SourceMapContent};
use crate::option::{
terser::{TerserCompressorOptions, TerserEcmaVersion},
@ -59,6 +59,8 @@ fn true_by_default() -> bool {
true
}
/// `sourceMap` of `minify()`.`
///
/// `jsc.minify.sourceMap`
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
@ -73,7 +75,7 @@ pub struct TerserSourceMapOption {
pub root: Option<String>,
#[serde(default)]
pub content: Option<String>,
pub content: Option<SourceMapContent>,
}
/// Parser options for `minify()`, which should have the same API as terser.

View File

@ -2,9 +2,8 @@
use serde::{Deserialize, Serialize};
use swc_atoms::JsWord;
use swc_cached::regex::CachedRegex;
use swc_common::{collections::AHashMap, Mark};
use swc_config::merge::Merge;
use swc_config::{merge::Merge, CachedRegex};
use swc_ecma_ast::{EsVersion, Expr};
/// Implement default using serde.