Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Implement CSS OM API #10

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 67 additions & 0 deletions node/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
pub enum CompileError<'i> {
ParseError(cssparser::ParseError<'i, ()>),
PrinterError,
SourceMapError(parcel_sourcemap::SourceMapError)
}

impl<'i> CompileError<'i> {
pub fn reason(&self) -> String {
match self {
CompileError::ParseError(e) => {
match &e.kind {
cssparser::ParseErrorKind::Basic(b) => {
use cssparser::BasicParseErrorKind::*;
match b {
AtRuleBodyInvalid => "Invalid at rule body".into(),
EndOfInput => "Unexpected end of input".into(),
AtRuleInvalid(name) => format!("Unknown at rule: @{}", name),
QualifiedRuleInvalid => "Invalid qualified rule".into(),
UnexpectedToken(token) => format!("Unexpected token {:?}", token)
}
},
_ => "Unknown error".into()
}
}
CompileError::PrinterError => "Printer error".into(),
_ => "Unknown error".into()
}
}
}

impl<'i> From<cssparser::ParseError<'i, ()>> for CompileError<'i> {
fn from(e: cssparser::ParseError<'i, ()>) -> CompileError<'i> {
CompileError::ParseError(e)
}
}

impl<'i> From<std::fmt::Error> for CompileError<'i> {
fn from(_: std::fmt::Error) -> CompileError<'i> {
CompileError::PrinterError
}
}

impl<'i> From<parcel_sourcemap::SourceMapError> for CompileError<'i> {
fn from(e: parcel_sourcemap::SourceMapError) -> CompileError<'i> {
CompileError::SourceMapError(e)
}
}

#[cfg(not(target_arch = "wasm32"))]
impl<'i> From<CompileError<'i>> for napi::Error {
fn from(e: CompileError) -> napi::Error {
match e {
CompileError::SourceMapError(e) => e.into(),
_ => napi::Error::new(napi::Status::GenericFailure, e.reason())
}
}
}

#[cfg(target_arch = "wasm32")]
impl<'i> From<CompileError<'i>> for wasm_bindgen::JsValue {
fn from(e: CompileError) -> wasm_bindgen::JsValue {
match e {
CompileError::SourceMapError(e) => e.into(),
_ => js_sys::Error::new(&e.reason()).into()
}
}
}
88 changes: 18 additions & 70 deletions node/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
#[macro_use]
extern crate napi_derive;

#[cfg(target_os = "macos")]
#[global_allocator]
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;

#[cfg(not(target_arch = "wasm32"))]
mod stylesheet;
mod rule_list;
mod rule;
mod style_rule;

mod error;
use error::CompileError;

use serde::{Serialize, Deserialize};
use parcel_css::stylesheet::StyleSheet;
use parcel_css::targets::Browsers;
Expand All @@ -28,7 +40,7 @@ pub fn transform(config_val: JsValue) -> Result<JsValue, JsValue> {
#[cfg(not(target_arch = "wasm32"))]
use napi_derive::{js_function, module_exports};
#[cfg(not(target_arch = "wasm32"))]
use napi::{CallContext, JsObject, JsUnknown};
use napi::{CallContext, JsObject, JsUnknown, Env};

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -87,8 +99,12 @@ fn transform(ctx: CallContext) -> napi::Result<JsUnknown> {

#[cfg(not(target_arch = "wasm32"))]
#[module_exports]
fn init(mut exports: JsObject) -> napi::Result<()> {
fn init(mut exports: JsObject, env: Env) -> napi::Result<()> {
exports.create_named_method("transform", transform)?;
stylesheet::init(&mut exports, env)?;
rule_list::init(&mut exports, env)?;
rule::init(&mut exports, env)?;
style_rule::init(&mut exports, env)?;

Ok(())
}
Expand Down Expand Up @@ -137,71 +153,3 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
map
})
}

enum CompileError<'i> {
ParseError(cssparser::ParseError<'i, ()>),
PrinterError,
SourceMapError(parcel_sourcemap::SourceMapError)
}

impl<'i> CompileError<'i> {
fn reason(&self) -> String {
match self {
CompileError::ParseError(e) => {
match &e.kind {
cssparser::ParseErrorKind::Basic(b) => {
use cssparser::BasicParseErrorKind::*;
match b {
AtRuleBodyInvalid => "Invalid at rule body".into(),
EndOfInput => "Unexpected end of input".into(),
AtRuleInvalid(name) => format!("Unknown at rule: @{}", name),
QualifiedRuleInvalid => "Invalid qualified rule".into(),
UnexpectedToken(token) => format!("Unexpected token {:?}", token)
}
},
_ => "Unknown error".into()
}
}
CompileError::PrinterError => "Printer error".into(),
_ => "Unknown error".into()
}
}
}

