Skip to content

Commit

Permalink
Support auto import dts
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Nov 23, 2024
1 parent d2bd596 commit 4171641
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 29 deletions.
4 changes: 4 additions & 0 deletions crates/swc-plugin-gem/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# local test file
*.wasm
.swcrc
.swc
4 changes: 4 additions & 0 deletions crates/swc-plugin-gem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
"main": "swc_plugin_gem.wasm",
"files": [],
"scripts": {
"install": "node -e \"require('@swc/core').transform('',{filename:'auto-import'})\"",
"prepublishOnly": "cross-env CARGO_TARGET_DIR=target cargo build-wasi --release && cp target/wasm32-wasip1/release/swc_plugin_gem.wasm .",
"test": "cross-env RUST_LOG=info cargo watch -x test"
},
"devDependencies": {
"@swc/core": "^1.9.3"
},
"preferUnplugged": true,
"author": "mantou132",
"license": "ISC",
Expand Down
11 changes: 9 additions & 2 deletions crates/swc-plugin-gem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use swc_common::pass::Optional;
use swc_core::ecma::visit::VisitMutWith;
use swc_core::plugin::{plugin_transform, proxies::TransformPluginProgramMetadata};
use swc_ecma_ast::Program;
use visitors::import::gen_dts;
pub use visitors::import::import_transform;
pub use visitors::memo::memo_transform;
pub use visitors::minify::minify_transform;
Expand All @@ -12,16 +13,18 @@ mod visitors;
#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
struct PluginConfig {
#[serde(default)]
pub style_minify: bool,
#[serde(default)]
pub auto_import: bool,
#[serde(default)]
pub auto_import_dts: bool,
#[serde(default)]
pub resolve_path: bool,
#[serde(default)]
pub style_minify: bool,
#[serde(default)]
pub esm_provider: String,
#[serde(default)]
pub hmr: bool,
}

