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

Optimize binary size and performance #164

Merged
merged 3 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading