Skip to content

Commit 05fc137

Browse files
fix(loadable-components): Support aliased loadable imports
Make the 'from' field in Signature optional to allow matching any import with a specific name regardless of source package. This enables support for vendored or aliased loadable implementations. - Made Signature.from an Option<Wtf8Atom> - Updated visit_mut_import_decl to skip source check when from is None - Added test case for vendored loadable imports - Updated documentation with usage examples Fixes #572 Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
1 parent 15f32da commit 05fc137

File tree

6 files changed

+163
-30
lines changed

6 files changed

+163
-30
lines changed

packages/loadable-components/README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,40 @@
88

99
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.
1010
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.
11+
12+
### Custom signatures with specific package sources
13+
1114
```json
1215
["loadable-components", { "signatures": [
1316
{
1417
"from": "myLoadableWrapper",
15-
"name": "default"
18+
"name": "default"
1619
},
1720
{
1821
"from": "myLoadableWrapper",
19-
"name": "lazy"
22+
"name": "lazy"
2023
}]
2124
}]
2225
```
2326

27+
### Custom signatures without package source (matches any import)
28+
29+
To match any import with a specific name regardless of the source package (useful for vendored or aliased packages), omit the `from` field:
30+
31+
```json
32+
["loadable-components", { "signatures": [
33+
{
34+
"name": "loadable"
35+
}]
36+
}]
37+
```
38+
39+
This will transform any default import named `loadable`, regardless of where it's imported from:
40+
```js
41+
import loadable from 'my-vendored-loadable'; // will be transformed
42+
import loadable from '@loadable/component'; // will be transformed
43+
```
44+
2445
# @swc/plugin-loadable-components
2546

2647
## 11.3.0

packages/loadable-components/README.tmpl.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,38 @@
88

99
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.
1010
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.
11+
12+
### Custom signatures with specific package sources
13+
1114
```json
1215
["loadable-components", { "signatures": [
1316
{
1417
"from": "myLoadableWrapper",
15-
"name": "default"
18+
"name": "default"
1619
},
1720
{
1821
"from": "myLoadableWrapper",
19-
"name": "lazy"
22+
"name": "lazy"
2023
}]
2124
}]
2225
```
2326

27+
### Custom signatures without package source (matches any import)
28+
29+
To match any import with a specific name regardless of the source package (useful for vendored or aliased packages), omit the `from` field:
30+
31+
```json
32+
["loadable-components", { "signatures": [
33+
{
34+
"name": "loadable"
35+
}]
36+
}]
37+
```
38+
39+
This will transform any default import named `loadable`, regardless of where it's imported from:
40+
```js
41+
import loadable from 'my-vendored-loadable'; // will be transformed
42+
import loadable from '@loadable/component'; // will be transformed
43+
```
44+
2445
${CHANGELOG}

