Skip to content

Commit

Permalink
Optimize binary size and performance (#164)
Browse files Browse the repository at this point in the history
* feat: avoid visit types

* chore: upgrade rust, optimize binary size
  • Loading branch information
JSerFeng authored Aug 4, 2023
1 parent d2facc1 commit 2b75e9a
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 134 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-ants-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@modern-js/swc-plugins": patch
---

chore: upgrade rust, optimize binary size
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ env:
DEBUG: napi:*
APP_NAME: swc-plugins
MACOSX_DEPLOYMENT_TARGET: "10.13"
CARGO_PROFILE_RELEASE_LTO: "fat"
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 1

"on":
workflow_dispatch: null

Expand Down
13 changes: 10 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ cargo-features = ["strip"]
[workspace]
exclude = ["crates/plugin_modernjs_ssr_loader_id/wasm"]
members = ["crates/*"]
resolver = "2"

[profile.release]
lto = false
strip = "symbols"

# Enable following optimization on CI, based on env variable
# lto = true
# codegen-units = 1

[workspace.dependencies]
rustc-hash = { version = "1.1.0" }
anyhow = { version = "1.0.69" }
Expand Down
1 change: 1 addition & 0 deletions crates/plugin_import/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ regex = "1.6.0"
serde = { workspace = true }
rustc-hash = { workspace = true }
swc_core = { workspace = true, features = ["common", "ecma_ast", "ecma_visit"] }
heck = "0.4.1"

[dev-dependencies]
test_plugins = { path = "../test_plugins" }
220 changes: 103 additions & 117 deletions crates/plugin_import/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
mod visit;
use std::fmt::Debug;

use handlebars::{Context, Helper, HelperResult, Output, RenderContext, Template};
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, Template};
use heck::ToKebabCase;
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use serde::Deserialize;
use swc_core::{
common::{util::take::Take, BytePos, Span, SyntaxContext, DUMMY_SP},
common::{sync::Lazy, util::take::Take, BytePos, Span, SyntaxContext, DUMMY_SP},
ecma::{
ast::{
Ident, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, ImportSpecifier, Module,
Expand All @@ -18,63 +19,7 @@ use swc_core::{

use crate::visit::IdentComponent;

#[derive(Debug, Deserialize, Clone)]
pub enum StyleConfig {
StyleLibraryDirectory(String),
#[serde(skip)]
Custom(CustomTransform),
Css,
Bool(bool),
None,
}

#[derive(Deserialize)]
pub enum CustomTransform {
#[serde(skip)]
Fn(Box<dyn Sync + Send + Fn(String) -> Option<String>>),
Tpl(String),
}

impl Clone for CustomTransform {
fn clone(&self) -> Self {
match self {
Self::Fn(_) => panic!("Function cannot be cloned"),
Self::Tpl(s) => Self::Tpl(s.clone()),
}
}
}

impl Debug for CustomTransform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CustomTransform::Fn(_) => f.write_str("Function"),
CustomTransform::Tpl(t) => f.write_str(t),
}
}
}

#[derive(Debug, Deserialize, Default, Clone)]
pub struct PluginImportConfig {
pub library_name: String,
pub library_directory: Option<String>, // default to `lib`
#[serde(skip)]
pub custom_name: Option<CustomTransform>,
#[serde(skip)]
pub custom_style_name: Option<CustomTransform>, // If this is set, `style` option will be ignored
pub style: Option<StyleConfig>,

pub camel_to_dash_component_name: Option<bool>, // default to true
pub transform_to_default_import: Option<bool>,

pub ignore_es_component: Option<Vec<String>>,
pub ignore_style_component: Option<Vec<String>>,
}

const CUSTOM_JS: &str = "CUSTOM_JS_NAME";
const CUSTOM_STYLE: &str = "CUSTOM_STYLE";
const CUSTOM_STYLE_NAME: &str = "CUSTOM_STYLE_NAME";

pub fn plugin_import(config: &Vec<PluginImportConfig>) -> impl Fold + '_ {
static RENDERER: Lazy<Handlebars> = Lazy::new(|| {
let mut renderer = handlebars::Handlebars::new();

renderer.register_helper(
Expand All @@ -90,7 +35,7 @@ pub fn plugin_import(config: &Vec<PluginImportConfig>) -> impl Fold + '_ {
.param(0)
.and_then(|v| v.value().as_str())
.unwrap_or("");
out.write(param.camel_to_kebab().as_ref())?;
out.write(param.to_kebab_case().as_ref())?;
Ok(())
},
),
Expand Down Expand Up @@ -134,6 +79,68 @@ pub fn plugin_import(config: &Vec<PluginImportConfig>) -> impl Fold + '_ {
),
);

renderer
});

#[derive(Debug, Deserialize, Clone)]
pub enum StyleConfig {
StyleLibraryDirectory(String),
#[serde(skip)]
Custom(CustomTransform),
Css,
Bool(bool),
None,
}

#[derive(Deserialize)]
pub enum CustomTransform {
#[serde(skip)]
Fn(Box<dyn Sync + Send + Fn(String) -> Option<String>>),
Tpl(String),
}

impl Clone for CustomTransform {
fn clone(&self) -> Self {
match self {
Self::Fn(_) => panic!("Function cannot be cloned"),
Self::Tpl(s) => Self::Tpl(s.clone()),
}
}
}