impl<'i> From<cssparser::ParseError<'i, ()>> for CompileError<'i> {
fn from(e: cssparser::ParseError<'i, ()>) -> CompileError<'i> {
CompileError::ParseError(e)
}
}

impl<'i> From<std::fmt::Error> for CompileError<'i> {
fn from(_: std::fmt::Error) -> CompileError<'i> {
CompileError::PrinterError
}
}

impl<'i> From<parcel_sourcemap::SourceMapError> for CompileError<'i> {
fn from(e: parcel_sourcemap::SourceMapError) -> CompileError<'i> {
CompileError::SourceMapError(e)
}
}

#[cfg(not(target_arch = "wasm32"))]
impl<'i> From<CompileError<'i>> for napi::Error {
fn from(e: CompileError) -> napi::Error {
match e {
CompileError::SourceMapError(e) => e.into(),
_ => napi::Error::new(napi::Status::GenericFailure, e.reason())
}
}
}

#[cfg(target_arch = "wasm32")]
impl<'i> From<CompileError<'i>> for wasm_bindgen::JsValue {
fn from(e: CompileError) -> wasm_bindgen::JsValue {
match e {
CompileError::SourceMapError(e) => e.into(),
_ => js_sys::Error::new(&e.reason()).into()
}
}
}
59 changes: 59 additions & 0 deletions node/src/rule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use napi::{CallContext, JsString, JsObject, JsUndefined, JsFunction, Property, Ref, Result, Env};
use std::cell::RefCell;
use parcel_css::rules::CssRule;
use std::convert::TryInto;
use crate::style_rule;

static mut CLASS: RefCell<Option<Ref<()>>> = RefCell::new(None);

#[js_function(0)]
fn constructor(ctx: CallContext) -> Result<JsUndefined> {
ctx.env.get_undefined()
}

#[js_function(0)]
fn get_css_text(ctx: CallContext) -> Result<JsString> {
let this: JsObject = ctx.this_unchecked();
let rule: &mut CssRule = ctx.env.unwrap(&this)?;
let text = rule.to_css_string();
ctx.env.create_string_from_std(text)
}

pub fn init(exports: &mut JsObject, env: Env) -> Result<()> {
let stylesheet_class = env
.define_class("CSSRule", constructor, &[
Property::new(&env, "cssText")?.with_getter(get_css_text)
])?;
let mut c = unsafe { CLASS.borrow_mut() };
*c = Some(env.create_reference(&stylesheet_class)?);
exports.set_named_property("CSSRule", stylesheet_class)?;

Ok(())
}

pub fn inherit(env: &Env, obj: JsFunction) -> Result<JsFunction> {
let r = unsafe { CLASS.borrow() };
let r = r.as_ref().unwrap();
let super_class: JsFunction = env.get_reference_value(&r)?;
let super_class_obj: JsObject = super_class.coerce_to_object()?;
let class_obj: JsObject = obj.coerce_to_object()?;

// Based on https://github.com/nodejs/node-addon-api/issues/229#issuecomment-383583352
let global = env.get_global()?;
let object: JsFunction = global.get_named_property("Object")?;
let object = object.coerce_to_object()?;
let set_proto: JsFunction = object.get_named_property("setPrototypeOf")?;
let proto: JsObject = class_obj.get_named_property("prototype")?;
let super_proto: JsObject = super_class_obj.get_named_property("prototype")?;

set_proto.call(None, &[&proto, &super_proto])?;
set_proto.call(None, &[&class_obj, &super_class_obj])?;
class_obj.into_unknown().try_into()
}

pub fn create(env: &Env, rule: CssRule) -> Result<JsObject> {
match rule {
CssRule::Style(_) => style_rule::create(env, rule),
_ => unimplemented!()
}
}
81 changes: 81 additions & 0 deletions node/src/rule_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use napi::{CallContext, sys, JsNumber, JsObject, NapiRaw, NapiValue, JsUndefined, JsUnknown, JsFunction, Property, Ref, Result, Env};
use std::cell::RefCell;
use parcel_css::stylesheet::StyleSheet;
use std::rc::Rc;
use crate::rule;

// TODO: this is probably not safe.
static mut CLASS: RefCell<Option<Ref<()>>> = RefCell::new(None);

