1use base64::Engine;
4use thiserror::Error;
5
6use crate::ModuleSpecifier;
7use crate::ProgramRef;
8use crate::SourceMap;
9use crate::swc::codegen::Node;
10use crate::swc::codegen::text_writer::JsWriter;
11use crate::swc::common::FileName;
12
13#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum SourceMapOption {
15 #[default]
17 Inline,
18 Separate,
20 None,
22}
23
24#[derive(Debug, Clone, Hash)]
25pub struct EmitOptions {
26 pub source_map: SourceMapOption,
28 pub source_map_base: Option<ModuleSpecifier>,
33 pub source_map_file: Option<String>,
35 pub inline_sources: bool,
37 pub remove_comments: bool,
39}
40
41impl Default for EmitOptions {
42 fn default() -> Self {
43 EmitOptions {
44 source_map: SourceMapOption::default(),
45 source_map_base: None,
46 source_map_file: None,
47 inline_sources: true,
48 remove_comments: false,
49 }
50 }
51}
52
53#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
55pub struct EmittedSourceText {
56 pub text: String,
58 pub source_map: Option<String>,
60}
61
62#[derive(Debug, Error, deno_error::JsError)]
63pub enum EmitError {
64 #[class(inherit)]
65 #[error(transparent)]
66 SwcEmit(std::io::Error),
67 #[class(type)]
68 #[error(transparent)]
69 SourceMap(crate::swc::sourcemap::Error),
70 #[class(type)]
71 #[error(transparent)]
72 SourceMapEncode(base64::EncodeSliceError),
73}
74
75pub fn emit(
78 program: ProgramRef,
79 comments: &dyn crate::swc::common::comments::Comments,
80 source_map: &SourceMap,
81 emit_options: &EmitOptions,
82) -> Result<EmittedSourceText, EmitError> {
83 let source_map = source_map.inner();
84 let mut src_map_buf = vec![];
85 let mut src_buf = vec![];
86 {
87 let mut writer = Box::new(JsWriter::new(
88 source_map.clone(),
89 "\n",
90 &mut src_buf,
91 Some(&mut src_map_buf),
92 ));
93 writer.set_indent_str(" "); let mut emitter = crate::swc::codegen::Emitter {
96 cfg: swc_codegen_config(),
97 comments: if emit_options.remove_comments {
98 None
99 } else {
100 Some(&comments)
101 },
102 cm: source_map.clone(),
103 wr: writer,
104 };
105 match program {
106 ProgramRef::Module(n) => {
107 n.emit_with(&mut emitter).map_err(EmitError::SwcEmit)?;
108 }
109 ProgramRef::Script(n) => {
110 n.emit_with(&mut emitter).map_err(EmitError::SwcEmit)?;
111 }
112 }
113 }
114
115 let mut map: Option<Vec<u8>> = None;
116
117 if emit_options.source_map != SourceMapOption::None {
118 let mut map_buf = Vec::new();
119 let source_map_config = SourceMapConfig {
120 inline_sources: emit_options.inline_sources,
121 maybe_base: emit_options.source_map_base.as_ref(),
122 };
123 let mut source_map =
124 source_map.build_source_map(&src_map_buf, None, source_map_config);
125 if let Some(file) = &emit_options.source_map_file {
126 source_map.set_file(Some(file.to_string()));
127 }
128 source_map
129 .to_writer(&mut map_buf)
130 .map_err(EmitError::SourceMap)?;
131
132 if emit_options.source_map == SourceMapOption::Inline {
133 let mut inline_buf = vec![0; map_buf.len() * 4 / 3 + 4];
135 let size = base64::prelude::BASE64_STANDARD
136 .encode_slice(map_buf, &mut inline_buf)
137 .map_err(EmitError::SourceMapEncode)?;
138 let inline_buf = &inline_buf[..size];
139 let prelude_text = "//# sourceMappingURL=data:application/json;base64,";
140 let src_has_trailing_newline = src_buf.ends_with(b"\n");
141 let additional_capacity = if src_has_trailing_newline { 0 } else { 1 }
142 + prelude_text.len()
143 + inline_buf.len();
144 let expected_final_capacity = src_buf.len() + additional_capacity;
145 src_buf.reserve(additional_capacity);
146 if !src_has_trailing_newline {
147 src_buf.push(b'\n');
148 }
149 src_buf.extend(prelude_text.as_bytes());
150 src_buf.extend(inline_buf);
151 debug_assert_eq!(src_buf.len(), expected_final_capacity);
152 } else {
153 map = Some(map_buf);
154 }
155 }
156
157 debug_assert!(std::str::from_utf8(&src_buf).is_ok(), "valid utf-8");
158 if let Some(map) = &map {
159 debug_assert!(std::str::from_utf8(map).is_ok(), "valid utf-8");
160 }
161
162 Ok(EmittedSourceText {
165 text: unsafe { String::from_utf8_unchecked(src_buf) },
168 source_map: map.map(|b| unsafe { String::from_utf8_unchecked(b) }),
170 })
171}
172
173#[derive(Debug)]
176pub struct SourceMapConfig<'a> {
177 pub inline_sources: bool,
178 pub maybe_base: Option<&'a ModuleSpecifier>,
179}
180
181impl crate::swc::common::source_map::SourceMapGenConfig
182 for SourceMapConfig<'_>
183{
184 fn file_name_to_source(&self, f: &FileName) -> String {
185 match f {
186 FileName::Url(specifier) => self
187 .maybe_base
188 .and_then(|base| {
189 debug_assert!(
190 base.as_str().ends_with('/'),
191 "source map base should end with a slash"
192 );
193 base.make_relative(specifier)
194 })
195 .filter(|relative| !relative.is_empty())
196 .unwrap_or_else(|| f.to_string()),
197 _ => f.to_string(),
198 }
199 }
200
201 fn inline_sources_content(&self, f: &FileName) -> bool {
202 match f {
203 FileName::Real(..) | FileName::Custom(..) => false,
204 FileName::Url(..) => self.inline_sources,
205 _ => true,
206 }
207 }
208}
209
210pub fn swc_codegen_config() -> crate::swc::codegen::Config {
211 let mut config = crate::swc::codegen::Config::default();
216 config.target = crate::ES_VERSION;
217 config.ascii_only = false;
218 config.minify = false;
219 config.omit_last_semi = false;
220 config.emit_assert_for_import_attributes = false;
221 config.inline_script = false;
222 config
223}