impl Debug for CustomTransform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CustomTransform::Fn(_) => f.write_str("Function"),
CustomTransform::Tpl(t) => f.write_str(t),
}
}
}

#[derive(Debug, Deserialize, Default, Clone)]
pub struct PluginImportConfig {
pub library_name: String,
pub library_directory: Option<String>, // default to `lib`
#[serde(skip)]
pub custom_name: Option<CustomTransform>,
#[serde(skip)]
pub custom_style_name: Option<CustomTransform>, // If this is set, `style` option will be ignored
pub style: Option<StyleConfig>,

pub camel_to_dash_component_name: Option<bool>, // default to true
pub transform_to_default_import: Option<bool>,

pub ignore_es_component: Option<Vec<String>>,
pub ignore_style_component: Option<Vec<String>>,
}

const CUSTOM_JS: &str = "CUSTOM_JS_NAME";
const CUSTOM_STYLE: &str = "CUSTOM_STYLE";
const CUSTOM_STYLE_NAME: &str = "CUSTOM_STYLE_NAME";

pub fn plugin_import(config: &Vec<PluginImportConfig>) -> impl Fold + VisitMut + '_ {
let mut renderer = RENDERER.clone();

config.iter().for_each(|cfg| {
if let Some(CustomTransform::Tpl(tpl)) = &cfg.custom_name {
renderer.register_template(
Expand Down Expand Up @@ -198,7 +205,7 @@ impl<'a> ImportPlugin<'a> {
.unwrap_or(false);

let transformed_name = if config.camel_to_dash_component_name.unwrap_or(true) {
name.camel_to_kebab()
name.to_kebab_case()
} else {
name.clone()
};
Expand Down Expand Up @@ -374,17 +381,32 @@ impl<'a> VisitMut for ImportPlugin<'a> {
}
}

module.body = module
let other_imports: Vec<_> = module
.body
.take()
.into_iter()
.enumerate()
.filter_map(|(idx, stmt)| (!specifiers_rm_es.contains(&idx)).then_some(stmt))
.collect();

let body = &mut module.body;
let mut imports = vec![];

for js_source in specifiers_es {
for css_source in specifiers_css.iter().rev() {
let dec = ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: vec![],
src: Box::new(Str {
span: DUMMY_SP,
value: JsWord::from(css_source.as_str()),
raw: None,
}),
type_only: false,
asserts: None,
}));
imports.push(dec);
}

for js_source in specifiers_es.iter().rev() {
let js_source_ref = js_source.source.as_str();
let dec = ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
Expand All @@ -397,7 +419,13 @@ impl<'a> VisitMut for ImportPlugin<'a> {
BytePos::DUMMY,
SyntaxContext::from_u32(js_source.mark),
),
sym: JsWord::from(js_source.as_name.unwrap_or(js_source.default_spec).as_str()),
sym: JsWord::from(
js_source
.as_name
.clone()
.unwrap_or(js_source.default_spec.clone())
.as_str(),
),
optional: false,
},
})]
Expand All @@ -419,7 +447,13 @@ impl<'a> VisitMut for ImportPlugin<'a> {
BytePos::DUMMY,
SyntaxContext::from_u32(js_source.mark),
),
sym: JsWord::from(js_source.as_name.unwrap_or(js_source.default_spec).as_str()),
sym: JsWord::from(
js_source
.as_name
.clone()
.unwrap_or(js_source.default_spec.clone())
.as_str(),
),
optional: false,
},
is_type_only: false,
Expand All @@ -433,23 +467,11 @@ impl<'a> VisitMut for ImportPlugin<'a> {
type_only: false,
asserts: None,
}));
body.insert(0, dec);
imports.push(dec);
}

for css_source in specifiers_css {
let dec = ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: vec![],
src: Box::new(Str {
span: DUMMY_SP,
value: JsWord::from(css_source),
raw: None,
}),
type_only: false,
asserts: None,
}));
body.insert(0, dec);
}
imports.extend(other_imports);
module.body = imports;
}
}

Expand All @@ -458,39 +480,3 @@ fn render_context(s: String) -> HashMap<&'static str, String> {
ctx.insert("member", s);
ctx
}

trait KebabCase {
fn camel_to_kebab(&self) -> String;
}

impl<T> KebabCase for T
where
T: AsRef<str>,
{
fn camel_to_kebab(&self) -> String {
let s: &str = self.as_ref();
let mut output = String::with_capacity(s.len());

s.chars().enumerate().for_each(|(idx, c)| {
if c.is_uppercase() {
if idx > 0 {
output.push('-');
}
output.push_str(c.to_lowercase().to_string().as_str());
} else {
output.push(c);
}
});

output
}
}

#[test]
fn test_kebab_case() {
assert_eq!("ABCD".camel_to_kebab(), "a-b-c-d");
assert_eq!("AbCd".camel_to_kebab(), "ab-cd");
assert_eq!("Aaaa".camel_to_kebab(), "aaaa");
assert_eq!("A".camel_to_kebab(), "a");
assert_eq!("".camel_to_kebab(), "");
}
Loading

0 comments on commit 2b75e9a

Please sign in to comment.