Skip to content

Commit 516bed2

Browse files
authored
Add visitor support to WASM build via napi-wasm (parcel-bundler#373)
1 parent f4bf515 commit 516bed2

File tree

12 files changed

+645
-174
lines changed

12 files changed

+645
-174
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ linker = "arm-linux-gnueabihf-gcc"
88
[target.aarch64-unknown-linux-musl]
99
linker = "aarch64-linux-musl-gcc"
1010
rustflags = ["-C", "target-feature=-crt-static"]
11+
12+
[target.wasm32-unknown-unknown]
13+
rustflags = ["-C", "link-arg=--export-table"]

.github/workflows/release.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,28 @@ jobs:
179179
uses: actions/setup-node@v2
180180
with:
181181
node-version: 14
182-
- name: Install wasm-pack
183-
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
182+
- name: Install Rust
183+
uses: actions-rs/toolchain@v1
184+
with:
185+
toolchain: stable
186+
profile: minimal
187+
override: true
188+
target: wasm32-unknown-unknown
189+
- name: Install wasm-opt
190+
run: |
191+
curl -L -O https://github.com/WebAssembly/binaryen/releases/download/version_111/binaryen-version_111-x86_64-linux.tar.gz
192+
tar -xf binaryen-version_111-x86_64-linux.tar.gz
184193
- name: Build wasm
185-
run: yarn wasm-browser:build-release
194+
run: yarn wasm:build-release
195+
- name: Optimize wasm
196+
run: |
197+
./binaryen-version_111/bin/wasm-opt wasm/lightningcss_node.wasm -Oz -o wasm/lightningcss_node.opt.wasm
198+
mv wasm/lightningcss_node.opt.wasm wasm/lightningcss_node.wasm
186199
- name: Upload artifacts
187200
uses: actions/upload-artifact@v2
188201
with:
189202
name: wasm
190-
path: node/pkg
203+
path: wasm/lightningcss_node.wasm
191204

192205
release:
193206
runs-on: ubuntu-latest
@@ -207,6 +220,7 @@ jobs:
207220
- name: Build npm packages
208221
run: |
209222
node scripts/build-npm.js
223+
cp artifacts/wasm/* wasm/.
210224
node scripts/build-wasm.js
211225
- run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc
212226
env:
@@ -219,6 +233,10 @@ jobs:
219233
npm publish;
220234
cd ../..;
221235
done
236+
cd wasm
237+
echo "Publishing lightningcss-wasm...";
238+
npm publish
239+
cd ..
222240
cd cli
223241
echo "Publishing lightningcss-cli...";
224242
npm publish

Cargo.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/Cargo.toml

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,13 @@ lightningcss = { path = "../", features = ["nodejs", "serde", "visitor"] }
1616
parcel_sourcemap = { version = "2.1.1", features = ["json"] }
1717
serde-detach = "*"
1818
smallvec = { version = "1.7.0", features = ["union"] }
19-
20-
[target.'cfg(target_os = "macos")'.dependencies]
21-
jemallocator = { version = "0.3.2", features = ["disable_initial_exec_tls"] }
22-
23-
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
2419
napi = {version = "2.10.0", default-features = false, features = ["napi4", "napi5", "compat-mode", "serde-json"]}
2520
napi-derive = "2"
2621
crossbeam-channel = "0.5.6"
2722
rayon = "1.5.1"
2823

29-
[target.'cfg(target_arch = "wasm32")'.dependencies]
30-
js-sys = "0.3"
31-
serde-wasm-bindgen = "0.3.0"
32-
wasm-bindgen = "0.2"
24+
[target.'cfg(target_os = "macos")'.dependencies]
25+
jemallocator = { version = "0.3.2", features = ["disable_initial_exec_tls"] }
3326

3427
[target.'cfg(not(target_arch = "wasm32"))'.build-dependencies]
3528
napi-build = "1"

node/src/lib.rs

Lines changed: 40 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -18,54 +18,13 @@ use std::path::{Path, PathBuf};
1818
use std::str::FromStr;
1919
use std::sync::{Arc, Mutex, RwLock};
2020

21-
#[cfg(not(target_arch = "wasm32"))]
2221
use transformer::JsVisitor;
2322

2423
#[cfg(not(target_arch = "wasm32"))]
2524
mod threadsafe_function;
26-
#[cfg(not(target_arch = "wasm32"))]
2725
mod transformer;
2826

29-
// ---------------------------------------------
30-
31-
#[cfg(target_arch = "wasm32")]
32-
use serde_wasm_bindgen::{from_value, Serializer};
33-
#[cfg(target_arch = "wasm32")]
34-
use wasm_bindgen::prelude::*;
35-
36-
#[cfg(target_arch = "wasm32")]
37-
struct JsVisitor;
38-
39-
#[cfg(target_arch = "wasm32")]
40-
impl<'i> lightningcss::visitor::Visitor<'i> for JsVisitor {
41-
const TYPES: lightningcss::visitor::VisitTypes = lightningcss::visitor::VisitTypes::empty();
42-
}
43-
44-
#[cfg(target_arch = "wasm32")]
45-
#[wasm_bindgen]
46-
pub fn transform(config_val: JsValue) -> Result<JsValue, JsValue> {
47-
let config: Config = from_value(config_val).map_err(JsValue::from)?;
48-
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
49-
let res = compile(code, &config, &mut None)?;
50-
let serializer = Serializer::new().serialize_maps_as_objects(true);
51-
res.serialize(&serializer).map_err(JsValue::from)
52-
}
53-
54-
#[cfg(target_arch = "wasm32")]
55-
#[wasm_bindgen(js_name = "transformStyleAttribute")]
56-
pub fn transform_style_attribute(config_val: JsValue) -> Result<JsValue, JsValue> {
57-
let config: AttrConfig = from_value(config_val).map_err(JsValue::from)?;
58-
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
59-
let res = compile_attr(code, &config, &mut None)?;
60-
let serializer = Serializer::new().serialize_maps_as_objects(true);
61-
res.serialize(&serializer).map_err(JsValue::from)
62-
}
63-
64-
// ---------------------------------------------
65-
66-
#[cfg(not(target_arch = "wasm32"))]
6727
use napi::{CallContext, Env, JsObject, JsUnknown};
68-
#[cfg(not(target_arch = "wasm32"))]
6928
use napi_derive::{js_function, module_exports};
7029

7130
#[derive(Serialize)]
@@ -81,7 +40,6 @@ struct TransformResult<'i> {
8140
warnings: Vec<Warning<'i>>,
8241
}
8342

84-
#[cfg(not(target_arch = "wasm32"))]
8543
impl<'i> TransformResult<'i> {
8644
fn into_js(self, env: Env) -> napi::Result<JsUnknown> {
8745
// Manually construct buffers so we avoid a copy and work around
@@ -106,7 +64,6 @@ impl<'i> TransformResult<'i> {
10664
}
10765
}
10866

109-
#[cfg(not(target_arch = "wasm32"))]
11067
#[js_function(1)]
11168
fn transform(ctx: CallContext) -> napi::Result<JsUnknown> {
11269
use transformer::JsVisitor;
@@ -134,7 +91,6 @@ fn transform(ctx: CallContext) -> napi::Result<JsUnknown> {
13491
}
13592
}
13693

137-
#[cfg(not(target_arch = "wasm32"))]
13894
#[js_function(1)]
13995
fn transform_style_attribute(ctx: CallContext) -> napi::Result<JsUnknown> {
14096
use transformer::JsVisitor;
@@ -370,7 +326,6 @@ mod bundle {
370326
handle_error(tx, read_on_js_thread(ctx))
371327
}
372328

373-
#[cfg(not(target_arch = "wasm32"))]
374329
#[js_function(1)]
375330
pub fn bundle_async(ctx: CallContext) -> napi::Result<JsObject> {
376331
use transformer::JsVisitor;
@@ -488,17 +443,53 @@ mod bundle {
488443
}
489444
}
490445

491-
#[cfg(not(target_arch = "wasm32"))]
492-
#[module_exports]
446+
#[cfg_attr(not(target_arch = "wasm32"), module_exports)]
493447
fn init(mut exports: JsObject) -> napi::Result<()> {
494448
exports.create_named_method("transform", transform)?;
495449
exports.create_named_method("transformStyleAttribute", transform_style_attribute)?;
496-
exports.create_named_method("bundle", bundle::bundle)?;
497-
exports.create_named_method("bundleAsync", bundle::bundle_async)?;
450+
451+
#[cfg(not(target_arch = "wasm32"))]
452+
{
453+
exports.create_named_method("bundle", bundle::bundle)?;
454+
exports.create_named_method("bundleAsync", bundle::bundle_async)?;
455+
}
498456

499457
Ok(())
500458
}
501459

460+
#[cfg(target_arch = "wasm32")]
461+
#[no_mangle]
462+
pub unsafe fn napi_register_wasm_v1(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) {
463+
use napi::{Env, JsObject, NapiValue};
464+
465+
let env = Env::from_raw(raw_env);
466+
let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
467+
init(exports);
468+
}
469+
470+
#[cfg(target_arch = "wasm32")]
471+
#[no_mangle]
472+
pub extern "C" fn napi_wasm_malloc(size: usize) -> *mut u8 {
473+
use std::alloc::{alloc, Layout};
474+
use std::mem;
475+
476+
let align = mem::align_of::<usize>();
477+
if let Ok(layout) = Layout::from_size_align(size, align) {
478+
unsafe {
479+
if layout.size() > 0 {
480+
let ptr = alloc(layout);
481+
if !ptr.is_null() {
482+
return ptr;
483+
}
484+
} else {
485+
return align as *mut u8;
486+
}
487+
}
488+
}
489+
490+
std::process::abort();
491+
}
492+
502493
// ---------------------------------------------
503494

504495
#[derive(Debug, Deserialize)]
@@ -705,7 +696,6 @@ fn compile<'i>(
705696
})
706697
}
707698

708-
#[cfg(not(target_arch = "wasm32"))]
709699
fn compile_bundle<'i, 'o, P: SourceProvider, F: FnOnce(&mut StyleSheet<'i, 'o>) -> napi::Result<()>>(
710700
fs: &'i P,
711701
config: &'o BundleConfig,
@@ -827,7 +817,6 @@ struct AttrResult<'i> {
827817
warnings: Vec<Warning<'i>>,
828818
}
829819

830-
#[cfg(not(target_arch = "wasm32"))]
831820
impl<'i> AttrResult<'i> {
832821
fn into_js(self, ctx: CallContext) -> napi::Result<JsUnknown> {
833822
// Manually construct buffers so we avoid a copy and work around
@@ -906,7 +895,6 @@ enum CompileError<'i, E: std::error::Error> {
906895
SourceMapError(parcel_sourcemap::SourceMapError),
907896
BundleError(Error<BundleErrorKind<'i, E>>),
908897
PatternError(PatternParseError),
909-
#[cfg(not(target_arch = "wasm32"))]
910898
JsError(napi::Error),
911899
}
912900

@@ -919,14 +907,12 @@ impl<'i, E: std::error::Error> std::fmt::Display for CompileError<'i, E> {
919907
CompileError::BundleError(err) => err.kind.fmt(f),
920908
CompileError::PatternError(err) => err.fmt(f),
921909
CompileError::SourceMapError(err) => write!(f, "{}", err.to_string()), // TODO: switch to `fmt::Display` once parcel_sourcemap supports this
922-
#[cfg(not(target_arch = "wasm32"))]
923910
CompileError::JsError(err) => std::fmt::Debug::fmt(&err, f),
924911
}
925912
}
926913
}
927914

928915
impl<'i, E: std::error::Error> CompileError<'i, E> {
929-
#[cfg(not(target_arch = "wasm32"))]
930916
fn throw(self, env: Env, code: Option<&str>) -> napi::Result<JsUnknown> {
931917
let reason = self.to_string();
932918
let data = match &self {
@@ -999,7 +985,6 @@ impl<'i, E: std::error::Error> From<Error<BundleErrorKind<'i, E>>> for CompileEr
999985
}
1000986
}
1001987

1002-
#[cfg(not(target_arch = "wasm32"))]
1003988
impl<'i, E: std::error::Error> From<CompileError<'i, E>> for napi::Error {
1004989
fn from(e: CompileError<'i, E>) -> napi::Error {
1005990
match e {
@@ -1011,17 +996,6 @@ impl<'i, E: std::error::Error> From<CompileError<'i, E>> for napi::Error {
1011996
}
1012997
}
1013998

1014-
#[cfg(target_arch = "wasm32")]
1015-
impl<'i, E: std::error::Error> From<CompileError<'i, E>> for wasm_bindgen::JsValue {
1016-
fn from(e: CompileError<'i, E>) -> wasm_bindgen::JsValue {
1017-
match e {
1018-
CompileError::SourceMapError(e) => js_sys::Error::new(&e.to_string()).into(),
1019-
CompileError::PatternError(e) => js_sys::Error::new(&e.to_string()).into(),
1020-
_ => js_sys::Error::new(&e.to_string()).into(),
1021-
}
1022-
}
1023-
}
1024-
1025999
#[derive(Serialize)]
10261000
struct Warning<'i> {
10271001
message: String,

package.json

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,21 @@
4141
"detect-libc": "^1.0.3"
4242
},
4343
"devDependencies": {
44+
"@codemirror/lang-css": "^6.0.1",
45+
"@codemirror/lang-javascript": "^6.1.2",
46+
"@codemirror/lint": "^6.1.0",
47+
"@codemirror/theme-one-dark": "^6.1.0",
4448
"@mdn/browser-compat-data": "^5.1.6",
4549
"@napi-rs/cli": "^2.6.2",
4650
"autoprefixer": "^10.4.8",
4751
"caniuse-lite": "^1.0.30001373",
52+
"codemirror": "^6.0.1",
4853
"cssnano": "^5.0.8",
4954
"esbuild": "^0.13.10",
5055
"flowgen": "^1.21.0",
5156
"jest-diff": "^27.4.2",
5257
"json-schema-to-typescript": "^11.0.2",
58+
"napi-wasm": "^1.0.1",
5359
"node-fetch": "^3.1.0",
5460
"parcel": "^2.7.0",
5561
"patch-package": "^6.5.0",
@@ -66,13 +72,11 @@
6672
"build": "node scripts/build.js && node scripts/build-flow.js",
6773
"build-release": "node scripts/build.js --release && node scripts/build-flow.js",
6874
"prepublishOnly": "node scripts/build-flow.js",
69-
"wasm:build": "wasm-pack build node --target nodejs",
70-
"wasm:build-release": "wasm-pack build node --target nodejs --release",
71-
"wasm-browser:build": "wasm-pack build node --target web",
72-
"wasm-browser:build-release": "wasm-pack build node --target web --release",
75+
"wasm:build": "cargo build --target wasm32-unknown-unknown -p lightningcss_node && cp target/wasm32-unknown-unknown/debug/lightningcss_node.wasm wasm/. && node scripts/build-wasm.js",
76+
"wasm:build-release": "cargo build --target wasm32-unknown-unknown -p lightningcss_node --release && cp target/wasm32-unknown-unknown/release/lightningcss_node.wasm wasm/. && node scripts/build-wasm.js",
7377
"website:start": "parcel website/index.html website/playground/index.html",
74-
"website:build": "yarn wasm-browser:build-release && parcel build website/index.html website/playground/index.html",
78+
"website:build": "yarn wasm:build-release && parcel build website/index.html website/playground/index.html",
7579
"build-ast": "cargo run --example schema --features jsonschema && node scripts/build-ast.js",
7680
"test": "uvu node/test"
7781
}
78-
}
82+
}

0 commit comments

Comments
 (0)