struct CSSRuleList {
stylesheet: Rc<RefCell<StyleSheet>>,
rules: Vec<sys::napi_ref>
}

#[js_function(0)]
fn constructor(ctx: CallContext) -> Result<JsUndefined> {
ctx.env.get_undefined()
}

#[js_function(0)]
fn get_length(ctx: CallContext) -> Result<JsNumber> {
let this: JsObject = ctx.this_unchecked();
let list: &mut CSSRuleList = ctx.env.unwrap(&this)?;
ctx.env.create_uint32(list.stylesheet.borrow().rules.0.len() as u32)
}

#[js_function(1)]
fn item(ctx: CallContext) -> Result<JsObject> {
let this: JsObject = ctx.this_unchecked();
let index = ctx.get::<JsNumber>(0)?.get_uint32()? as usize;
let list: &mut CSSRuleList = ctx.env.unwrap(&this)?;

// See if we already have a JS object created for this rule to preserve referential equality.
// e.g. rules.item(0) === rules.item(0)
// This drops down to low level C bindings for napi because there is currently no way
// to create a weak reference in napi-rs.
match &list.rules.get(index) {
Some(r) if !r.is_null() => {
let mut js_value = std::ptr::null_mut();
unsafe {
sys::napi_get_reference_value(ctx.env.raw(), **r, &mut js_value);
if !js_value.is_null() {
return JsObject::from_raw(ctx.env.raw(), js_value)
}
};
}
_ => {}
};

let r = list.stylesheet.borrow().rules.0[index].clone();
let obj = rule::create(ctx.env, r)?;
while list.rules.len() <= index {
list.rules.push(std::ptr::null_mut());
}
unsafe {
sys::napi_create_reference(ctx.env.raw(), obj.raw(), 0, &mut list.rules[index]);
};
Ok(obj)
}

pub fn init(exports: &mut JsObject, env: Env) -> Result<()> {
let stylesheet_class = env
.define_class("CSSRuleList", constructor, &[
Property::new(&env, "length")?.with_getter(get_length),
Property::new(&env, "item")?.with_method(item)
])?;
let mut c = unsafe { CLASS.borrow_mut() };
*c = Some(env.create_reference(&stylesheet_class)?);
exports.set_named_property("CSSRuleList", stylesheet_class)?;

Ok(())
}

pub fn create(env: &Env, stylesheet: Rc<RefCell<StyleSheet>>) -> Result<JsObject> {
let r = unsafe { CLASS.borrow() };
let r = r.as_ref().unwrap();
let c: JsFunction = env.get_reference_value(&r)?;
let mut instance = c.new::<JsUnknown>(&[])?;
env.wrap(&mut instance, CSSRuleList { stylesheet, rules: Vec::new() })?;
Ok(instance)
}
45 changes: 45 additions & 0 deletions node/src/style_rule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use napi::{CallContext, JsString, JsObject, JsUndefined, JsUnknown, JsFunction, Property, Ref, Result, Env};
use std::cell::RefCell;
use parcel_css::rules::CssRule;
use crate::rule;
use cssparser::ToCss;

static mut CLASS: RefCell<Option<Ref<()>>> = RefCell::new(None);

#[js_function(0)]
fn constructor(ctx: CallContext) -> Result<JsUndefined> {
ctx.env.get_undefined()
}

#[js_function(0)]
fn get_selector_text(ctx: CallContext) -> Result<JsString> {
let this: JsObject = ctx.this_unchecked();
let rule: &mut CssRule = ctx.env.unwrap(&this)?;
let text = match rule {
CssRule::Style(style) => style.borrow().selectors.to_css_string(),
_ => unreachable!()
};
ctx.env.create_string_from_std(text)
}

pub fn init(exports: &mut JsObject, env: Env) -> Result<()> {
let stylesheet_class = env
.define_class("CSSStyleRule", constructor, &[
Property::new(&env, "selectorText")?.with_getter(get_selector_text)
])?;
let stylesheet_class = rule::inherit(&env, stylesheet_class)?;
let mut c = unsafe { CLASS.borrow_mut() };
*c = Some(env.create_reference(&stylesheet_class)?);
exports.set_named_property("CSSStyleRule", stylesheet_class)?;

Ok(())
}

pub fn create(env: &Env, list: CssRule) -> Result<JsObject> {
let r = unsafe { CLASS.borrow() };
let r = r.as_ref().unwrap();
let c: JsFunction = env.get_reference_value(&r)?;
let mut instance = c.new::<JsUnknown>(&[])?;
env.wrap(&mut instance, list)?;
Ok(instance)
}