mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 05:01:42 +03:00
feat(wasm): Expose async facade interfaces (#5352)
This commit is contained in:
parent
f88a8c96ed
commit
281bdd9e97
@ -5,3 +5,4 @@ imports_granularity = "Crate"
|
||||
reorder_impl_items = true
|
||||
use_field_init_shorthand = true
|
||||
wrap_comments = true
|
||||
edition = "2018"
|
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -217,6 +217,7 @@ dependencies = [
|
||||
"swc_plugin_runner",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
]
|
||||
@ -4757,9 +4758,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.28"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
|
||||
checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
|
@ -25,13 +25,12 @@ plugin = [
|
||||
"wasmer-wasi",
|
||||
"wasmer/js-default",
|
||||
"wasmer-wasi/js-default",
|
||||
"js-sys",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.58"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
js-sys = { version = "0.3.59", optional = true }
|
||||
js-sys = { version = "0.3.59" }
|
||||
once_cell = "1.13.0"
|
||||
parking_lot_core = "0.9.3"
|
||||
path-clean = "0.1.0"
|
||||
@ -49,6 +48,7 @@ wasm-bindgen = { version = "0.2.82", features = [
|
||||
"serde-serialize",
|
||||
"enable-interning",
|
||||
] }
|
||||
wasm-bindgen-futures = "0.4.32"
|
||||
wasmer = { version = "2.3.0", optional = true, default-features = false }
|
||||
wasmer-wasi = { version = "2.3.0", optional = true, default-features = false }
|
||||
|
||||
|
@ -1,33 +1,248 @@
|
||||
const swc = require("../pkg");
|
||||
|
||||
it("should be loadable", function () {
|
||||
const output = swc.transformSync("class Foo {}", {});
|
||||
describe("transform", () => {
|
||||
it("should work", function () {
|
||||
const output = swc.transformSync("class Foo {}", {});
|
||||
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError(\\"Cannot call a class as a function\\");
|
||||
}
|
||||
}
|
||||
var Foo = function Foo() {
|
||||
\\"use strict\\";
|
||||
_classCallCheck(this, Foo);
|
||||
};
|
||||
",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should work with async facade", async () => {
|
||||
const output = await swc.transform("class Foo {}", {});
|
||||
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError(\\"Cannot call a class as a function\\");
|
||||
}
|
||||
}
|
||||
var Foo = function Foo() {
|
||||
\\"use strict\\";
|
||||
_classCallCheck(this, Foo);
|
||||
};
|
||||
",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should work with program object", async () => {
|
||||
const input = swc.parseSync("class Foo {}", {
|
||||
syntax: "typescript",
|
||||
target: "es2021",
|
||||
});
|
||||
|
||||
const output = await swc.transform(input, {});
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError(\\"Cannot call a class as a function\\");
|
||||
}
|
||||
}
|
||||
var Foo = function Foo() {
|
||||
\\"use strict\\";
|
||||
_classCallCheck(this, Foo);
|
||||
};
|
||||
",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should support 'paths' and 'baseUrl'", () => {
|
||||
const { code } = swc.transformSync(
|
||||
`
|
||||
import foo from '@src/app';
|
||||
console.log(foo)
|
||||
`,
|
||||
{
|
||||
filename: "main.js",
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: "typescript",
|
||||
},
|
||||
target: "es2021",
|
||||
transform: {},
|
||||
baseUrl: __dirname,
|
||||
paths: {
|
||||
"@src/*": ["bar/*"],
|
||||
},
|
||||
},
|
||||
module: {
|
||||
type: "commonjs",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(code).toContain(`bar/app`);
|
||||
});
|
||||
});
|
||||
|
||||
it("should support 'paths' and 'baseUrl'", async () => {
|
||||
const { code } = await swc.transformSync(
|
||||
`
|
||||
import foo from '@src/app';
|
||||
console.log(foo)
|
||||
`,
|
||||
{
|
||||
filename: "main.js",
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: "typescript",
|
||||
},
|
||||
target: "es2021",
|
||||
transform: {},
|
||||
baseUrl: __dirname,
|
||||
paths: {
|
||||
"@src/*": ["bar/*"],
|
||||
},
|
||||
},
|
||||
module: {
|
||||
type: "commonjs",
|
||||
},
|
||||
}
|
||||
);
|
||||
describe("parse", () => {
|
||||
it("should work", () => {
|
||||
const output = swc.parseSync("class Foo {}", {
|
||||
syntax: "typescript",
|
||||
target: "es2021",
|
||||
});
|
||||
|
||||
expect(code).toContain(`bar/app`);
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Array [
|
||||
Object {
|
||||
"body": Array [],
|
||||
"declare": false,
|
||||
"decorators": Array [],
|
||||
"identifier": Object {
|
||||
"optional": false,
|
||||
"span": Object {
|
||||
"ctxt": 0,
|
||||
"end": 394,
|
||||
"start": 391,
|
||||
},
|
||||
"type": "Identifier",
|
||||
"value": "Foo",
|
||||
},
|
||||
"implements": Array [],
|
||||
"isAbstract": false,
|
||||
"span": Object {
|
||||
"ctxt": 0,
|
||||
"end": 397,
|
||||
"start": 385,
|
||||
},
|
||||
"superClass": null,
|
||||
"superTypeParams": null,
|
||||
"type": "ClassDeclaration",
|
||||
"typeParams": null,
|
||||
},
|
||||
],
|
||||
"interpreter": null,
|
||||
"span": Object {
|
||||
"ctxt": 0,
|
||||
"end": 397,
|
||||
"start": 385,
|
||||
},
|
||||
"type": "Module",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should work with async facade", async () => {
|
||||
const output = await swc.parse("class Foo {}", {
|
||||
syntax: "typescript",
|
||||
target: "es2021",
|
||||
});
|
||||
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Array [
|
||||
Object {
|
||||
"body": Array [],
|
||||
"declare": false,
|
||||
"decorators": Array [],
|
||||
"identifier": Object {
|
||||
"optional": false,
|
||||
"span": Object {
|
||||
"ctxt": 0,
|
||||
"end": 407,
|
||||
"start": 404,
|
||||
},
|
||||
"type": "Identifier",
|
||||
"value": "Foo",
|
||||
},
|
||||
"implements": Array [],
|
||||
"isAbstract": false,
|
||||
"span": Object {
|
||||
"ctxt": 0,
|
||||
"end": 410,
|
||||
"start": 398,
|
||||
},
|
||||
"superClass": null,
|
||||
"superTypeParams": null,
|
||||
"type": "ClassDeclaration",
|
||||
"typeParams": null,
|
||||
},
|
||||
],
|
||||
"interpreter": null,
|
||||
"span": Object {
|
||||
"ctxt": 0,
|
||||
"end": 410,
|
||||
"start": 398,
|
||||
},
|
||||
"type": "Module",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("minify", () => {
|
||||
it("should work", () => {
|
||||
const output = swc.minifySync(
|
||||
"const somename = 1; console.log(somename);"
|
||||
);
|
||||
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "const a=1;console.log(1)",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should work with async facade", async () => {
|
||||
const output = await swc.minify(
|
||||
"const somename = 1; console.log(somename);"
|
||||
);
|
||||
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "const a=1;console.log(1)",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("print", () => {
|
||||
it("should work", () => {
|
||||
const input = swc.parseSync("class Foo {}", {
|
||||
syntax: "typescript",
|
||||
target: "es2021",
|
||||
});
|
||||
|
||||
const output = swc.printSync(input);
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "class Foo {
|
||||
}
|
||||
",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should work with async facade", async () => {
|
||||
const input = swc.parseSync("class Foo {}", {
|
||||
syntax: "typescript",
|
||||
target: "es2021",
|
||||
});
|
||||
|
||||
const output = await swc.print(input);
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"code": "class Foo {
|
||||
}
|
||||
",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
#![allow(unused)]
|
||||
#![deny(warnings)]
|
||||
#![allow(clippy::unused_unit)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use js_sys::{JsString, JSON};
|
||||
use once_cell::sync::Lazy;
|
||||
use swc::{
|
||||
config::{ErrorFormat, JsMinifyOptions, Options, ParseOptions, SourceMapsConfig},
|
||||
@ -11,7 +13,8 @@ use swc::{
|
||||
};
|
||||
use swc_common::{comments::Comments, FileName, FilePathMapping, SourceMap};
|
||||
use swc_ecmascript::ast::{EsVersion, Program};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::{prelude::*, JsCast};
|
||||
use wasm_bindgen_futures::{future_to_promise, spawn_local, JsFuture};
|
||||
|
||||
mod types;
|
||||
|
||||
@ -23,24 +26,39 @@ fn convert_err(err: Error, error_format: ErrorFormat) -> JsValue {
|
||||
/// 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 minify(src: string, opts?: JsMinifyOptions): Promise<Output>;
|
||||
export function minifySync(code: string, opts?: JsMinifyOptions): Output;
|
||||
export function parseSync(
|
||||
src: string,
|
||||
options: ParseOptions & { isModule: false }
|
||||
): Script;
|
||||
|
||||
export function parse(src: string, options: ParseOptions & {
|
||||
isModule: false;
|
||||
}): Promise<Script>;
|
||||
export function parse(src: string, options?: ParseOptions): Promise<Module>;
|
||||
export function parseSync(src: string, options: ParseOptions & {
|
||||
isModule: false;
|
||||
}): Script;
|
||||
export function parseSync(src: string, options?: ParseOptions): Module;
|
||||
export function parseSync(src: string, options?: ParseOptions): Program;
|
||||
|
||||
export function print(m: Program, options?: Options): Promise<Output>;
|
||||
export function printSync(m: Program, options?: Options): Output
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @param {Options} opts
|
||||
* @param {Record<string, ArrayBuffer>} experimental_plugin_bytes_resolver An object contains bytes array for the plugin
|
||||
* specified in config. Key of record represents the name of the plugin specified in config. Note this is an experimental
|
||||
* interface, likely will change.
|
||||
* @returns {Output}
|
||||
*/
|
||||
export function transformSync(code: string, opts: Options, experimental_plugin_bytes_resolver?: any): Output;
|
||||
* Note: this interface currently does not do _actual_ async work, only provides
|
||||
* a corresponding async interfaces to the `@swc/core`'s interface.
|
||||
*/
|
||||
export function transform(
|
||||
code: string | Program,
|
||||
options?: Options,
|
||||
experimental_plugin_bytes_resolver?: any
|
||||
): Promise<Output>;
|
||||
/**
|
||||
* @param {string} code
|
||||
* @param {Options} opts
|
||||
* @param {Record<string, ArrayBuffer>} experimental_plugin_bytes_resolver An object contains bytes array for the plugin
|
||||
* specified in config. Key of record represents the name of the plugin specified in config. Note this is an experimental
|
||||
* interface, likely will change.
|
||||
* @returns {Output}
|
||||
*/
|
||||
export function transformSync(code: string | Program, opts?: Options, experimental_plugin_bytes_resolver?: any): Output;
|
||||
"#;
|
||||
|
||||
#[wasm_bindgen(
|
||||
@ -48,7 +66,7 @@ export function transformSync(code: string, opts: Options, experimental_plugin_b
|
||||
typescript_type = "minifySync",
|
||||
skip_typescript
|
||||
)]
|
||||
pub fn minify_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
pub fn minify_sync(s: JsString, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let c = compiler();
|
||||
@ -60,7 +78,11 @@ pub fn minify_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
},
|
||||
|handler| {
|
||||
c.run(|| {
|
||||
let opts: JsMinifyOptions = opts.into_serde().context("failed to parse options")?;
|
||||
let opts = if opts.is_null() || opts.is_undefined() {
|
||||
Default::default()
|
||||
} else {
|
||||
opts.into_serde().context("failed to parse options")?
|
||||
};
|
||||
|
||||
let fm = c.cm.new_source_file(FileName::Anon, s.into());
|
||||
let program = c
|
||||
@ -74,8 +96,15 @@ pub fn minify_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
.map_err(|e| convert_err(e, ErrorFormat::Normal))
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "minify", typescript_type = "minify", skip_typescript)]
|
||||
pub fn minify(s: JsString, opts: JsValue) -> js_sys::Promise {
|
||||
// TODO: This'll be properly scheduled once wasm have standard backed thread
|
||||
// support.
|
||||
future_to_promise(async { minify_sync(s, opts) })
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "parseSync", typescript_type = "parseSync", skip_typescript)]
|
||||
pub fn parse_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
pub fn parse_sync(s: JsString, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let c = compiler();
|
||||
@ -87,7 +116,11 @@ pub fn parse_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
},
|
||||
|handler| {
|
||||
c.run(|| {
|
||||
let opts: ParseOptions = opts.into_serde().context("failed to parse options")?;
|
||||
let opts: ParseOptions = if opts.is_null() || opts.is_undefined() {
|
||||
Default::default()
|
||||
} else {
|
||||
opts.into_serde().context("failed to parse options")?
|
||||
};
|
||||
|
||||
let fm = c.cm.new_source_file(FileName::Anon, s.into());
|
||||
|
||||
@ -116,6 +149,13 @@ pub fn parse_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
.map_err(|e| convert_err(e, ErrorFormat::Normal))
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "parse", typescript_type = "parse", skip_typescript)]
|
||||
pub fn parse(s: JsString, opts: JsValue) -> js_sys::Promise {
|
||||
// TODO: This'll be properly scheduled once wasm have standard backed thread
|
||||
// support.
|
||||
future_to_promise(async { parse_sync(s, opts) })
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "printSync", typescript_type = "printSync", skip_typescript)]
|
||||
pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
@ -129,7 +169,11 @@ pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
},
|
||||
|_handler| {
|
||||
c.run(|| {
|
||||
let opts: Options = opts.into_serde().context("failed to parse options")?;
|
||||
let opts: Options = if opts.is_null() || opts.is_undefined() {
|
||||
Default::default()
|
||||
} else {
|
||||
opts.into_serde().context("failed to parse options")?
|
||||
};
|
||||
|
||||
let program: Program = s.into_serde().context("failed to deserialize program")?;
|
||||
|
||||
@ -159,6 +203,13 @@ pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
.map_err(|e| convert_err(e, ErrorFormat::Normal))
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "print", typescript_type = "print", skip_typescript)]
|
||||
pub fn print(s: JsValue, opts: JsValue) -> js_sys::Promise {
|
||||
// TODO: This'll be properly scheduled once wasm have standard backed thread
|
||||
// support.
|
||||
future_to_promise(async { print_sync(s, opts) })
|
||||
}
|
||||
|
||||
#[wasm_bindgen(
|
||||
js_name = "transformSync",
|
||||
typescript_type = "transformSync",
|
||||
@ -166,7 +217,7 @@ pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
|
||||
)]
|
||||
#[allow(unused_variables)]
|
||||
pub fn transform_sync(
|
||||
s: &str,
|
||||
s: JsValue,
|
||||
opts: JsValue,
|
||||
experimental_plugin_bytes_resolver: JsValue,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
@ -219,10 +270,13 @@ pub fn transform_sync(
|
||||
}
|
||||
}
|
||||
|
||||
let opts: Options = opts
|
||||
.into_serde()
|
||||
.context("failed to parse options")
|
||||
.map_err(|e| convert_err(e, ErrorFormat::Normal))?;
|
||||
let opts: Options = if opts.is_null() || opts.is_undefined() {
|
||||
Default::default()
|
||||
} else {
|
||||
opts.into_serde()
|
||||
.context("failed to parse options")
|
||||
.map_err(|e| convert_err(e, ErrorFormat::Normal))?
|
||||
};
|
||||
|
||||
let error_format = opts.experimental.error_format.unwrap_or_default();
|
||||
|
||||
@ -233,17 +287,22 @@ pub fn transform_sync(
|
||||
},
|
||||
|handler| {
|
||||
c.run(|| {
|
||||
let fm = c.cm.new_source_file(
|
||||
if opts.filename.is_empty() {
|
||||
FileName::Anon
|
||||
} else {
|
||||
FileName::Real(opts.filename.clone().into())
|
||||
},
|
||||
s.into(),
|
||||
);
|
||||
let out = c
|
||||
.process_js_file(fm, handler, &opts)
|
||||
.context("failed to process input file")?;
|
||||
let s = s.dyn_into::<js_sys::JsString>();
|
||||
let out = match s {
|
||||
Ok(s) => {
|
||||
let fm = c.cm.new_source_file(
|
||||
if opts.filename.is_empty() {
|
||||
FileName::Anon
|
||||
} else {
|
||||
FileName::Real(opts.filename.clone().into())
|
||||
},
|
||||
s.into(),
|
||||
);
|
||||
c.process_js_file(fm, handler, &opts)
|
||||
.context("failed to process input file")?
|
||||
}
|
||||
Err(v) => unsafe { c.process_js(handler, v.into_serde().expect(""), &opts)? },
|
||||
};
|
||||
|
||||
JsValue::from_serde(&out).context("failed to serialize json")
|
||||
})
|
||||
@ -252,6 +311,17 @@ pub fn transform_sync(
|
||||
.map_err(|e| convert_err(e, error_format))
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "transform", typescript_type = "transform", skip_typescript)]
|
||||
pub fn transform(
|
||||
s: JsValue,
|
||||
opts: JsValue,
|
||||
experimental_plugin_bytes_resolver: JsValue,
|
||||
) -> js_sys::Promise {
|
||||
// TODO: This'll be properly scheduled once wasm have standard backed thread
|
||||
// support.
|
||||
future_to_promise(async { transform_sync(s, opts, experimental_plugin_bytes_resolver) })
|
||||
}
|
||||
|
||||
/// Get global sourcemap
|
||||
fn compiler() -> Arc<Compiler> {
|
||||
static C: Lazy<Arc<Compiler>> = Lazy::new(|| {
|
||||
|
@ -848,7 +848,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
/// Second argument of `minify`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct JsMinifyOptions {
|
||||
#[serde(default)]
|
||||
|
Loading…
Reference in New Issue
Block a user