diff --git a/Cargo.lock b/Cargo.lock index a994b5ead..e4828cb83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3411,6 +3411,23 @@ dependencies = [ "widestring", ] +[[package]] +name = "swc_inject_imports" +version = "0.1.0" +dependencies = [ + "rustc-hash", + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_testing", + "swc_ecma_utils", + "swc_ecma_visit", + "testing", +] + [[package]] name = "swc_macros_common" version = "1.0.1" @@ -3678,6 +3695,15 @@ dependencies = [ "swc_core", ] +[[package]] +name = "swc_plugin_swc_inject_imports" +version = "0.7.4" +dependencies = [ + "serde_json", + "swc_core", + "swc_inject_imports", +] + [[package]] name = "swc_plugin_swc_magic" version = "0.7.4" diff --git a/packages/swc-inject-imports/.npmignore b/packages/swc-inject-imports/.npmignore new file mode 100644 index 000000000..a601c5cd1 --- /dev/null +++ b/packages/swc-inject-imports/.npmignore @@ -0,0 +1,3 @@ +__tests__/ +transform/ +Cargo.toml \ No newline at end of file diff --git a/packages/swc-inject-imports/CHANGELOG.md b/packages/swc-inject-imports/CHANGELOG.md new file mode 100644 index 000000000..93eb5e042 --- /dev/null +++ b/packages/swc-inject-imports/CHANGELOG.md @@ -0,0 +1 @@ +# ChangeLog diff --git a/packages/swc-inject-imports/Cargo.toml b/packages/swc-inject-imports/Cargo.toml new file mode 100644 index 000000000..28f6fd4d4 --- /dev/null +++ b/packages/swc-inject-imports/Cargo.toml @@ -0,0 +1,25 @@ +[package] + +description = "SWC Plugin for swc-experimental-inject-imports" + + +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +name = "swc_plugin_swc_inject_imports" +publish = false +repository = { workspace = true } +rust-version = { workspace = true } +version = "0.7.4" + + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +serde_json = { workspace = true } +swc_core = { workspace = true, features = ["ecma_plugin_transform"] } + + +swc_inject_imports = { path = "./transform" } diff --git a/packages/swc-inject-imports/README.md b/packages/swc-inject-imports/README.md new file mode 100644 index 000000000..0fef57748 --- /dev/null +++ b/packages/swc-inject-imports/README.md @@ -0,0 +1,23 @@ +# @swc/plugin-swc-experimental-inject-imports + +## Usage + +.swcrc: + +```json +{ + "jsc": { + "experimental": { + "plugins": [ + "@swc/plugin-swc-experimental-inject-imports", + { + "importsPaths": ["@swc/experimental-inject-imports"], + "onlyFilenames": ["page.tsx", "layout.tsx"] + } + ] + } + } +} +``` + +# @swc/plugin-swc-experimental-inject-imports diff --git a/packages/swc-inject-imports/README.tmpl.md b/packages/swc-inject-imports/README.tmpl.md new file mode 100644 index 000000000..032e40f39 --- /dev/null +++ b/packages/swc-inject-imports/README.tmpl.md @@ -0,0 +1,23 @@ +# @swc/plugin-swc-experimental-inject-imports + +## Usage + +.swcrc: + +```json +{ + "jsc": { + "experimental": { + "plugins": [ + "@swc/plugin-swc-experimental-inject-imports", + { + "importsPaths": ["@swc/example-import"], + "onlyFilenames": ["page.tsx", "layout.tsx"] + } + ] + } + } +} +``` + +${CHANGELOG} diff --git a/packages/swc-inject-imports/package.json b/packages/swc-inject-imports/package.json new file mode 100644 index 000000000..d034f58e7 --- /dev/null +++ b/packages/swc-inject-imports/package.json @@ -0,0 +1,29 @@ +{ + "name": "@swc/plugin-swc-experimental-inject-imports", + "version": "1.0.0", + "publishConfig": { + "access": "public", + "provenance": true + }, + "description": "SWC plugin for swc-experimental-inject-imports", + "main": "swc_plugin_swc_inject_imports.wasm", + "scripts": { + "prepack": "cargo build --release -p swc_plugin_swc_inject_imports --target wasm32-wasip1 && cp ../../target/wasm32-wasip1/release/swc_plugin_swc_inject_imports.wasm ." + }, + "homepage": "https://swc.rs", + "repository": { + "type": "git", + "url": "git+https://github.com/swc-project/plugins.git", + "directory": "packages/swc-experimental-inject-imports" + }, + "bugs": { + "url": "https://github.com/swc-project/plugins/issues" + }, + "author": "강동윤 ", + "keywords": [], + "license": "Apache-2.0", + "preferUnplugged": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } +} \ No newline at end of file diff --git a/packages/swc-inject-imports/src/lib.rs b/packages/swc-inject-imports/src/lib.rs new file mode 100644 index 000000000..cad84c0b9 --- /dev/null +++ b/packages/swc-inject-imports/src/lib.rs @@ -0,0 +1,33 @@ +#![allow(clippy::not_unsafe_ptr_arg_deref)] +#![feature(box_patterns)] + +use swc_core::{ + ecma::ast::Program, + plugin::{ + metadata::TransformPluginMetadataContextKind, plugin_transform, + proxies::TransformPluginProgramMetadata, + }, +}; + +#[plugin_transform] +fn swc_inject_imports_plugin( + mut program: Program, + data: TransformPluginProgramMetadata, +) -> Program { + let config = serde_json::from_str::( + &data + .get_transform_plugin_config() + .expect("failed to get plugin config for swc-experimental-inject-imports"), + ) + .expect("invalid config for swc-experimental-inject-imports"); + + let filename = data + .get_context(&TransformPluginMetadataContextKind::Filename) + .unwrap() + .to_string() + .into(); + + program.mutate(swc_inject_imports::swc_inject_imports(filename, config)); + + program +} diff --git a/packages/swc-inject-imports/transform/Cargo.toml b/packages/swc-inject-imports/transform/Cargo.toml new file mode 100644 index 000000000..ac24e75e7 --- /dev/null +++ b/packages/swc-inject-imports/transform/Cargo.toml @@ -0,0 +1,29 @@ +[package] + +description = "AST transforms visitor for swc-experimental-inject-imports" + + +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +name = "swc_inject_imports" +repository = { workspace = true } +rust-version = { workspace = true } +version = "0.1.0" + + +[dependencies] +rustc-hash = { workspace = true } +serde = { workspace = true, features = ["derive"] } +swc_atoms = { workspace = true } +swc_common = { workspace = true } +swc_ecma_ast = { workspace = true } +swc_ecma_utils = { workspace = true } +swc_ecma_visit = { workspace = true } + +[dev-dependencies] +swc_ecma_parser = { workspace = true } +swc_ecma_transforms_base = { workspace = true } +swc_ecma_transforms_testing = { workspace = true } +testing = { workspace = true } diff --git a/packages/swc-inject-imports/transform/src/lib.rs b/packages/swc-inject-imports/transform/src/lib.rs new file mode 100644 index 000000000..a301b780f --- /dev/null +++ b/packages/swc-inject-imports/transform/src/lib.rs @@ -0,0 +1,61 @@ +#![feature(box_patterns)] + +use serde::Deserialize; +use swc_atoms::Atom; +use swc_common::util::take::Take; +use swc_ecma_ast::{ImportDecl, Module, ModuleDecl, ModuleItem, Pass, Program, Str}; +use swc_ecma_utils::prepend_stmts; + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Config { + #[serde(default)] + pub imports_paths: Vec, + + #[serde(default)] + pub only_filenames: Vec, +} + +pub fn swc_inject_imports(filename: Atom, config: Config) -> impl Pass { + InjectImports { config, filename } +} + +struct InjectImports { + config: Config, + filename: Atom, +} + +impl Pass for InjectImports { + fn process(&mut self, program: &mut swc_ecma_ast::Program) { + if self.config.imports_paths.is_empty() { + return; + } + + if !self.config.only_filenames.is_empty() { + let mut find = false; + for filename_filter in self.config.only_filenames.iter() { + if self.filename.ends_with(&**filename_filter) { + find = true; + break; + } + } + if !find { + return; + } + } + + let Program::Module(Module { body, .. }) = program else { + return; + }; + + let new_imports = self.config.imports_paths.iter().map(|path| { + ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { + src: Box::new(Str::from(path.clone())), + specifiers: vec![], + ..Take::dummy() + })) + }); + + prepend_stmts(body, new_imports); + } +} diff --git a/packages/swc-inject-imports/transform/tests/fixture.rs b/packages/swc-inject-imports/transform/tests/fixture.rs new file mode 100644 index 000000000..ce21b10e3 --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture.rs @@ -0,0 +1,34 @@ +use std::path::PathBuf; + +use swc_atoms::Atom; +use swc_common::Mark; +use swc_ecma_parser::Syntax; +use swc_ecma_transforms_base::resolver; +use swc_ecma_transforms_testing::test_fixture; +use swc_inject_imports::Config; + +#[testing::fixture("tests/fixture/**/input.js")] +fn pure(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + test_fixture( + Syntax::default(), + &|_tr| { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + + ( + resolver(unresolved_mark, top_level_mark, false), + swc_inject_imports::swc_inject_imports( + "test.js".into(), + Config { + imports_paths: vec![Atom::new("@swc/experimental-inject-imports")], + only_filenames: vec![], + }, + ), + ) + }, + &input, + &output, + Default::default(), + ); +} diff --git a/packages/swc-inject-imports/transform/tests/fixture/directive/input.js b/packages/swc-inject-imports/transform/tests/fixture/directive/input.js new file mode 100644 index 000000000..857130634 --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture/directive/input.js @@ -0,0 +1,5 @@ +'use client'; + +import { useEffect } from "react"; + +console.log(useEffect); \ No newline at end of file diff --git a/packages/swc-inject-imports/transform/tests/fixture/directive/output.js b/packages/swc-inject-imports/transform/tests/fixture/directive/output.js new file mode 100644 index 000000000..95dc9e7ad --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture/directive/output.js @@ -0,0 +1,4 @@ +'use client'; +import "@swc/experimental-inject-imports"; +import { useEffect } from "react"; +console.log(useEffect); diff --git a/packages/swc-inject-imports/transform/tests/fixture/no-name-clash/input.js b/packages/swc-inject-imports/transform/tests/fixture/no-name-clash/input.js new file mode 100644 index 000000000..a62322c22 --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture/no-name-clash/input.js @@ -0,0 +1,3 @@ +import { markAsPure } from "not-swc-experimental-inject-imports"; + +markAsPure(console.log("test!")); diff --git a/packages/swc-inject-imports/transform/tests/fixture/no-name-clash/output.js b/packages/swc-inject-imports/transform/tests/fixture/no-name-clash/output.js new file mode 100644 index 000000000..27b1c2b9f --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture/no-name-clash/output.js @@ -0,0 +1,3 @@ +import "@swc/experimental-inject-imports"; +import { markAsPure } from "not-swc-experimental-inject-imports"; +markAsPure(console.log("test!")); diff --git a/packages/swc-inject-imports/transform/tests/fixture/simple/input.js b/packages/swc-inject-imports/transform/tests/fixture/simple/input.js new file mode 100644 index 000000000..b18cc8148 --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture/simple/input.js @@ -0,0 +1,3 @@ +import { markAsPure } from "@swc/experimental-inject-imports"; + +markAsPure(console.log("test!")); diff --git a/packages/swc-inject-imports/transform/tests/fixture/simple/output.js b/packages/swc-inject-imports/transform/tests/fixture/simple/output.js new file mode 100644 index 000000000..5811530a8 --- /dev/null +++ b/packages/swc-inject-imports/transform/tests/fixture/simple/output.js @@ -0,0 +1,3 @@ +import "@swc/experimental-inject-imports"; +import { markAsPure } from "@swc/experimental-inject-imports"; +markAsPure(console.log("test!")); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c17318ab1..a3f282ec2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,6 +122,12 @@ importers: specifier: ^0.1.3 version: 0.1.3 + packages/swc-inject-imports: + dependencies: + '@swc/counter': + specifier: ^0.1.3 + version: 0.1.3 + packages/swc-magic: dependencies: '@swc/counter':