diff --git a/packages/loadable-components/README.md b/packages/loadable-components/README.md
index 16a483ad2..585682178 100644
--- a/packages/loadable-components/README.md
+++ b/packages/loadable-components/README.md
@@ -8,19 +8,40 @@
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.
+
+### Custom signatures with specific package sources
+
```json
["loadable-components", { "signatures": [
{
"from": "myLoadableWrapper",
- "name": "default"
+ "name": "default"
},
{
"from": "myLoadableWrapper",
- "name": "lazy"
+ "name": "lazy"
}]
}]
```
+### Custom signatures without package source (matches any import)
+
+To match any import with a specific name regardless of the source package (useful for vendored or aliased packages), omit the `from` field:
+
+```json
+["loadable-components", { "signatures": [
+ {
+ "name": "loadable"
+ }]
+}]
+```
+
+This will transform any default import named `loadable`, regardless of where it's imported from:
+```js
+import loadable from 'my-vendored-loadable'; // will be transformed
+import loadable from '@loadable/component'; // will be transformed
+```
+
# @swc/plugin-loadable-components
## 11.3.0
diff --git a/packages/loadable-components/README.tmpl.md b/packages/loadable-components/README.tmpl.md
index 7f65b61e8..72282e037 100644
--- a/packages/loadable-components/README.tmpl.md
+++ b/packages/loadable-components/README.tmpl.md
@@ -8,17 +8,38 @@
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.
+
+### Custom signatures with specific package sources
+
```json
["loadable-components", { "signatures": [
{
"from": "myLoadableWrapper",
- "name": "default"
+ "name": "default"
},
{
"from": "myLoadableWrapper",
- "name": "lazy"
+ "name": "lazy"
}]
}]
```
+### Custom signatures without package source (matches any import)
+
+To match any import with a specific name regardless of the source package (useful for vendored or aliased packages), omit the `from` field:
+
+```json
+["loadable-components", { "signatures": [
+ {
+ "name": "loadable"
+ }]
+}]
+```
+
+This will transform any default import named `loadable`, regardless of where it's imported from:
+```js
+import loadable from 'my-vendored-loadable'; // will be transformed
+import loadable from '@loadable/component'; // will be transformed
+```
+
${CHANGELOG}
diff --git a/packages/loadable-components/src/lib.rs b/packages/loadable-components/src/lib.rs
index 234e1d8e5..636067d96 100644
--- a/packages/loadable-components/src/lib.rs
+++ b/packages/loadable-components/src/lib.rs
@@ -636,29 +636,34 @@ where
{
fn visit_mut_import_decl(&mut self, import_decl: &mut ImportDecl) {
for signature in self.signatures.iter() {
- if 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.clone());
- }
+ // Skip source check if from is None (match any source)
+ if let Some(ref from) = signature.from {
+ if from != &*import_decl.src.value {
+ continue;
+ }
+ }
+
+ 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.clone());
}
- ImportSpecifier::Named(named_specifier) => {
- if let Some(ModuleExportName::Ident(imported)) =
- &named_specifier.imported
- {
- if imported.sym == signature.name {
- self.specifiers.insert(named_specifier.local.sym.clone());
- return;
- }
- }
- if named_specifier.local.sym == signature.name {
+ }
+ ImportSpecifier::Named(named_specifier) => {
+ if let Some(ModuleExportName::Ident(imported)) =
+ &named_specifier.imported
+ {
+ if imported.sym == signature.name {
self.specifiers.insert(named_specifier.local.sym.clone());
+ return;
}
}
- _ => (),
+ if named_specifier.local.sym == signature.name {
+ self.specifiers.insert(named_specifier.local.sym.clone());
+ }
}
+ _ => (),
}
}
}
@@ -776,14 +781,15 @@ fn clone_params(e: &Expr) -> Vec {
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Signature {
pub name: Atom,
- pub from: Wtf8Atom,
+ #[serde(default)]
+ pub from: Option,
}
impl Default for Signature {
fn default() -> Self {
Signature {
name: "default".into(),
- from: "@loadable/component".into(),
+ from: Some("@loadable/component".into()),
}
}
}
@@ -796,7 +802,7 @@ impl Signature {
pub fn default_lazy() -> Self {
Signature {
name: "lazy".into(),
- from: "@loadable/component".into(),
+ from: Some("@loadable/component".into()),
}
}
}
@@ -836,7 +842,7 @@ mod tests {
"signatures": [
{
"from": "myLoadableWrapper",
- "name": "lazy"
+ "name": "lazy"
}
]
}"#,
@@ -844,9 +850,29 @@ mod tests {
assert_eq!(
config.signatures,
vec![Signature {
- from: "myLoadableWrapper".into(),
+ from: Some("myLoadableWrapper".into()),
name: "lazy".into()
}]
)
}
+
+ #[test]
+ fn should_support_signatures_without_from() {
+ let config = get_config(
+ r#"{
+ "signatures": [
+ {
+ "name": "loadable"
+ }
+ ]
+ }"#,
+ );
+ assert_eq!(
+ config.signatures,
+ vec![Signature {
+ from: None,
+ name: "loadable".into()
+ }]
+ )
+ }
}
diff --git a/packages/loadable-components/tests/fixture.rs b/packages/loadable-components/tests/fixture.rs
index 20199aea4..68e4a1a56 100644
--- a/packages/loadable-components/tests/fixture.rs
+++ b/packages/loadable-components/tests/fixture.rs
@@ -38,15 +38,38 @@ fn fixture_custom_signatures(input: PathBuf) {
vec![
Signature {
name: "lazy".into(),
- from: "my-custom-package".into(),
+ from: Some("my-custom-package".into()),
},
Signature {
name: "custom".into(),
- from: "my-custom-package".into(),
+ from: Some("my-custom-package".into()),
},
Signature {
name: "default".into(),
- from: "my-custom-package".into(),
+ from: Some("my-custom-package".into()),
+ },
+ ],
+ ))
+ },
+ &input,
+ &output,
+ Default::default(),
+ );
+}
+
+#[testing::fixture("tests/fixture/aliased import/**/input.js")]
+fn fixture_aliased_import(input: PathBuf) {
+ let output = input.parent().unwrap().join("output.js");
+
+ test_fixture(
+ Default::default(),
+ &|t| {
+ visit_mut_pass(loadable_transform(
+ t.comments.clone(),
+ vec![
+ Signature {
+ name: "default".into(),
+ from: None,
},
],
))
diff --git a/packages/loadable-components/tests/fixture/aliased import/should work with vendored loadable/input.js b/packages/loadable-components/tests/fixture/aliased import/should work with vendored loadable/input.js
new file mode 100644
index 000000000..a1842a6ae
--- /dev/null
+++ b/packages/loadable-components/tests/fixture/aliased import/should work with vendored loadable/input.js
@@ -0,0 +1,3 @@
+import loadable from 'my-vendored-loadable';
+
+loadable(() => import('./SomeComponent'));
diff --git a/packages/loadable-components/tests/fixture/aliased import/should work with vendored loadable/output.js b/packages/loadable-components/tests/fixture/aliased import/should work with vendored loadable/output.js
new file mode 100644
index 000000000..2d0605fe9
--- /dev/null
+++ b/packages/loadable-components/tests/fixture/aliased import/should work with vendored loadable/output.js
@@ -0,0 +1,39 @@
+import loadable from 'my-vendored-loadable';
+loadable({
+ resolved: {},
+ chunkName () {
+ return "SomeComponent";
+ },
+ 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: "SomeComponent"*/ './SomeComponent'),
+ 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('./SomeComponent');
+ }
+ return eval('require.resolve')('./SomeComponent');
+ }
+});