mirror of
https://github.com/swc-project/swc.git
synced 2024-11-23 09:38:16 +03:00
feat(cli): Support stdin (#3688)
This commit is contained in:
parent
61d9e329c9
commit
3cf3b20f15
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2740,6 +2740,7 @@ name = "swc_cli"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
"clap",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
|
@ -22,6 +22,7 @@ swc_common = { version = "0.17.5", path = "../swc_common" }
|
||||
relative-path = "1.6.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", features = ["unbounded_depth"] }
|
||||
atty = "0.2.14"
|
||||
|
||||
[dependencies.path-absolutize]
|
||||
version = "3.0.11"
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, BufRead},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
@ -13,26 +14,75 @@ use swc::{
|
||||
config::{Config, Options},
|
||||
try_with_handler, Compiler, TransformOutput,
|
||||
};
|
||||
use swc_common::{sync::Lazy, FilePathMapping, SourceMap};
|
||||
use swc_common::{sync::Lazy, FileName, FilePathMapping, SourceMap};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// Configuration option for transform files.
|
||||
#[derive(Parser)]
|
||||
pub struct CompileOptions {
|
||||
/// Override a config from .swcrc file.
|
||||
#[clap(long)]
|
||||
config: Option<Vec<String>>,
|
||||
|
||||
/// Path to a .swcrc file to use
|
||||
#[clap(long = "config")]
|
||||
#[clap(long)]
|
||||
config_file: Option<PathBuf>,
|
||||
|
||||
/// Files to compile
|
||||
files: Vec<PathBuf>,
|
||||
/// Filename to use when reading from stdin - this will be used in
|
||||
/// source-maps, errors etc
|
||||
#[clap(long, short = 'f', group = "input")]
|
||||
filename: Option<PathBuf>,
|
||||
|
||||
/// The name of the 'env' to use when loading configs and plugins. Defaults
|
||||
/// to the value of SWC_ENV, or else NODE_ENV, or else development.
|
||||
#[clap(long)]
|
||||
env_name: Option<String>,
|
||||
|
||||
/// List of glob paths to not compile.
|
||||
#[clap(long)]
|
||||
ignore: Option<String>,
|
||||
|
||||
/// Values: true|false|inline|both
|
||||
#[clap(long)]
|
||||
source_maps: Option<String>,
|
||||
|
||||
/// Define the file for the source map.
|
||||
#[clap(long)]
|
||||
source_maps_target: Option<String>,
|
||||
|
||||
/// Set sources[0] on returned source map
|
||||
#[clap(long)]
|
||||
source_file_name: Option<String>,
|
||||
|
||||
/// The root from which all sources are relative.
|
||||
#[clap(long)]
|
||||
source_root: Option<String>,
|
||||
|
||||
/// Automatically recompile files on change
|
||||
#[clap(long)]
|
||||
watch: bool,
|
||||
|
||||
/// Compile all input files into a single file.
|
||||
#[clap(long, group = "output")]
|
||||
out_file: Option<PathBuf>,
|
||||
|
||||
/// The output directory
|
||||
#[clap(long)]
|
||||
#[clap(long, group = "output")]
|
||||
out_dir: Option<PathBuf>,
|
||||
|
||||
/// Specify specific file extensions to compile.
|
||||
#[clap(long)]
|
||||
extensions: Option<Vec<String>>,
|
||||
|
||||
/// Files to compile
|
||||
#[clap(group = "input")]
|
||||
files: Vec<PathBuf>,
|
||||
//Flags legacy @swc/cli supports, might need some thoughts if we need support same.
|
||||
//log_watch_compilation: bool,
|
||||
//copy_files: bool,
|
||||
//include_dotfiles: bool,
|
||||
//only: Option<String>,
|
||||
//no_swcrc: bool,
|
||||
}
|
||||
|
||||
static COMPILER: Lazy<Arc<Compiler>> = Lazy::new(|| {
|
||||
@ -80,7 +130,7 @@ fn get_files_list(
|
||||
|
||||
fn build_transform_options(
|
||||
config_file: &Option<PathBuf>,
|
||||
file_path: &Path,
|
||||
file_path: &Option<&Path>,
|
||||
) -> anyhow::Result<Options> {
|
||||
let base_options = Options::default();
|
||||
let base_config = Config::default();
|
||||
@ -94,12 +144,17 @@ fn build_transform_options(
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Options {
|
||||
let mut ret = Options {
|
||||
config: Config { ..base_config },
|
||||
config_file,
|
||||
filename: file_path.to_str().unwrap_or_default().to_owned(),
|
||||
..base_options
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(file_path) = file_path {
|
||||
ret.filename = file_path.to_str().unwrap_or_default().to_owned();
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Calculate full, absolute path to the file to emit.
|
||||
@ -176,39 +231,96 @@ fn emit_output(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_stdin_input() -> Option<String> {
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
io::stdin()
|
||||
.lock()
|
||||
.lines()
|
||||
.map(|line| line.expect("Not able to read stdin"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
impl super::CommandRunner for CompileOptions {
|
||||
fn execute(&self) -> anyhow::Result<()> {
|
||||
let included_extensions = if let Some(extensions) = &self.extensions {
|
||||
extensions.clone()
|
||||
} else {
|
||||
DEFAULT_EXTENSIONS.iter().map(|v| v.to_string()).collect()
|
||||
};
|
||||
let stdin_input = collect_stdin_input();
|
||||
|
||||
let files = get_files_list(&self.files, &included_extensions, false)?;
|
||||
let cm = COMPILER.clone();
|
||||
|
||||
if let Some(out_dir) = &self.out_dir {
|
||||
fs::create_dir_all(out_dir)?;
|
||||
if stdin_input.is_some() && !self.files.is_empty() {
|
||||
anyhow::bail!("Cannot specify inputs from stdin and files at the same time");
|
||||
}
|
||||
|
||||
files
|
||||
.into_par_iter()
|
||||
.try_for_each_with(cm, |compiler, file_path| {
|
||||
let result = try_with_handler(compiler.cm.clone(), false, |handler| {
|
||||
let options = build_transform_options(&self.config_file, &file_path)?;
|
||||
let fm = compiler
|
||||
.cm
|
||||
.load_file(&file_path)
|
||||
.context("failed to load file")?;
|
||||
compiler.process_js_file(fm, handler, &options)
|
||||
if stdin_input.is_none() && self.files.is_empty() {
|
||||
anyhow::bail!("Input is empty");
|
||||
}
|
||||
|
||||
if let Some(stdin_input) = stdin_input {
|
||||
let comp = COMPILER.clone();
|
||||
|
||||
let result = try_with_handler(comp.cm.clone(), false, |handler| {
|
||||
let options =
|
||||
build_transform_options(&self.config_file, &self.filename.as_deref())?;
|
||||
|
||||
let fm = comp.cm.new_source_file(
|
||||
if options.filename.is_empty() {
|
||||
FileName::Anon
|
||||
} else {
|
||||
FileName::Real(options.filename.clone().into())
|
||||
},
|
||||
stdin_input,
|
||||
);
|
||||
|
||||
comp.process_js_file(fm, handler, &options)
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(output) => emit_output(
|
||||
&output,
|
||||
&self.out_dir,
|
||||
self.filename.as_ref().unwrap_or(&PathBuf::from("unknown")),
|
||||
)?,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !self.files.is_empty() {
|
||||
let included_extensions = if let Some(extensions) = &self.extensions {
|
||||
extensions.clone()
|
||||
} else {
|
||||
DEFAULT_EXTENSIONS.iter().map(|v| v.to_string()).collect()
|
||||
};
|
||||
|
||||
let files = get_files_list(&self.files, &included_extensions, false)?;
|
||||
let cm = COMPILER.clone();
|
||||
|
||||
return files
|
||||
.into_par_iter()
|
||||
.try_for_each_with(cm, |compiler, file_path| {
|
||||
let result = try_with_handler(compiler.cm.clone(), false, |handler| {
|
||||
let options =
|
||||
build_transform_options(&self.config_file, &Some(&file_path))?;
|
||||
let fm = compiler
|
||||
.cm
|
||||
.load_file(&file_path)
|
||||
.context("failed to load file")?;
|
||||
compiler.process_js_file(fm, handler, &options)
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(output) => emit_output(&output, &self.out_dir, &file_path)?,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
match result {
|
||||
Ok(output) => emit_output(&output, &self.out_dir, &file_path)?,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user