mirror of
https://github.com/swc-project/swc.git
synced 2024-10-03 19:57:22 +03:00
feat(binding): Create Wasm package for stripping only TypeScript (#9124)
**Description:** This PR adds a Wasm binding which is only capable of stripping TypeScript types. **Related issue:** - https://github.com/marco-ippolito/node/pull/2
This commit is contained in:
parent
1597c5dee5
commit
6b3c0da755
7
.github/workflows/publish-core.yml
vendored
7
.github/workflows/publish-core.yml
vendored
@ -508,11 +508,14 @@ jobs:
|
||||
matrix:
|
||||
settings:
|
||||
- crate: "binding_core_wasm"
|
||||
npm: "@swc/wasm"
|
||||
npm: "@swc\\/wasm"
|
||||
target: nodejs
|
||||
- crate: "binding_core_wasm"
|
||||
npm: "@swc/wasm-web"
|
||||
npm: "@swc\\/wasm-web"
|
||||
target: web
|
||||
- crate: "binding_typescript_wasm"
|
||||
npm: "@swc\\/wasm-typescript"
|
||||
target: no-modules
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
22
bindings/Cargo.lock
generated
22
bindings/Cargo.lock
generated
@ -302,6 +302,23 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binding_typescript_wasm"
|
||||
version = "1.6.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"swc_core",
|
||||
"swc_ecma_codegen",
|
||||
"swc_error_reporters",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -2653,9 +2670,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.115"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@ -3208,6 +3225,7 @@ dependencies = [
|
||||
"swc_ecma_minifier",
|
||||
"swc_ecma_parser",
|
||||
"swc_ecma_transforms_base",
|
||||
"swc_ecma_transforms_typescript",
|
||||
"swc_ecma_visit",
|
||||
"swc_malloc",
|
||||
"swc_node_bundler",
|
||||
|
@ -4,6 +4,7 @@ members = [
|
||||
"binding_core_wasm",
|
||||
"binding_minifier_node",
|
||||
"binding_minifier_wasm",
|
||||
"binding_typescript_wasm",
|
||||
"swc_cli",
|
||||
]
|
||||
resolver = "2"
|
||||
|
@ -17,7 +17,7 @@ fn main() {
|
||||
let out_dir = env::var("OUT_DIR").expect("Outdir should exist");
|
||||
let dest_path = Path::new(&out_dir).join("triple.txt");
|
||||
let mut f =
|
||||
BufWriter::new(File::create(&dest_path).expect("Failed to create target triple text"));
|
||||
BufWriter::new(File::create(dest_path).expect("Failed to create target triple text"));
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
|
@ -7,8 +7,7 @@ use napi::{
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use swc_compiler_base::{
|
||||
minify_file_comments, parse_js, IdentCollector, IsModule, PrintArgs, SourceMapsConfig,
|
||||
TransformOutput,
|
||||
minify_file_comments, parse_js, IdentCollector, PrintArgs, SourceMapsConfig, TransformOutput,
|
||||
};
|
||||
use swc_config::config_types::BoolOr;
|
||||
use swc_core::{
|
||||
@ -23,7 +22,7 @@ use swc_core::{
|
||||
js::{JsMinifyCommentOption, JsMinifyOptions},
|
||||
option::{MinifyOptions, TopLevelOptions},
|
||||
},
|
||||
parser::{EsConfig, Syntax},
|
||||
parser::{EsSyntax, Syntax},
|
||||
transforms::base::{fixer::fixer, hygiene::hygiene, resolver},
|
||||
visit::{FoldWith, VisitMutWith, VisitWith},
|
||||
},
|
||||
@ -109,11 +108,30 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<Transf
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let comments = SingleThreadedComments::default();
|
||||
|
||||
let module = parse_js(
|
||||
cm.clone(),
|
||||
fm.clone(),
|
||||
handler,
|
||||
target,
|
||||
Syntax::Es(EsSyntax {
|
||||
jsx: true,
|
||||
decorators: true,
|
||||
decorators_before_export: true,
|
||||
import_attributes: true,
|
||||
..Default::default()
|
||||
}),
|
||||
options.module,
|
||||
Some(&comments),
|
||||
)
|
||||
.context("failed to parse input file")?;
|
||||
|
||||
// top_level defaults to true if module is true
|
||||
|
||||
// https://github.com/swc-project/swc/issues/2254
|
||||
|
||||
if options.module {
|
||||
if module.is_module() {
|
||||
if let Some(opts) = &mut min_opts.compress {
|
||||
if opts.top_level.is_none() {
|
||||
opts.top_level = Some(TopLevelOptions { functions: true });
|
||||
@ -136,25 +154,6 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<Transf
|
||||
}
|
||||
}
|
||||
|
||||
let comments = SingleThreadedComments::default();
|
||||
|
||||
let module = parse_js(
|
||||
cm.clone(),
|
||||
fm.clone(),
|
||||
handler,
|
||||
target,
|
||||
Syntax::Es(EsConfig {
|
||||
jsx: true,
|
||||
decorators: true,
|
||||
decorators_before_export: true,
|
||||
import_attributes: true,
|
||||
..Default::default()
|
||||
}),
|
||||
IsModule::Bool(options.module),
|
||||
Some(&comments),
|
||||
)
|
||||
.context("failed to parse input file")?;
|
||||
|
||||
let source_map_names = if source_map.enabled() {
|
||||
let mut v = IdentCollector {
|
||||
names: Default::default(),
|
||||
@ -172,7 +171,7 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<Transf
|
||||
|
||||
let is_mangler_enabled = min_opts.mangle.is_some();
|
||||
|
||||
let module = (|| {
|
||||
let module = {
|
||||
let module = module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false));
|
||||
|
||||
let mut module = swc_core::ecma::minifier::optimize(
|
||||
@ -192,7 +191,7 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<Transf
|
||||
}
|
||||
module.visit_mut_with(&mut fixer(Some(&comments as &dyn Comments)));
|
||||
module
|
||||
})();
|
||||
};
|
||||
|
||||
let preserve_comments = options
|
||||
.format
|
||||
@ -211,7 +210,7 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<Transf
|
||||
inline_sources_content: options.inline_sources_content,
|
||||
source_map,
|
||||
source_map_names: &source_map_names,
|
||||
orig: orig.as_ref(),
|
||||
orig,
|
||||
comments: Some(&comments),
|
||||
emit_source_map_columns: options.emit_source_map_columns,
|
||||
preamble: &options.format.preamble,
|
||||
|
@ -1 +0,0 @@
|
||||
wasm-pack build --debug --scope swc -t nodejs --features plugin --features getrandom/js $@
|
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# run this script from the wasm folder ./scripts/build_nodejs_release.sh
|
||||
npx wasm-pack build --scope swc -t nodejs --features plugin
|
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# run this script from the wasm folder ./scripts/build_web_release.sh
|
||||
npx wasm-pack build --scope swc --features plugin
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
./scripts/build.sh
|
||||
npx jest $@
|
40
bindings/binding_typescript_wasm/Cargo.toml
Normal file
40
bindings/binding_typescript_wasm/Cargo.toml
Normal file
@ -0,0 +1,40 @@
|
||||
[package]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
description = "wasm module for swc"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
name = "binding_typescript_wasm"
|
||||
publish = false
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "1.6.6"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["swc_v1"]
|
||||
swc_v1 = []
|
||||
swc_v2 = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.66"
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.4.5"
|
||||
serde_json = "1.0.120"
|
||||
swc_core = { version = "0.95.10", features = [
|
||||
"ecma_ast_serde",
|
||||
"ecma_codegen",
|
||||
"ecma_transforms",
|
||||
"ecma_transforms_typescript",
|
||||
"ecma_visit",
|
||||
] }
|
||||
swc_ecma_codegen = { version = "0.151.1", features = ["serde-impl"] }
|
||||
swc_error_reporters = "0.18.0"
|
||||
tracing = { version = "0.1.37", features = ["max_level_off"] }
|
||||
wasm-bindgen = { version = "0.2.82", features = ["enable-interning"] }
|
||||
wasm-bindgen-futures = { version = "0.4.41" }
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
5
bindings/binding_typescript_wasm/package.json
Normal file
5
bindings/binding_typescript_wasm/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"jest": "^25.1.0"
|
||||
}
|
||||
}
|
227
bindings/binding_typescript_wasm/src/lib.rs
Normal file
227
bindings/binding_typescript_wasm/src/lib.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use anyhow::{Context, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swc_core::{
|
||||
base::{config::ErrorFormat, HandlerOpts},
|
||||
common::{
|
||||
comments::SingleThreadedComments, errors::ColorConfig, source_map::SourceMapGenConfig,
|
||||
sync::Lrc, FileName, Mark, SourceMap, GLOBALS,
|
||||
},
|
||||
ecma::{
|
||||
ast::{EsVersion, Program},
|
||||
codegen::text_writer::JsWriter,
|
||||
parser::{
|
||||
parse_file_as_module, parse_file_as_program, parse_file_as_script, Syntax, TsSyntax,
|
||||
},
|
||||
transforms::base::{
|
||||
fixer::fixer,
|
||||
helpers::{inject_helpers, Helpers, HELPERS},
|
||||
hygiene::hygiene,
|
||||
resolver,
|
||||
},
|
||||
visit::VisitMutWith,
|
||||
},
|
||||
};
|
||||
use swc_error_reporters::handler::try_with_handler;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::{
|
||||
future_to_promise,
|
||||
js_sys::{JsString, Promise},
|
||||
};
|
||||
|
||||
/// Custom interface definitions for the @swc/wasm's public interface instead of
|
||||
/// auto generated one, which is not reflecting most of types in detail.
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const INTERFACE_DEFINITIONS: &'static str = r#"
|
||||
export function transform(src: string, opts?: Options): Promise<TransformOutput>;
|
||||
export function transformSync(src: string, opts?: Options): TransformOutput;
|
||||
"#;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Options {
|
||||
#[serde(default)]
|
||||
pub module: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub filename: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub parser: TsSyntax,
|
||||
|
||||
#[serde(default)]
|
||||
pub external_helpers: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub source_maps: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub transform: swc_core::ecma::transforms::typescript::Config,
|
||||
|
||||
#[serde(default)]
|
||||
pub codegen: swc_core::ecma::codegen::Config,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TransformOutput {
|
||||
code: String,
|
||||
map: Option<String>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn transform(input: JsString, options: JsValue) -> Promise {
|
||||
future_to_promise(async move { transform_sync(input, options) })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn transform_sync(input: JsString, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
let options: Options = serde_wasm_bindgen::from_value(options)?;
|
||||
|
||||
let input = input.as_string().unwrap();
|
||||
|
||||
let result = GLOBALS
|
||||
.set(&Default::default(), || operate(input, options))
|
||||
.map_err(|err| convert_err(err, None))?;
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&result)?)
|
||||
}
|
||||
|
||||
fn operate(input: String, options: Options) -> Result<TransformOutput, Error> {
|
||||
let cm = Lrc::new(SourceMap::default());
|
||||
|
||||
try_with_handler(
|
||||
cm.clone(),
|
||||
HandlerOpts {
|
||||
color: ColorConfig::Never,
|
||||
skip_filename: true,
|
||||
},
|
||||
|handler| {
|
||||
let filename = options
|
||||
.filename
|
||||
.map_or(FileName::Anon, |f| FileName::Real(f.into()));
|
||||
|
||||
let fm = cm.new_source_file(filename, input);
|
||||
|
||||
let syntax = Syntax::Typescript(options.parser);
|
||||
let target = EsVersion::latest();
|
||||
|
||||
let comments = SingleThreadedComments::default();
|
||||
let mut errors = vec![];
|
||||
|
||||
let program = match options.module {
|
||||
Some(true) => {
|
||||
parse_file_as_module(&fm, syntax, target, Some(&comments), &mut errors)
|
||||
.map(Program::Module)
|
||||
}
|
||||
Some(false) => {
|
||||
parse_file_as_script(&fm, syntax, target, Some(&comments), &mut errors)
|
||||
.map(Program::Script)
|
||||
}
|
||||
None => parse_file_as_program(&fm, syntax, target, Some(&comments), &mut errors),
|
||||
};
|
||||
|
||||
let mut program = match program {
|
||||
Ok(program) => program,
|
||||
Err(err) => {
|
||||
err.into_diagnostic(handler).emit();
|
||||
|
||||
for e in errors {
|
||||
e.into_diagnostic(handler).emit();
|
||||
}
|
||||
|
||||
return Err(anyhow::anyhow!("failed to parse"));
|
||||
}
|
||||
};
|
||||
|
||||
if !errors.is_empty() {
|
||||
for e in errors {
|
||||
e.into_diagnostic(handler).emit();
|
||||
}
|
||||
|
||||
return Err(anyhow::anyhow!("failed to parse"));
|
||||
}
|
||||
|
||||
let unresolved_mark = Mark::new();
|
||||
let top_level_mark = Mark::new();
|
||||
|
||||
HELPERS.set(&Helpers::new(options.external_helpers), || {
|
||||
// Apply resolver
|
||||
|
||||
program.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, true));
|
||||
|
||||
// Strip typescript types
|
||||
|
||||
program.visit_mut_with(&mut swc_core::ecma::transforms::typescript::typescript(
|
||||
options.transform,
|
||||
top_level_mark,
|
||||
));
|
||||
|
||||
// Apply external helpers
|
||||
|
||||
program.visit_mut_with(&mut inject_helpers(unresolved_mark));
|
||||
|
||||
// Apply hygiene
|
||||
|
||||
program.visit_mut_with(&mut hygiene());
|
||||
|
||||
// Apply fixer
|
||||
|
||||
program.visit_mut_with(&mut fixer(Some(&comments)));
|
||||
});
|
||||
|
||||
// Generate code
|
||||
|
||||
let mut buf = vec![];
|
||||
let mut src_map_buf = if options.source_maps {
|
||||
Some(vec![])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
{
|
||||
let wr = JsWriter::new(cm.clone(), "\n", &mut buf, src_map_buf.as_mut());
|
||||
let mut emitter = swc_core::ecma::codegen::Emitter {
|
||||
cfg: options.codegen,
|
||||
cm: cm.clone(),
|
||||
comments: Some(&comments),
|
||||
wr,
|
||||
};
|
||||
|
||||
emitter.emit_program(&program).unwrap();
|
||||
}
|
||||
|
||||
let code = String::from_utf8(buf).context("generated code is not utf-8")?;
|
||||
|
||||
let map = if let Some(src_map_buf) = src_map_buf {
|
||||
let mut wr = vec![];
|
||||
let map = cm.build_source_map_with_config(&src_map_buf, None, TsSourceMapGenConfig);
|
||||
|
||||
map.to_writer(&mut wr)
|
||||
.context("failed to write source map")?;
|
||||
|
||||
let map = String::from_utf8(wr).context("source map is not utf-8")?;
|
||||
Some(map)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(TransformOutput { code, map })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn convert_err(
|
||||
err: Error,
|
||||
error_format: Option<ErrorFormat>,
|
||||
) -> wasm_bindgen::prelude::JsValue {
|
||||
error_format
|
||||
.unwrap_or(ErrorFormat::Normal)
|
||||
.format(&err)
|
||||
.into()
|
||||
}
|
||||
|
||||
struct TsSourceMapGenConfig;
|
||||
|
||||
impl SourceMapGenConfig for TsSourceMapGenConfig {
|
||||
fn file_name_to_source(&self, f: &FileName) -> String {
|
||||
f.to_string()
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ echo "Publishing $version with swc_core $swc_core_version"
|
||||
# Update version
|
||||
(cd ./packages/core && npm version "$version" --no-git-tag-version --allow-same-version || true)
|
||||
(cd ./packages/minifier && npm version "$version" --no-git-tag-version --allow-same-version || true)
|
||||
(cd ./bindings && cargo set-version $version -p binding_core_wasm -p binding_minifier_wasm)
|
||||
(cd ./bindings && cargo set-version $version -p binding_core_wasm -p binding_minifier_wasm -p binding_typescript_wasm)
|
||||
(cd ./bindings && cargo set-version --bump patch -p swc_cli)
|
||||
|
||||
|
||||
|
@ -6323,6 +6323,14 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"binding_typescript_wasm-2142ce@workspace:bindings/binding_typescript_wasm":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "binding_typescript_wasm-2142ce@workspace:bindings/binding_typescript_wasm"
|
||||
dependencies:
|
||||
jest: "npm:^25.1.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"bindings@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "bindings@npm:1.5.0"
|
||||
|
Loading…
Reference in New Issue
Block a user