mirror of
https://github.com/swc-project/swc.git
synced 2024-09-11 08:45:34 +03:00
Compiler apis & .swcrc improvement (#434)
- Expose high-level compiler apis (#431) - Support multiple entries in .swcrc (#414)
This commit is contained in:
parent
2add7a08ef
commit
ae3326cd9d
@ -57,7 +57,7 @@ deploy:
|
||||
provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GH_TOKEN
|
||||
email: kdy1@outlook.kr
|
||||
email: kdy1997.dev@gmail.com
|
||||
name: "강동윤"
|
||||
on:
|
||||
branch: master
|
||||
|
16
Cargo.toml
16
Cargo.toml
@ -3,11 +3,12 @@
|
||||
[package]
|
||||
name = "swc"
|
||||
version = "0.1.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc/"
|
||||
description = "Speedy web compiler"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "swc"
|
||||
@ -16,7 +17,18 @@ name = "swc"
|
||||
swc_atoms = { path ="./atoms" }
|
||||
swc_common = { path ="./common" }
|
||||
swc_ecmascript = { path ="./ecmascript" }
|
||||
sourcemap = "2.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sourcemap = "2"
|
||||
failure = "0.1"
|
||||
path-clean = "0.1"
|
||||
lazy_static = "1"
|
||||
hashbrown = "0.5"
|
||||
regex = "1"
|
||||
chashmap = "2.2.2"
|
||||
|
||||
[[example]]
|
||||
name = "usage"
|
||||
|
||||
[profile.bench]
|
||||
lto = true
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "swc_atoms"
|
||||
build = "build.rs"
|
||||
version = "0.1.2"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_atoms/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_common"
|
||||
version = "0.3.1"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_common/"
|
||||
|
@ -108,6 +108,12 @@ pub struct SourceMap {
|
||||
doctest_offset: Option<(FileName, isize)>,
|
||||
}
|
||||
|
||||
impl Default for SourceMap {
|
||||
fn default() -> Self {
|
||||
Self::new(FilePathMapping::empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceMap {
|
||||
pub fn new(path_mapping: FilePathMapping) -> SourceMap {
|
||||
SourceMap {
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecmascript"
|
||||
version = "0.1.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecmascript/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.9.3"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_ast/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_codegen"
|
||||
version = "0.7.2"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_codegen/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_codegen_macros"
|
||||
version = "0.3.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_codegen_macros/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_lints"
|
||||
version = "0.1.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.10.3"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_parser/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_parser_macros"
|
||||
version = "0.3.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_parser_macros/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_ecma_transforms"
|
||||
version = "0.1.2"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_transforms/"
|
||||
|
27
examples/usage.rs
Normal file
27
examples/usage.rs
Normal file
@ -0,0 +1,27 @@
|
||||
extern crate swc;
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
use swc::{
|
||||
common::{
|
||||
errors::{ColorConfig, Handler},
|
||||
SourceMap,
|
||||
},
|
||||
config::Options,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let cm = Arc::<SourceMap>::default();
|
||||
let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
|
||||
let c = swc::Compiler::new(cm.clone(), handler);
|
||||
|
||||
let fm = cm
|
||||
.load_file(Path::new("foo.js"))
|
||||
.expect("failed to load file");
|
||||
|
||||
c.process_js_file(
|
||||
fm,
|
||||
Options {
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ast_node"
|
||||
version = "0.4.2"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/ast_node/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "swc_macros_common"
|
||||
version = "0.2.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/swc_macros_common/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "enum_kind"
|
||||
version = "0.1.3"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/enum_kind/"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "string_enum"
|
||||
version = "0.2.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/string_enum/"
|
||||
|
106
src/builder.rs
Normal file
106
src/builder.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use crate::config::{GlobalPassOption, JscTarget, ModuleConfig};
|
||||
use atoms::JsWord;
|
||||
use common::{errors::Handler, SourceMap};
|
||||
use ecmascript::{
|
||||
ast::Module,
|
||||
transforms::{
|
||||
chain_at, compat, const_modules, fixer, helpers, hygiene, modules,
|
||||
pass::{JoinedPass, Optional, Pass},
|
||||
typescript,
|
||||
},
|
||||
};
|
||||
use hashbrown::hash_map::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Builder is used to create a high performance `Compiler`.
|
||||
pub struct PassBuilder<'a, 'b, P: Pass> {
|
||||
cm: &'a Arc<SourceMap>,
|
||||
handler: &'b Handler,
|
||||
pass: P,
|
||||
target: JscTarget,
|
||||
}
|
||||
|
||||
impl<'a, 'b, P: Pass> PassBuilder<'a, 'b, P> {
|
||||
pub fn new(cm: &'a Arc<SourceMap>, handler: &'b Handler, pass: P) -> Self {
|
||||
PassBuilder {
|
||||
cm,
|
||||
handler,
|
||||
pass,
|
||||
target: JscTarget::Es5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn then<N>(self, next: N) -> PassBuilder<'a, 'b, JoinedPass<P, N, Module>> {
|
||||
let pass = chain_at!(Module, self.pass, next);
|
||||
PassBuilder {
|
||||
cm: self.cm,
|
||||
handler: self.handler,
|
||||
pass,
|
||||
target: self.target,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn const_modules(
|
||||
self,
|
||||
globals: HashMap<JsWord, HashMap<JsWord, String>>,
|
||||
) -> PassBuilder<'a, 'b, impl Pass> {
|
||||
self.then(const_modules(globals))
|
||||
}
|
||||
|
||||
pub fn inline_globals(self, c: GlobalPassOption) -> PassBuilder<'a, 'b, impl Pass> {
|
||||
let pass = c.build(&self.cm, &self.handler);
|
||||
self.then(pass)
|
||||
}
|
||||
|
||||
pub fn strip_typescript(self) -> PassBuilder<'a, 'b, impl Pass> {
|
||||
self.then(typescript::strip())
|
||||
}
|
||||
|
||||
pub fn target(mut self, target: JscTarget) -> Self {
|
||||
self.target = target;
|
||||
self
|
||||
}
|
||||
|
||||
/// # Arguments
|
||||
/// ## module
|
||||
/// - Use `None` if you want swc to emit import statements.
|
||||
///
|
||||
///
|
||||
/// Returned pass includes
|
||||
///
|
||||
/// - compatibility helper
|
||||
/// - module handler
|
||||
/// - helper injector
|
||||
/// - identifier hygiene handler
|
||||
/// - fixer
|
||||
pub fn finalize(self, module: Option<ModuleConfig>) -> impl Pass {
|
||||
let need_interop_analysis = match module {
|
||||
Some(ModuleConfig::CommonJs(ref c)) => !c.no_interop,
|
||||
Some(ModuleConfig::Amd(ref c)) => !c.config.no_interop,
|
||||
Some(ModuleConfig::Umd(ref c)) => !c.config.no_interop,
|
||||
None => false,
|
||||
};
|
||||
|
||||
chain_at!(
|
||||
Module,
|
||||
self.pass,
|
||||
// compat
|
||||
Optional::new(compat::es2018(), self.target <= JscTarget::Es2018),
|
||||
Optional::new(compat::es2017(), self.target <= JscTarget::Es2017),
|
||||
Optional::new(compat::es2016(), self.target <= JscTarget::Es2016),
|
||||
Optional::new(compat::es2015(), self.target <= JscTarget::Es2015),
|
||||
Optional::new(compat::es3(), self.target <= JscTarget::Es3),
|
||||
// module / helper
|
||||
Optional::new(
|
||||
modules::import_analysis::import_analyzer(),
|
||||
need_interop_analysis
|
||||
),
|
||||
helpers::InjectHelpers,
|
||||
ModuleConfig::build(self.cm.clone(), module),
|
||||
// hygiene
|
||||
hygiene(),
|
||||
// fixer
|
||||
fixer(),
|
||||
)
|
||||
}
|
||||
}
|
29
src/config/array.json
Normal file
29
src/config/array.json
Normal file
@ -0,0 +1,29 @@
|
||||
[
|
||||
{
|
||||
"test": "\\.ts",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": false,
|
||||
"decorators": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "\\.js",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "ecmascript",
|
||||
"jsx": false,
|
||||
"dynamicImport": false,
|
||||
"numericSeparator": false,
|
||||
"classPrivateProperty": false,
|
||||
"privateMethod": false,
|
||||
"classProperty": false,
|
||||
"functionBind": false,
|
||||
"decorators": false,
|
||||
"decoratorsBeforeExport": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
609
src/config/mod.rs
Normal file
609
src/config/mod.rs
Normal file
@ -0,0 +1,609 @@
|
||||
use crate::{builder::PassBuilder, error::Error};
|
||||
use atoms::JsWord;
|
||||
use chashmap::CHashMap;
|
||||
use common::{errors::Handler, FileName, SourceMap};
|
||||
use ecmascript::{
|
||||
ast::{Expr, Module, ModuleItem, Stmt},
|
||||
parser::{Parser, Session as ParseSess, SourceFileInput, Syntax},
|
||||
transforms::{
|
||||
chain_at, const_modules, modules,
|
||||
pass::{noop, Optional, Pass},
|
||||
proposals::{class_properties, decorators, export},
|
||||
react, resolver, simplifier, typescript, InlineGlobals,
|
||||
},
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParseOptions {
|
||||
#[serde(default)]
|
||||
pub comments: bool,
|
||||
#[serde(flatten)]
|
||||
pub syntax: Syntax,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct Options {
|
||||
#[serde(flatten, default)]
|
||||
pub config: Option<Config>,
|
||||
|
||||
#[serde(default = "default_cwd")]
|
||||
pub cwd: PathBuf,
|
||||
|
||||
#[serde(default)]
|
||||
pub caller: Option<CallerOptions>,
|
||||
|
||||
#[serde(default)]
|
||||
pub filename: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub config_file: Option<ConfigFile>,
|
||||
|
||||
#[serde(default)]
|
||||
pub root: Option<PathBuf>,
|
||||
|
||||
#[serde(default)]
|
||||
pub root_mode: RootMode,
|
||||
|
||||
#[serde(default = "default_swcrc")]
|
||||
pub swcrc: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub swcrc_roots: Option<PathBuf>,
|
||||
|
||||
#[serde(default = "default_env_name")]
|
||||
pub env_name: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub input_source_map: Option<InputSourceMap>,
|
||||
|
||||
#[serde(default)]
|
||||
pub source_maps: Option<SourceMapsConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub source_file_name: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub source_root: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SourceMapsConfig {
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
impl Default for SourceMapsConfig {
|
||||
fn default() -> Self {
|
||||
SourceMapsConfig::Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InputSourceMap {
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
impl Default for InputSourceMap {
|
||||
fn default() -> Self {
|
||||
InputSourceMap::Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn build(
|
||||
&self,
|
||||
cm: &Arc<SourceMap>,
|
||||
handler: &Handler,
|
||||
config: Option<Config>,
|
||||
) -> BuiltConfig<impl Pass> {
|
||||
let mut config = config.unwrap_or_else(|| Default::default());
|
||||
if let Some(ref c) = self.config {
|
||||
config.merge(c)
|
||||
}
|
||||
|
||||
let JscConfig {
|
||||
transform,
|
||||
syntax,
|
||||
external_helpers,
|
||||
target,
|
||||
} = config.jsc;
|
||||
|
||||
let syntax = syntax.unwrap_or_default();
|
||||
let transform = transform.unwrap_or_default();
|
||||
|
||||
let const_modules = {
|
||||
let enabled = transform.const_modules.is_some();
|
||||
let config = transform.const_modules.unwrap_or_default();
|
||||
|
||||
let globals = config.globals;
|
||||
|
||||
Optional::new(const_modules(globals), enabled)
|
||||
};
|
||||
|
||||
let optimizer = transform.optimizer;
|
||||
let enable_optimizer = optimizer.is_some();
|
||||
let pass = if let Some(opts) =
|
||||
optimizer.map(|o| o.globals.unwrap_or_else(|| Default::default()))
|
||||
{
|
||||
opts.build(cm, handler)
|
||||
} else {
|
||||
GlobalPassOption::default().build(cm, handler)
|
||||
};
|
||||
|
||||
let pass = chain_at!(
|
||||
Module,
|
||||
// handle jsx
|
||||
Optional::new(react::react(cm.clone(), transform.react), syntax.jsx()),
|
||||
Optional::new(typescript::strip(), syntax.typescript()),
|
||||
resolver(),
|
||||
const_modules,
|
||||
pass,
|
||||
Optional::new(decorators(), syntax.decorators()),
|
||||
Optional::new(class_properties(), syntax.class_props()),
|
||||
Optional::new(
|
||||
export(),
|
||||
syntax.export_default_from() || syntax.export_namespace_from()
|
||||
),
|
||||
Optional::new(simplifier(), enable_optimizer),
|
||||
);
|
||||
|
||||
let pass = PassBuilder::new(&cm, &handler, pass)
|
||||
.target(target)
|
||||
.finalize(config.module);
|
||||
|
||||
BuiltConfig {
|
||||
minify: config.minify.unwrap_or(false),
|
||||
pass,
|
||||
external_helpers,
|
||||
syntax,
|
||||
source_maps: self
|
||||
.source_maps
|
||||
.as_ref()
|
||||
.map(|s| match s {
|
||||
SourceMapsConfig::Bool(v) => *v,
|
||||
// TODO: Handle source map
|
||||
SourceMapsConfig::Str(_) => true,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum RootMode {
|
||||
#[serde(rename = "root")]
|
||||
Root,
|
||||
#[serde(rename = "upward")]
|
||||
Upward,
|
||||
#[serde(rename = "upward-optional")]
|
||||
UpwardOptional,
|
||||
}
|
||||
|
||||
impl Default for RootMode {
|
||||
fn default() -> Self {
|
||||
RootMode::Root
|
||||
}
|
||||
}
|
||||
const fn default_swcrc() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ConfigFile {
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
impl Default for ConfigFile {
|
||||
fn default() -> Self {
|
||||
ConfigFile::Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallerOptions {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
fn default_cwd() -> PathBuf {
|
||||
::std::env::current_dir().unwrap()
|
||||
}
|
||||
|
||||
/// `.swcrc` file
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Rc {
|
||||
Single(Config),
|
||||
Multi(Vec<Config>),
|
||||
}
|
||||
|
||||
impl Rc {
|
||||
pub fn into_config(self, filename: Option<&Path>) -> Result<Config, Error> {
|
||||
let cs = match self {
|
||||
Rc::Single(c) => match filename {
|
||||
Some(filename) => {
|
||||
if c.matches(filename)? {
|
||||
return Ok(c);
|
||||
} else {
|
||||
return Err(Error::Unmatched);
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
None => return Ok(c),
|
||||
},
|
||||
Rc::Multi(cs) => cs,
|
||||
};
|
||||
|
||||
match filename {
|
||||
Some(filename) => {
|
||||
for c in cs {
|
||||
if c.matches(filename)? {
|
||||
return Ok(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
None => {
|
||||
let mut first = None;
|
||||
for c in cs {
|
||||
if c.test.is_none() {
|
||||
return Ok(c);
|
||||
}
|
||||
|
||||
if first.is_none() {
|
||||
first = Some(c);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(first.unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Unmatched)
|
||||
}
|
||||
}
|
||||
|
||||
/// A single object in the `.swcrc` file
|
||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub test: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub jsc: JscConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub module: Option<ModuleConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub minify: Option<bool>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn matches(&self, filename: &Path) -> Result<bool, Error> {
|
||||
lazy_static! {
|
||||
static ref CACHE: CHashMap<String, Regex> = Default::default();
|
||||
}
|
||||
|
||||
match self.test {
|
||||
Some(ref test) => {
|
||||
if !CACHE.contains_key(&*test) {
|
||||
let re = Regex::new(&test).map_err(|err| Error::InvalidRegex { err })?;
|
||||
CACHE.insert(test.clone(), re);
|
||||
}
|
||||
|
||||
let re = CACHE.get(&*test).unwrap();
|
||||
|
||||
Ok(re.is_match(&filename.display().to_string()))
|
||||
}
|
||||
None => Ok(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One `BuiltConfig` per a directory with swcrc
|
||||
pub struct BuiltConfig<P: Pass> {
|
||||
pub pass: P,
|
||||
pub syntax: Syntax,
|
||||
pub minify: bool,
|
||||
pub external_helpers: bool,
|
||||
pub source_maps: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct JscConfig {
|
||||
#[serde(rename = "parser", default)]
|
||||
pub syntax: Option<Syntax>,
|
||||
|
||||
#[serde(default)]
|
||||
pub transform: Option<TransformConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub external_helpers: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub target: JscTarget,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub enum JscTarget {
|
||||
#[serde(rename = "es3")]
|
||||
Es3,
|
||||
#[serde(rename = "es5")]
|
||||
Es5,
|
||||
#[serde(rename = "es2015")]
|
||||
Es2015,
|
||||
#[serde(rename = "es2016")]
|
||||
Es2016,
|
||||
#[serde(rename = "es2017")]
|
||||
Es2017,
|
||||
#[serde(rename = "es2018")]
|
||||
Es2018,
|
||||
#[serde(rename = "es2019")]
|
||||
Es2019,
|
||||
}
|
||||
|
||||
impl Default for JscTarget {
|
||||
fn default() -> Self {
|
||||
JscTarget::Es3
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ModuleConfig {
|
||||
#[serde(rename = "commonjs")]
|
||||
CommonJs(modules::common_js::Config),
|
||||
#[serde(rename = "umd")]
|
||||
Umd(modules::umd::Config),
|
||||
#[serde(rename = "amd")]
|
||||
Amd(modules::amd::Config),
|
||||
}
|
||||
|
||||
impl ModuleConfig {
|
||||
pub fn build(cm: Arc<SourceMap>, config: Option<ModuleConfig>) -> Box<dyn Pass> {
|
||||
match config {
|
||||
None => box noop(),
|
||||
Some(ModuleConfig::CommonJs(config)) => box modules::common_js::common_js(config),
|
||||
Some(ModuleConfig::Umd(config)) => box modules::umd::umd(cm, config),
|
||||
Some(ModuleConfig::Amd(config)) => box modules::amd::amd(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct TransformConfig {
|
||||
#[serde(default)]
|
||||
pub react: react::Options,
|
||||
|
||||
#[serde(default)]
|
||||
pub const_modules: Option<ConstModulesConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub optimizer: Option<OptimizerConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ConstModulesConfig {
|
||||
#[serde(default)]
|
||||
pub globals: HashMap<JsWord, HashMap<JsWord, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct OptimizerConfig {
|
||||
#[serde(default)]
|
||||
pub globals: Option<GlobalPassOption>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct GlobalPassOption {
|
||||
#[serde(default)]
|
||||
pub vars: HashMap<String, String>,
|
||||
#[serde(default = "default_envs")]
|
||||
pub envs: HashSet<String>,
|
||||
}
|
||||
|
||||
fn default_envs() -> HashSet<String> {
|
||||
let mut v = HashSet::default();
|
||||
v.insert(String::from("NODE_ENV"));
|
||||
v.insert(String::from("SWC_ENV"));
|
||||
v
|
||||
}
|
||||
|
||||
impl GlobalPassOption {
|
||||
pub fn build(self, cm: &SourceMap, handler: &Handler) -> InlineGlobals {
|
||||
fn mk_map(
|
||||
cm: &SourceMap,
|
||||
handler: &Handler,
|
||||
values: impl Iterator<Item = (String, String)>,
|
||||
is_env: bool,
|
||||
) -> HashMap<JsWord, Expr> {
|
||||
let mut m = HashMap::default();
|
||||
|
||||
for (k, v) in values {
|
||||
let v = if is_env {
|
||||
format!("'{}'", v)
|
||||
} else {
|
||||
(*v).into()
|
||||
};
|
||||
let v_str = v.clone();
|
||||
let fm = cm.new_source_file(FileName::Custom(format!("GLOBAL.{}", k)), v);
|
||||
let session = ParseSess { handler };
|
||||
let mut module = Parser::new(
|
||||
session,
|
||||
Syntax::Es(Default::default()),
|
||||
SourceFileInput::from(&*fm),
|
||||
None,
|
||||
)
|
||||
.parse_module()
|
||||
.map_err(|mut e| {
|
||||
e.emit();
|
||||
()
|
||||
})
|
||||
.unwrap_or_else(|()| {
|
||||
panic!(
|
||||
"failed to parse global variable {}=`{}` as module",
|
||||
k, v_str
|
||||
)
|
||||
});
|
||||
|
||||
let expr = match module.body.pop() {
|
||||
Some(ModuleItem::Stmt(Stmt::Expr(box expr))) => expr,
|
||||
_ => panic!("{} is not a valid expression", v_str),
|
||||
};
|
||||
|
||||
m.insert((*k).into(), expr);
|
||||
}
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
let envs = self.envs;
|
||||
InlineGlobals {
|
||||
globals: mk_map(cm, handler, self.vars.into_iter(), false),
|
||||
envs: mk_map(
|
||||
cm,
|
||||
handler,
|
||||
env::vars().filter(|(k, _)| envs.contains(&*k)),
|
||||
true,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_env_name() -> String {
|
||||
match env::var("SWC_ENV") {
|
||||
Ok(v) => return v,
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
match env::var("NODE_ENV") {
|
||||
Ok(v) => return v,
|
||||
Err(_) => return "development".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Merge {
|
||||
/// Apply overrides from `from`
|
||||
fn merge(&mut self, from: &Self);
|
||||
}
|
||||
|
||||
impl<T: Clone> Merge for Option<T>
|
||||
where
|
||||
T: Merge,
|
||||
{
|
||||
fn merge(&mut self, from: &Option<T>) {
|
||||
match *from {
|
||||
Some(ref from) => match *self {
|
||||
Some(ref mut v) => v.merge(from),
|
||||
None => *self = Some(from.clone()),
|
||||
},
|
||||
// no-op
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Config {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
self.jsc.merge(&from.jsc);
|
||||
self.module.merge(&from.module);
|
||||
self.minify.merge(&from.minify)
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for JscConfig {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
self.syntax.merge(&from.syntax);
|
||||
self.transform.merge(&from.transform);
|
||||
self.target.merge(&from.target);
|
||||
self.external_helpers.merge(&from.external_helpers);
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for JscTarget {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
if *self < *from {
|
||||
*self = *from
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Option<ModuleConfig> {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
match *from {
|
||||
Some(ref c2) => *self = Some(c2.clone()),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for bool {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
*self |= *from
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Syntax {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
*self = *from;
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for TransformConfig {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
self.optimizer.merge(&from.optimizer);
|
||||
self.const_modules.merge(&from.const_modules);
|
||||
self.react.merge(&from.react);
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for OptimizerConfig {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
self.globals.merge(&from.globals)
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for GlobalPassOption {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
*self = from.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for react::Options {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
*self = from.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for ConstModulesConfig {
|
||||
fn merge(&mut self, from: &Self) {
|
||||
*self = from.clone()
|
||||
}
|
||||
}
|
9
src/config/object.json
Normal file
9
src/config/object.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": false,
|
||||
"decorators": false
|
||||
}
|
||||
}
|
||||
}
|
12
src/config/tests.rs
Normal file
12
src/config/tests.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use super::Rc;
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
fn object() {
|
||||
let _: Rc = serde_json::from_str(include_str!("object.json")).expect("failed to parse");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array() {
|
||||
let _: Rc = serde_json::from_str(include_str!("array.json")).expect("failed to parse");
|
||||
}
|
54
src/error.rs
Normal file
54
src/error.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use failure::Fail;
|
||||
use lazy_static::lazy_static;
|
||||
use regex;
|
||||
use serde_json;
|
||||
use sourcemap;
|
||||
use std::{io, string::FromUtf8Error};
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "failed to read config file: {}", err)]
|
||||
FailedToReadConfigFile { err: io::Error },
|
||||
|
||||
#[fail(display = "failed to parse config file: {}", err)]
|
||||
FailedToParseConfigFile { err: serde_json::error::Error },
|
||||
|
||||
#[fail(display = "failed to parse module")]
|
||||
FailedToParseModule {},
|
||||
|
||||
#[fail(display = "failed to read module: {}", err)]
|
||||
FailedToReadModule { err: io::Error },
|
||||
|
||||
#[fail(display = "failed to emit module: {}", err)]
|
||||
FailedToEmitModule { err: io::Error },
|
||||
|
||||
#[fail(display = "failed to write sourcemap: {}", err)]
|
||||
FailedToWriteSourceMap { err: sourcemap::Error },
|
||||
|
||||
#[fail(display = "sourcemap is not utf8: {}", err)]
|
||||
SourceMapNotUtf8 { err: FromUtf8Error },
|
||||
|
||||
#[fail(display = "invalid regexp: {}", err)]
|
||||
InvalidRegex { err: regex::Error },
|
||||
|
||||
/* #[fail(display = "generated code is not utf8: {}", err)]
|
||||
* GeneratedCodeNotUtf8 { err: FromUtf8Error }, */
|
||||
/// This means `test` field in .swcrc file did not matched the compiling
|
||||
/// file.
|
||||
#[fail(display = "unmatched")]
|
||||
Unmatched,
|
||||
}
|
||||
|
||||
/// Returns true if `SWC_DEBUG` environment is set to `1` or `true`.
|
||||
pub(crate) fn debug() -> bool {
|
||||
lazy_static! {
|
||||
static ref DEBUG: bool = {
|
||||
match ::std::env::var("SWC_DEBUG") {
|
||||
Ok(ref v) if v == "1" || v.eq_ignore_ascii_case("true") => true,
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
*DEBUG
|
||||
}
|
283
src/lib.rs
283
src/lib.rs
@ -1,4 +1,287 @@
|
||||
#![feature(box_syntax, box_patterns)]
|
||||
|
||||
pub extern crate sourcemap;
|
||||
pub extern crate swc_atoms as atoms;
|
||||
pub extern crate swc_common as common;
|
||||
pub extern crate swc_ecmascript as ecmascript;
|
||||
|
||||
mod builder;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
|
||||
pub use crate::builder::PassBuilder;
|
||||
use crate::{
|
||||
config::{BuiltConfig, ConfigFile, Merge, Options, Rc, RootMode},
|
||||
error::Error,
|
||||
};
|
||||
use common::{
|
||||
comments::Comments, errors::Handler, FileName, FoldWith, Globals, SourceFile, SourceMap,
|
||||
GLOBALS,
|
||||
};
|
||||
use ecmascript::{
|
||||
ast::Module,
|
||||
codegen::{self, Emitter},
|
||||
parser::{Parser, Session as ParseSess, Syntax},
|
||||
transforms::{
|
||||
helpers::{self, Helpers},
|
||||
util,
|
||||
},
|
||||
};
|
||||
pub use ecmascript::{
|
||||
parser::SourceFileInput,
|
||||
transforms::{chain_at, pass::Pass},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use sourcemap::SourceMapBuilder;
|
||||
use std::{fs::File, path::Path, sync::Arc};
|
||||
|
||||
pub struct Compiler {
|
||||
/// swc uses rustc's span interning.
|
||||
///
|
||||
/// The `Globals` struct contains span interner.
|
||||
globals: Globals,
|
||||
/// CodeMap
|
||||
pub cm: Arc<SourceMap>,
|
||||
handler: Handler,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TransformOutput {
|
||||
pub code: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub map: Option<String>,
|
||||
}
|
||||
|
||||
/// These are **low-level** apis.
|
||||
impl Compiler {
|
||||
/// Runs `op` in current compiler's context.
|
||||
///
|
||||
/// Note: Other methods of `Compiler` already uses this internally.
|
||||
pub fn run<R, F>(&self, op: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
GLOBALS.set(&self.globals, op)
|
||||
}
|
||||
|
||||
/// This method parses a javascript / typescript file
|
||||
pub fn parse_js(
|
||||
&self,
|
||||
fm: Arc<SourceFile>,
|
||||
syntax: Syntax,
|
||||
comments: Option<&Comments>,
|
||||
) -> Result<Module, Error> {
|
||||
self.run(|| {
|
||||
let session = ParseSess {
|
||||
handler: &self.handler,
|
||||
};
|
||||
let mut parser = Parser::new(session, syntax, SourceFileInput::from(&*fm), comments);
|
||||
let module = parser.parse_module().map_err(|mut e| {
|
||||
e.emit();
|
||||
Error::FailedToParseModule {}
|
||||
})?;
|
||||
|
||||
Ok(module)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn print(
|
||||
&self,
|
||||
module: &Module,
|
||||
fm: Arc<SourceFile>,
|
||||
comments: &Comments,
|
||||
source_map: bool,
|
||||
minify: bool,
|
||||
) -> Result<TransformOutput, Error> {
|
||||
self.run(|| {
|
||||
let mut src_map_builder = SourceMapBuilder::new(None);
|
||||
|
||||
match fm.name {
|
||||
FileName::Real(ref p) => {
|
||||
let id = src_map_builder.add_source(&p.display().to_string());
|
||||
src_map_builder.set_source_contents(id, Some(&fm.src));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let src = {
|
||||
let mut buf = vec![];
|
||||
{
|
||||
let handlers = box MyHandlers;
|
||||
let mut emitter = Emitter {
|
||||
cfg: codegen::Config { minify },
|
||||
comments: if minify { None } else { Some(&comments) },
|
||||
cm: self.cm.clone(),
|
||||
wr: box codegen::text_writer::JsWriter::new(
|
||||
self.cm.clone(),
|
||||
"\n",
|
||||
&mut buf,
|
||||
if source_map {
|
||||
Some(&mut src_map_builder)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
handlers,
|
||||
pos_of_leading_comments: Default::default(),
|
||||
};
|
||||
|
||||
emitter
|
||||
.emit_module(&module)
|
||||
.map_err(|err| Error::FailedToEmitModule { err })?;
|
||||
}
|
||||
// Invalid utf8 is valid in javascript world.
|
||||
unsafe { String::from_utf8_unchecked(buf) }
|
||||
};
|
||||
Ok(TransformOutput {
|
||||
code: src,
|
||||
map: if source_map {
|
||||
let mut buf = vec![];
|
||||
src_map_builder
|
||||
.into_sourcemap()
|
||||
.to_writer(&mut buf)
|
||||
.map_err(|err| Error::FailedToWriteSourceMap { err })?;
|
||||
let map =
|
||||
String::from_utf8(buf).map_err(|err| Error::SourceMapNotUtf8 { err })?;
|
||||
Some(map)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// High-level apis.
|
||||
impl Compiler {
|
||||
pub fn new(cm: Arc<SourceMap>, handler: Handler) -> Self {
|
||||
Compiler {
|
||||
cm,
|
||||
handler,
|
||||
globals: Globals::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// This method handles merging of config.
|
||||
pub fn config_for_file(
|
||||
&self,
|
||||
opts: &Options,
|
||||
fm: &SourceFile,
|
||||
) -> Result<BuiltConfig<impl Pass>, Error> {
|
||||
let Options {
|
||||
ref root,
|
||||
root_mode,
|
||||
swcrc,
|
||||
config_file,
|
||||
..
|
||||
} = opts;
|
||||
let root = root
|
||||
.clone()
|
||||
.unwrap_or_else(|| ::std::env::current_dir().unwrap());
|
||||
|
||||
let config_file = match config_file {
|
||||
Some(ConfigFile::Str(ref s)) => {
|
||||
let path = Path::new(s);
|
||||
let r = File::open(&path).map_err(|err| Error::FailedToReadConfigFile { err })?;
|
||||
let config: Rc = serde_json::from_reader(r)
|
||||
.map_err(|err| Error::FailedToParseConfigFile { err })?;
|
||||
Some(config)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match fm.name {
|
||||
FileName::Real(ref path) => {
|
||||
if *swcrc {
|
||||
let mut parent = path.parent();
|
||||
while let Some(dir) = parent {
|
||||
let swcrc = dir.join(".swcrc");
|
||||
|
||||
if swcrc.exists() {
|
||||
let r = File::open(&swcrc)
|
||||
.map_err(|err| Error::FailedToReadConfigFile { err })?;
|
||||
let mut config = serde_json::from_reader(r)
|
||||
.map_err(|err| Error::FailedToParseConfigFile { err })
|
||||
.and_then(|rc: Rc| rc.into_config(Some(path)))?;
|
||||
if let Some(config_file) = config_file {
|
||||
config.merge(&config_file.into_config(Some(path))?)
|
||||
}
|
||||
let built = opts.build(&self.cm, &self.handler, Some(config));
|
||||
return Ok(built);
|
||||
}
|
||||
|
||||
if dir == root && *root_mode == RootMode::Root {
|
||||
break;
|
||||
}
|
||||
parent = dir.parent();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(config_file) = config_file {
|
||||
let built = opts.build(
|
||||
&self.cm,
|
||||
&self.handler,
|
||||
Some(config_file.into_config(Some(path))?),
|
||||
);
|
||||
return Ok(built);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let built = opts.build(
|
||||
&self.cm,
|
||||
&self.handler,
|
||||
match config_file {
|
||||
Some(config_file) => Some(config_file.into_config(None)?),
|
||||
None => None,
|
||||
},
|
||||
);
|
||||
Ok(built)
|
||||
}
|
||||
|
||||
pub fn process_js_file(
|
||||
&self,
|
||||
fm: Arc<SourceFile>,
|
||||
opts: Options,
|
||||
) -> Result<TransformOutput, Error> {
|
||||
let config = self.run(|| self.config_for_file(&opts, &*fm))?;
|
||||
|
||||
self.process_js(fm, config)
|
||||
}
|
||||
|
||||
/// You can use custom pass with this method.
|
||||
///
|
||||
/// There exists a [PassBuilder] to help building custom passes.
|
||||
pub fn process_js(
|
||||
&self,
|
||||
fm: Arc<SourceFile>,
|
||||
config: BuiltConfig<impl Pass>,
|
||||
) -> Result<TransformOutput, Error> {
|
||||
self.run(|| {
|
||||
if error::debug() {
|
||||
eprintln!("processing js file: {:?}", fm)
|
||||
}
|
||||
|
||||
let comments = Default::default();
|
||||
let module = self.parse_js(
|
||||
fm.clone(),
|
||||
config.syntax,
|
||||
if config.minify { None } else { Some(&comments) },
|
||||
)?;
|
||||
let mut pass = config.pass;
|
||||
let module = helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
|
||||
util::HANDLER.set(&self.handler, || {
|
||||
// Fold module
|
||||
module.fold_with(&mut pass)
|
||||
})
|
||||
});
|
||||
|
||||
self.print(&module, fm, &comments, config.source_maps, config.minify)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MyHandlers;
|
||||
|
||||
impl ecmascript::codegen::Handlers for MyHandlers {}
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "testing"
|
||||
version = "0.3.1"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
documentation = "https://swc-project.github.io/rustdoc/testing/"
|
||||
|
Loading…
Reference in New Issue
Block a user