packages/loadable-components/src/lib.rs

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -636,29 +636,34 @@ where
636636
{
637637
fn visit_mut_import_decl(&mut self, import_decl: &mut ImportDecl) {
638638
for signature in self.signatures.iter() {
639-
if signature.from == *import_decl.src.value {
640-
for specifier in import_decl.specifiers.iter() {
641-
match specifier {
642-
ImportSpecifier::Default(default_spec) => {
643-
if signature.is_default_specifier() {
644-
self.specifiers.insert(default_spec.local.sym.clone());
645-
}
639+
// Skip source check if from is None (match any source)
640+
if let Some(ref from) = signature.from {
641+
if from != &*import_decl.src.value {
642+
continue;
643+
}
644+
}
645+
646+
for specifier in import_decl.specifiers.iter() {
647+
match specifier {
648+
ImportSpecifier::Default(default_spec) => {
649+
if signature.is_default_specifier() {
650+
self.specifiers.insert(default_spec.local.sym.clone());
646651
}
647-
ImportSpecifier::Named(named_specifier) => {
648-
if let Some(ModuleExportName::Ident(imported)) =
649-
&named_specifier.imported
650-
{
651-
if imported.sym == signature.name {
652-
self.specifiers.insert(named_specifier.local.sym.clone());
653-
return;
654-
}
655-
}
656-
if named_specifier.local.sym == signature.name {
652+
}
653+
ImportSpecifier::Named(named_specifier) => {
654+
if let Some(ModuleExportName::Ident(imported)) =
655+
&named_specifier.imported
656+
{
657+
if imported.sym == signature.name {
657658
self.specifiers.insert(named_specifier.local.sym.clone());
659+
return;
658660
}
659661
}
660-
_ => (),
662+
if named_specifier.local.sym == signature.name {
663+
self.specifiers.insert(named_specifier.local.sym.clone());
664+
}
661665
}
666+
_ => (),
662667
}
663668
}
664669
}
@@ -776,14 +781,15 @@ fn clone_params(e: &Expr) -> Vec<Param> {
776781
#[derive(Debug, Clone, Deserialize, PartialEq)]
777782
pub struct Signature {
778783
pub name: Atom,
779-
pub from: Wtf8Atom,
784+
#[serde(default)]
785+
pub from: Option<Wtf8Atom>,
780786
}
781787

782788
impl Default for Signature {
783789
fn default() -> Self {
784790
Signature {
785791
name: "default".into(),
786-
from: "@loadable/component".into(),
792+
from: Some("@loadable/component".into()),
787793
}
788794
}
789795
}
@@ -796,7 +802,7 @@ impl Signature {
796802
pub fn default_lazy() -> Self {
797803
Signature {
798804
name: "lazy".into(),
799-
from: "@loadable/component".into(),
805+
from: Some("@loadable/component".into()),
800806
}
801807
}
802808
}
@@ -836,17 +842,37 @@ mod tests {
836842
"signatures": [
837843
{
838844
"from": "myLoadableWrapper",
839-
"name": "lazy"
845+
"name": "lazy"
840846
}
841847
]
842848
}"#,
843849
);
844850
assert_eq!(
845851
config.signatures,
846852
vec![Signature {
847-
from: "myLoadableWrapper".into(),
853+
from: Some("myLoadableWrapper".into()),
848854
name: "lazy".into()
849855
}]
850856
)
851857
}
858+
859+
#[test]
860+
fn should_support_signatures_without_from() {
861+
let config = get_config(
862+
r#"{
863+
"signatures": [
864+
{
865+
"name": "loadable"
866+
}
867+
]
868+
}"#,
869+
);
870+
assert_eq!(
871+
config.signatures,
872+
vec![Signature {
873+
from: None,
874+
name: "loadable".into()
875+
}]
876+
)
877+
}
852878
}

packages/loadable-components/tests/fixture.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,38 @@ fn fixture_custom_signatures(input: PathBuf) {
3838
vec![
3939
Signature {
4040
name: "lazy".into(),
41-
from: "my-custom-package".into(),
41+
from: Some("my-custom-package".into()),
4242
},
4343
Signature {
4444
name: "custom".into(),
45-
from: "my-custom-package".into(),
45+
from: Some("my-custom-package".into()),
4646
},
4747
Signature {
4848
name: "default".into(),
49-
from: "my-custom-package".into(),
49+
from: Some("my-custom-package".into()),
50+
},
51+
],
52+
))
53+
},
54+
&input,
55+
&output,
56+
Default::default(),
57+
);
58+
}
59+
60+
#[testing::fixture("tests/fixture/aliased import/**/input.js")]
61+
fn fixture_aliased_import(input: PathBuf) {
62+
let output = input.parent().unwrap().join("output.js");
63+
64+
test_fixture(
65+
Default::default(),
66+
&|t| {
67+
visit_mut_pass(loadable_transform(
68+
t.comments.clone(),
69+
vec![
70+
Signature {
71+
name: "default".into(),
72+
from: None,
5073
},
5174
],
5275
))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import loadable from 'my-vendored-loadable';
2+
3+
loadable(() => import('./SomeComponent'));
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import loadable from 'my-vendored-loadable';
2+
loadable({
3+
resolved: {},
4+
chunkName () {
5+
return "SomeComponent";
6+
},
7+
isReady (props) {
8+
const key = this.resolve(props);
9+
if (this.resolved[key] !== true) {
10+
return false;
11+
}
12+
if (typeof __webpack_modules__ !== 'undefined') {
13+
return !!__webpack_modules__[key];
14+
}
15+
return false;
16+
},
17+
importAsync: ()=>import(/*webpackChunkName: "SomeComponent"*/ './SomeComponent'),
18+
requireAsync (props) {
19+
const key = this.resolve(props);
20+
this.resolved[key] = false;
21+
return this.importAsync(props).then((resolved)=>{
22+
this.resolved[key] = true;
23+
return resolved;
24+
});
25+
},
26+
requireSync (props) {
27+
const id = this.resolve(props);
28+
if (typeof __webpack_require__ !== 'undefined') {
29+
return __webpack_require__(id);
30+
}
31+
return eval('module.require')(id);
32+
},
33+
resolve () {
34+
if (require.resolveWeak) {
35+
return require.resolveWeak('./SomeComponent');
36+
}
37+
return eval('require.resolve')('./SomeComponent');
38+
}
39+
});

0 commit comments

Comments
 (0)