Skip to main content

intlayer_swc_plugin/
logger.rs

1//! Build-time reporting.
2//!
3//! Mirrors what the Babel/Vite optimize pipeline prints on the JavaScript side
4//! so a Next.js build driven by this plugin can be inspected the same way.
5//! Output is opt-in through the `logLevel` plugin option and goes to stdout,
6//! which both the Wasm host and a native embedder capture.
7
8use crate::config::LogLevel;
9use swc_core::{
10    common::{sync::Lrc, SourceMap},
11    ecma::{
12        ast::Program,
13        codegen::{text_writer::JsWriter, Config as CodegenConfig, Emitter},
14    },
15};
16
17/// Prefix every line carries so plugin output is greppable in a build log.
18const LOG_PREFIX: &str = "[intlayer/swc]";
19
20/// Forces the plugin to report at [`LogLevel::Debug`] whatever the `logLevel`
21/// option says, so the emitted code of every transformed file can be inspected
22/// without threading a config change through the JavaScript side.
23///
24/// Development aid only — set back to `false` before publishing: it prints the
25/// full output of every file the transform touches.
26pub static DEBUG_LOG: bool = false;
27
28/// Reports what the transform did, at the verbosity the user asked for.
29#[derive(Debug, Clone, Copy)]
30pub struct Logger {
31    level: LogLevel,
32}
33
34impl Logger {
35    /// Builds a logger for the given level.
36    pub fn new(level: LogLevel) -> Self {
37        Self { level }
38    }
39
40    /// Whether anything at all will be printed.
41    pub fn is_enabled(&self) -> bool {
42        self.level > LogLevel::Off
43    }
44
45    /// Whether the emitted code and per-file skip decisions are printed.
46    pub fn is_debug(&self) -> bool {
47        self.level >= LogLevel::Debug
48    }
49
50    /// Prints a line at `info` verbosity.
51    pub fn info(&self, message: impl AsRef<str>) {
52        if self.level >= LogLevel::Info {
53            println!("{} {}", LOG_PREFIX, message.as_ref());
54        }
55    }
56
57    /// Prints a line at `debug` verbosity.
58    pub fn debug(&self, message: impl AsRef<str>) {
59        if self.is_debug() {
60            println!("{} {}", LOG_PREFIX, message.as_ref());
61        }
62    }
63
64    /// Reports the outcome of a transformed file: how many dictionary imports
65    /// were injected, how many call sites were rewritten, and how many content
66    /// field accesses were renamed. At `debug` verbosity the emitted code
67    /// follows.
68    pub fn report_file(&self, file_path: &str, summary: &TransformSummary, program: &Program) {
69        if !self.is_enabled() {
70            return;
71        }
72
73        if !summary.changed_anything() {
74            self.debug(format!("{}: unchanged", file_path));
75            return;
76        }
77
78        self.info(format!(
79            "{}: {} static import(s), {} dynamic import(s), {} field rename(s)",
80            file_path, summary.static_imports, summary.dynamic_imports, summary.renamed_fields
81        ));
82
83        if self.is_debug() {
84            self.debug(format!(
85                "{}: output\n{}",
86                file_path,
87                program_to_code(program)
88            ));
89        }
90    }
91}
92
93/// Counters describing what the transform changed in a single file.
94#[derive(Debug, Default, Clone, Copy)]
95pub struct TransformSummary {
96    /// Number of injected static dictionary imports.
97    pub static_imports: usize,
98    /// Number of injected dynamic / fetch loader imports.
99    pub dynamic_imports: usize,
100    /// Number of content field accesses rewritten to their short alias.
101    pub renamed_fields: usize,
102}
103
104impl TransformSummary {
105    /// Whether the transform touched the file at all.
106    pub fn changed_anything(&self) -> bool {
107        self.static_imports > 0 || self.dynamic_imports > 0 || self.renamed_fields > 0
108    }
109}
110
111/// Emits a `Program` AST back to JavaScript/TypeScript source code as a `String`.
112/// Used exclusively for debug logging.
113pub fn program_to_code(program: &Program) -> String {
114    let source_map = Lrc::new(SourceMap::default());
115    let mut buffer = vec![];
116    {
117        let writer = JsWriter::new(source_map.clone(), "\n", &mut buffer, None);
118        let mut emitter = Emitter {
119            cfg: CodegenConfig::default(),
120            cm: source_map.clone(),
121            comments: None,
122            wr: writer,
123        };
124        let _ = emitter.emit_program(program);
125    }
126    String::from_utf8_lossy(&buffer).into_owned()
127}