Skip to content

Commit

Permalink
feat(loadable-components)!: support lazy and configuring
Browse files Browse the repository at this point in the history
  • Loading branch information
Themezv committed May 1, 2024
1 parent 6f37d7d commit ab11cbf
Show file tree
Hide file tree
Showing 49 changed files with 420 additions and 17 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions packages/loadable-components/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
once_cell = "1.13.1"
regex = "1.6.0"
serde = { version = "1.0.199", features = ["derive"] }
serde_json = "1.0.79"
swc_common = { version = "0.33.25", features = ["concurrent"] }
swc_core = { version = "0.91.0", features = [
Expand Down
15 changes: 15 additions & 0 deletions packages/loadable-components/README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,19 @@
["loadable-components", {}]
```

Sometimes you need to wrap loadable with your own custom logic. There are many use cases for it, from injecting telemetry to hiding external libraries behind facade.
By default `loadable-components` are configured to transform dynamic imports used only inside loadable helpers, but can be configured to instrument any other function of your choice.
```json
["loadable-components", { "signatures": [
{
"from": "myLoadableWrapper",
"name": "default"
},
{
"from": "myLoadableWrapper",
"name": "lazy"
}]
}]
```

${CHANGELOG}
107 changes: 98 additions & 9 deletions packages/loadable-components/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![allow(clippy::boxed_local)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]

use std::collections::HashSet;

use once_cell::sync::Lazy;
use serde::Deserialize;
use swc_common::{
comments::{Comment, CommentKind, Comments},
util::take::Take,
Expand Down Expand Up @@ -34,40 +37,59 @@ static MATCH_LEFT_HYPHENS_REPLACE_REGEX: Lazy<regex::Regex> =
#[plugin_transform]
fn loadable_components_plugin(
mut program: Program,
_data: TransformPluginProgramMetadata,
data: TransformPluginProgramMetadata,
) -> Program {
program.visit_mut_with(&mut loadable_transform(PluginCommentsProxy));
let config = serde_json::from_str::<PluginConfig>(
&data
.get_transform_plugin_config()
.expect("failed to get plugin config for swc-loadable-components"),
)
.expect("invalid config for swc-loadable-components");

let signatures = config.signatures;

program.visit_mut_with(&mut loadable_transform(
PluginCommentsProxy,
signatures.unwrap_or(vec![Signature::default(), Signature::default_lazy()]),
));

program
}

pub fn loadable_transform<C>(comments: C) -> impl VisitMut
pub fn loadable_transform<C>(comments: C, signatures: Vec<Signature>) -> impl VisitMut
where
C: Comments,
{
Loadable { comments }
Loadable {
comments,
signatures,
specifiers: HashSet::new(),
}
}

struct Loadable<C>
where
C: Comments,
{
comments: C,
signatures: Vec<Signature>,
specifiers: HashSet<String>,
}

impl<C> Loadable<C>
where
C: Comments,
{
fn is_valid_identifier(e: &Expr) -> bool {
fn is_valid_identifier(&self, e: &Expr) -> bool {
dbg!(&self.specifiers);
match e {
Expr::Ident(i) => &*i.sym == "loadable",
Expr::Ident(i) => self.specifiers.contains(&*i.sym),
Expr::Member(MemberExpr {
obj,
prop: MemberProp::Ident(prop),
..
}) => match &**obj {
Expr::Ident(i) => &*i.sym == "loadable" && &*prop.sym == "lib",
Expr::Ident(i) => self.specifiers.contains(&*i.sym) && &*prop.sym == "lib",
_ => false,
},
_ => false,
Expand Down Expand Up @@ -616,11 +638,45 @@ impl<C> VisitMut for Loadable<C>
where
C: Comments,
{
fn visit_mut_import_decl(&mut self, import_decl: &mut ImportDecl) {
for signature in self.signatures.iter() {
dbg!(&signature);
dbg!(&import_decl.src.value);
dbg!(&signature.from);
if dbg!(signature.from == *import_decl.src.value) {
for specifier in import_decl.specifiers.iter() {
match specifier {
ImportSpecifier::Default(default_spec) => {
if signature.is_default_specifier() {
self.specifiers.insert(default_spec.local.sym.to_string());
}
}
ImportSpecifier::Named(named_specifier) => {
dbg!(&named_specifier.local.sym);
if let Some(ModuleExportName::Ident(imported)) =
&named_specifier.imported
{
if imported.sym == signature.name {
self.specifiers
.insert(named_specifier.local.sym.to_string());
return;
}
}
if named_specifier.local.sym == signature.name {
self.specifiers.insert(signature.name.clone());
}
}
_ => (),
}
}
}
}
}

fn visit_mut_call_expr(&mut self, call: &mut CallExpr) {
call.visit_mut_children_with(self);

match &call.callee {
Callee::Expr(callee) if Self::is_valid_identifier(callee) => {}
Callee::Expr(callee) if self.is_valid_identifier(callee) => {}
_ => return,
}

Expand Down Expand Up @@ -725,3 +781,36 @@ fn clone_params(e: &Expr) -> Vec<Param> {
_ => Default::default(),
}
}

#[derive(Debug, Clone, Deserialize)]
pub struct Signature {
pub name: String,
pub from: String,
}

impl Default for Signature {
fn default() -> Self {
Signature {
name: "default".into(),
from: "@loadable/component".into(),
}
}
}

impl Signature {
fn is_default_specifier(&self) -> bool {
self.name == "default".to_string()
}

pub fn default_lazy() -> Self {
Signature {
name: "lazy".into(),
from: "@loadable/component".into(),
}
}
}

#[derive(Default, Debug, Clone, Deserialize)]
struct PluginConfig {
signatures: Option<Vec<Signature>>,
}
48 changes: 44 additions & 4 deletions packages/loadable-components/tests/fixture.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
use std::path::PathBuf;

use swc_core::ecma::{transforms::testing::test_fixture, visit::as_folder};
use swc_plugin_loadable_components::loadable_transform;
use swc_plugin_loadable_components::{loadable_transform, Signature};

#[testing::fixture("tests/fixture/**/input.js")]
fn fixture(input: PathBuf) {
#[testing::fixture("tests/fixture/aggressive import/**/input.js")]
#[testing::fixture("tests/fixture/lazy/**/input.js")]
#[testing::fixture("tests/fixture/loadable.lib/**/input.js")]
#[testing::fixture("tests/fixture/Magic comment/**/input.js")]
#[testing::fixture("tests/fixture/simple import/**/input.js")]
fn fixture_default_signatures(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");

test_fixture(
Default::default(),
&|t| as_folder(loadable_transform(t.comments.clone())),
&|t| {
as_folder(loadable_transform(
t.comments.clone(),
vec![Signature::default(), Signature::default_lazy()],
))
},
&input,
&output,
Default::default(),
);
}

#[testing::fixture("tests/fixture/signatures/**/input.js")]
fn fixture_custom_signatures(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");

test_fixture(
Default::default(),
&|t| {
as_folder(loadable_transform(
t.comments.clone(),
vec![
Signature {
name: "lazy".into(),
from: "my-custom-package".into(),
},
Signature {
name: "custom".into(),
from: "my-custom-package".into(),
},
Signature {
name: "default".into(),
from: "my-custom-package".into(),
},
],
))
},
&input,
&output,
Default::default(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import loadable from "@loadable/component";
loadable(({ foo }) => import(/* webpackChunkName: "Pages" */ `./${foo}`));
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable({
resolved: {},
chunkName ({ foo }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable(
(props) =>
import(/* webpackChunkName: "pages/[request]" */ `./pages/${props.path}`),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable({
resolved: {},
chunkName (props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import loadable from "@loadable/component";
loadable((props) => import(/* webpackChunkName: "Pages" */ `./${props.foo}`));
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable({
resolved: {},
chunkName (props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import loadable from "@loadable/component";
loadable((props) => import(`./dir/${props.foo}/test`));
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable({
resolved: {},
chunkName (props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import loadable from "@loadable/component";
loadable(({ foo }) => import(`./dir/${foo}/test`));
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable({
resolved: {},
chunkName ({ foo }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import loadable from "@loadable/component";
loadable((props) => import(`./${props.foo}`));
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadable from "@loadable/component";
loadable({
resolved: {},
chunkName (props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { lazy } from "@loadable/component";

lazy(() => import("./ModA"));
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { lazy } from "@loadable/component";

lazy({
resolved: {},
chunkName () {
return "ModA";
},
isReady (props) {
const key = this.resolve(props);
if (this.resolved[key] !== true) {
return false;
}
if (typeof __webpack_modules__ !== 'undefined') {
return !!__webpack_modules__[key];
}
return false;
},
importAsync: ()=>import(/*webpackChunkName: "ModA"*/ "./ModA"),
requireAsync (props) {
const key = this.resolve(props);
this.resolved[key] = false;
return this.importAsync(props).then((resolved)=>{
this.resolved[key] = true;
return resolved;
});
},
requireSync (props) {
const id = this.resolve(props);
if (typeof __webpack_require__ !== 'undefined') {
return __webpack_require__(id);
}
return eval('module.require')(id);
},
resolve () {
if (require.resolveWeak) {
return require.resolveWeak("./ModA");
}
return eval('require.resolve')("./ModA");
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { lazy } from "react";

lazy(() => import("./ModA"));
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { lazy } from "react";

lazy(() => import("./ModA"));
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { lazy as renamedLazy } from "@loadable/component";

renamedLazy(() => import("./ModA"));
Loading

0 comments on commit ab11cbf

Please sign in to comment.