#[plugin_transform]
Expand All @@ -32,6 +35,10 @@ pub fn process_transform(mut program: Program, data: TransformPluginProgramMetad
let config =
serde_json::from_str::<PluginConfig>(plugin_config).expect("invalid config for gem plugin");

if config.auto_import_dts {
gen_dts();
}

program.visit_mut_with(&mut (
Optional {
enabled: true,
Expand Down
40 changes: 38 additions & 2 deletions crates/swc-plugin-gem/src/visitors/import.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::{
collections::{BTreeMap, HashMap, HashSet},
fs,
path::Path,
};
use swc_common::DUMMY_SP;
use swc_core::ecma::visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use swc_ecma_ast::{
Callee, Class, ClassDecl, ClassExpr, Decorator, Ident, ImportDecl, ImportNamedSpecifier,
ImportSpecifier, ModuleDecl, ModuleItem, Str, TaggedTpl,
ImportSpecifier, JSXElementName, ModuleDecl, ModuleItem, Str, TaggedTpl,
};

static CUSTOM_ELEMENT_REGEX: Lazy<Regex> =
Expand Down Expand Up @@ -106,6 +110,12 @@ impl VisitMut for TransformVisitor {
}
}

fn visit_mut_jsx_element_name(&mut self, node: &mut JSXElementName) {
if let JSXElementName::Ident(ident) = node {
self.used_members.push(ident.to_name());
}
}

fn visit_mut_decorator(&mut self, node: &mut Decorator) {
node.visit_mut_children_with(self);

Expand Down Expand Up @@ -173,6 +183,7 @@ impl VisitMut for TransformVisitor {
}
out.push(ImportDecl {
specifiers,
// 也许可以支持替换:'@mantou/gem/{:pascal:}' + ColorPicker -> '@mantou/gem/ColorPicker'
src: Box::new(Str::from(pkg)),
span: DUMMY_SP,
type_only: false,
Expand Down Expand Up @@ -208,3 +219,28 @@ impl VisitMut for TransformVisitor {
pub fn import_transform() -> impl VisitMut {
TransformVisitor::default()
}

pub fn gen_dts() {
// https://github.com/swc-project/swc/discussions/4997
let types_dir = "/cwd/node_modules/@types/auto-import";
let mut import_list: Vec<String> = vec![];
for (member, pkg) in GEM_AUTO_IMPORT_CONFIG.member_map.iter() {
import_list.push(format!(
"const {member}: typeof import('{pkg}')['{member}'];",
));
}
fs::create_dir_all(types_dir).expect("create auto import dir error");
fs::write(
Path::new(types_dir).join("index.d.ts"),
format!(
r#"
export {{}}
declare global {{
{}
}}
"#,
import_list.join("\n")
),
)
.expect("create dts error");
}
6 changes: 3 additions & 3 deletions crates/swc-plugin-gem/src/visitors/memo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ fn is_memo_getter(node: &mut PrivateMethod) -> bool {
}

node.function.decorators.iter().any(|x| {
if let Expr::Call(ref call_expr) = *x.expr {
if let Callee::Expr(ref b) = call_expr.callee {
if let Expr::Ident(ref ident) = **b {
if let Some(call_expr) = x.expr.as_call() {
if let Callee::Expr(b) = &call_expr.callee {
if let Some(ident) = b.as_ident() {
return ident.sym.as_str() == "memo";
}
}
Expand Down
54 changes: 51 additions & 3 deletions crates/swc-plugin-gem/src/visitors/minify.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
use once_cell::sync::Lazy;
use regex::Regex;
use swc_common::DUMMY_SP;
use swc_core::ecma::visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use swc_ecma_ast::TaggedTpl;
use swc_ecma_ast::{Callee, KeyValueProp, TaggedTpl, Tpl, TplElement};

static HEAD_REG: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?s)\s*(\{)\s*").unwrap());
static TAIL_REG: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?s)(;|})\s+").unwrap());
static SPACE_REG: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?s)\s+").unwrap());

fn minify_tpl(tpl: &Tpl) -> Tpl {
Tpl {
span: DUMMY_SP,
exprs: tpl.exprs.clone(),
quasis: tpl
.quasis
.iter()
.map(|x| {
let remove_head = &HEAD_REG.replace_all(x.raw.as_str(), "$1");
let remove_tail = &TAIL_REG.replace_all(remove_head, "$1");
let remove_space = SPACE_REG.replace_all(remove_tail, " ");
TplElement {
span: DUMMY_SP,
tail: x.tail,
cooked: None,
raw: remove_space.trim().into(),
}
})
.collect(),
}
}

#[derive(Default)]
struct TransformVisitor {}
Expand All @@ -10,8 +39,27 @@ impl VisitMut for TransformVisitor {
fn visit_mut_tagged_tpl(&mut self, node: &mut TaggedTpl) {
node.visit_mut_children_with(self);

for _ele in node.tpl.quasis.iter() {
// TODO: implement
if let Some(ident) = node.tag.as_ident() {
let tag_fn = ident.sym.as_str();
if tag_fn == "css" || tag_fn == "styled" {
node.tpl = Box::new(minify_tpl(&node.tpl));
}
}
}

fn visit_mut_callee(&mut self, node: &mut Callee) {
if let Callee::Expr(expr) = &node {
if let Some(ident) = expr.as_ident() {
if ident.sym.as_str() == "css" {
node.visit_mut_children_with(self);
}
}
}
}

fn visit_mut_key_value_prop(&mut self, node: &mut KeyValueProp) {
if let Some(tpl) = node.value.as_tpl() {
node.value = minify_tpl(tpl).into();
}
}
}
Expand Down
15 changes: 14 additions & 1 deletion crates/swc-plugin-gem/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use swc_core::ecma::transforms::testing::test_fixture;
use swc_ecma_parser::{Syntax, TsSyntax};
use swc_ecma_visit::visit_mut_pass;
use swc_plugin_gem::{import_transform, memo_transform};
use swc_plugin_gem::{import_transform, memo_transform, minify_transform};
use testing::fixture;

fn get_syntax() -> Syntax {
Expand Down Expand Up @@ -38,3 +38,16 @@ fn fixture_memo(input: PathBuf) {
Default::default(),
);
}

#[fixture("tests/fixture/minify/input.ts")]
fn fixture_minify(input: PathBuf) {
let output = input.parent().unwrap().join("output.ts");

test_fixture(
get_syntax(),
&|_| visit_mut_pass(minify_transform()),
&input,
&output,
Default::default(),
);
}
7 changes: 6 additions & 1 deletion crates/swc-plugin-gem/tests/fixture/minify/input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// @ts-nocheck
const style = css`
:host {
color: red;
color: ${' red'};
}
`
const style2 = css({
$: `
color: ${' red'};
`
})
5 changes: 4 additions & 1 deletion crates/swc-plugin-gem/tests/fixture/minify/output.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
// @ts-nocheck
const style = css`:host {color: red;}`
const style = css`:host{color:${' red'};}`;
const style2 = css({
$: `color:${' red'};`
});
7 changes: 4 additions & 3 deletions crates/zed-plugin-gem/extension.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ authors = ["mantou132 <[email protected]>"]
repository = "https://github.com/mantou132/gem"
snippets = "./snippets/typescript.json"

[grammars.typescript]
repository = "https://github.com/tree-sitter/tree-sitter-typescript"
commit = "f975a621f4e7f532fe322e13c4f79495e0a7b2e7"
[language_servers.gem]
name = "Gem language server"
languages = ["TypeScript", "TSX", "JavaScript", "JSDoc"]
language_ids = { "TypeScript" = "typescript", "TSX" = "typescriptreact", "JavaScript" = "javascript" }
103 changes: 93 additions & 10 deletions crates/zed-plugin-gem/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,99 @@
use zed_extension_api as zed;
///! html: emmet + html
///! css: css
use std::{env, fs};
use zed::settings::LspSettings;
use zed_extension_api::{self as zed, LanguageServerId, Result};

struct MyExtension {
// ... state
const NPM_PKG_NAME: &str = "vscode-gem-languageservice";
const LS_BIN_PATH: &str = "node_modules/.bin/vscode-gem-languageservice";

#[derive(Default)]
struct GemExtension {
did_find_server: bool,
}

impl zed::Extension for MyExtension {
fn new() -> Self
where
Self: Sized,
{
MyExtension {}
impl GemExtension {
fn server_exists(&self) -> bool {
fs::metadata(LS_BIN_PATH).map_or(false, |stat| stat.is_file())
}

fn server_script_path(&mut self, language_server_id: &zed::LanguageServerId) -> Result<String> {
let server_exists = self.server_exists();
if self.did_find_server && server_exists {
return Ok(LS_BIN_PATH.to_string());
}

zed::set_language_server_installation_status(
language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
let version = zed::npm_package_latest_version(NPM_PKG_NAME)?;

if !server_exists
|| zed::npm_package_installed_version(NPM_PKG_NAME)?.as_ref() != Some(&version)
{
zed::set_language_server_installation_status(
language_server_id,
&zed::LanguageServerInstallationStatus::Downloading,
);
let result = zed::npm_install_package(NPM_PKG_NAME, &version);
match result {
Ok(()) => {
if !self.server_exists() {
Err(format!(
"installed package '{NPM_PKG_NAME}' did not contain expected path '{LS_BIN_PATH}'",
))?;
}
}
Err(error) => {
if !self.server_exists() {
Err(error)?;
}
}
}
}

self.did_find_server = true;
Ok(LS_BIN_PATH.to_string())
}
}

impl zed::Extension for GemExtension {
fn new() -> Self {
Self::default()
}

fn language_server_command(
&mut self,
language_server_id: &LanguageServerId,
_worktree: &zed::Worktree,
) -> Result<zed::Command> {
let server_path = self.server_script_path(language_server_id)?;
Ok(zed::Command {
command: zed::node_binary_path()?,
args: vec![
env::current_dir()
.unwrap()
.join(&server_path)
.to_string_lossy()
.to_string(),
"--stdio".to_string(),
],
env: Default::default(),
})
}

fn language_server_workspace_configuration(
&mut self,
server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<Option<zed::serde_json::Value>> {
let settings = LspSettings::for_worktree(server_id.as_ref(), worktree)
.ok()
.and_then(|lsp_settings| lsp_settings.settings.clone())
.unwrap_or_default();
Ok(Some(settings))
}
}

zed::register_extension!(MyExtension);
zed::register_extension!(GemExtension);
4 changes: 1 addition & 3 deletions packages/language-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
],
"type": "module",
"module": "src/index.ts",
"bin": {
"gem-language-server": "src/index.ts"
},
"bin": "src/index.ts",
"files": [
"/src/"
],
Expand Down

0 comments on commit 4171641

Please sign in to comment.