Skip to content

Commit 5e6e201

Browse files
committed
✨ feat: enhance importmap handling to support absolute URLs
1 parent 06eb13e commit 5e6e201

File tree

16 files changed

+117
-75
lines changed

16 files changed

+117
-75
lines changed

packages/app/src/pluginManager.ts

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const untrustedDomains: string[] = [];
2222
export class PluginManager implements IPluginManager {
2323
readonly plugins = new Map<string, Plugin>();
2424
readonly manifests = new Map<string, PluginManifest>();
25+
readonly shouldRevokes = new Map<string, string[]>();
2526

2627
constructor(readonly app: IApplication) {}
2728

@@ -52,7 +53,7 @@ export class PluginManager implements IPluginManager {
5253
if (!url.endsWith("/")) url += "/";
5354
const manifest = await this.readManifestFromUrl(`${url}manifest.json`);
5455
if (manifest) {
55-
await this.loadPluginFromUrl(manifest.name, url, manifest.main, manifest.importMap);
56+
await this.loadPluginFromUrl(manifest.name, url, manifest.main, manifest.importmap);
5657
}
5758
}
5859
}
@@ -121,26 +122,29 @@ export class PluginManager implements IPluginManager {
121122
alert(`${manifest.main} not found in plugin archive`);
122123
return;
123124
}
124-
const code = await codeFile.async("text");
125-
const handlePluginIcon = async (plugin: Plugin) => {
126-
await this.transformZipCommandIcon(zip, plugin);
127-
};
128125

129-
const importMap = await this.getImportMapFromZip(zip, manifest);
130-
if (importMap) {
131-
this.injectImportMap(JSON.stringify(importMap));
132-
}
133-
await this.loadMainCode(manifest.name, code, handlePluginIcon);
134-
await this.loadCssFromZip(zip, manifest);
126+
const importmap = await this.getImportmapFromZip(zip, manifest);
127+
if (importmap) {
128+
this.injectImportmap(JSON.stringify(importmap));
135129

136-
if (importMap) {
137-
Object.values(importMap.imports).forEach((url) => {
138-
URL.revokeObjectURL(url);
139-
});
130+
this.shouldRevokes.set(manifest.name, Object.values(importmap.imports));
140131
}
132+
133+
const code = await codeFile.async("text");
134+
const blob = new Blob([code], { type: "application/javascript" });
135+
const blobUrl = URL.createObjectURL(blob);
136+
await Promise.try(async () => {
137+
const handlePluginIcon = async (plugin: Plugin) => {
138+
await this.transformZipCommandIcon(zip, plugin);
139+
};
140+
await this.loadMainCode(manifest.name, blobUrl, handlePluginIcon);
141+
await this.loadCssFromZip(zip, manifest);
142+
}).finally(() => {
143+
URL.revokeObjectURL(blobUrl);
144+
});
141145
}
142146

143-
private async loadPluginFromUrl(name: string, baseUrl: string, codePath: string, importMapPath?: string) {
147+
private async loadPluginFromUrl(name: string, baseUrl: string, codePath: string, importmapPath?: string) {
144148
if (codePath.startsWith("/")) codePath = codePath.substring(1);
145149

146150
const fullUrl = baseUrl + codePath;
@@ -149,52 +153,72 @@ export class PluginManager implements IPluginManager {
149153
return undefined;
150154
}
151155

152-
const code = await response.text();
153156
const handlePluginIcon = async (plugin: Plugin) => {
154157
await this.transformUrlCommandIcon(baseUrl, plugin);
155158
};
156159

157-
if (importMapPath) {
158-
await this.loadImportMapFromUrl(baseUrl, importMapPath);
160+
if (importmapPath) {
161+
await this.loadImportmapFromUrl(baseUrl, importmapPath);
159162
}
160163

161-
await this.loadMainCode(name, code, handlePluginIcon);
164+
await this.loadMainCode(name, fullUrl, handlePluginIcon);
162165
await this.loadCssFromUrl(baseUrl, name);
163166
}
164167

165-
private async loadImportMapFromUrl(baseUrl: string, importMapPath: string) {
166-
if (importMapPath.startsWith("/")) importMapPath = importMapPath.substring(1);
167-
const response = await fetch(baseUrl + importMapPath);
168+
private async loadImportmapFromUrl(baseUrl: string, importmapPath: string) {
169+
if (importmapPath.startsWith("/")) importmapPath = importmapPath.substring(1);
170+
const importmapUrl = baseUrl + importmapPath;
171+
const response = await fetch(importmapUrl);
168172
if (!response.ok) {
169173
return undefined;
170174
}
171175

172-
const json = await response.text();
173-
this.injectImportMap(json);
176+
const importmapObj = await response.json();
177+
const importmapBaseUrl = new URL(importmapPath, baseUrl).href;
178+
if (importmapObj.imports) {
179+
for (const key in importmapObj.imports) {
180+
const value = importmapObj.imports[key];
181+
if (!value.startsWith("http://") && !value.startsWith("https://")) {
182+
const absoluteUrl = new URL(value, importmapBaseUrl).href;
183+
importmapObj.imports[key] = absoluteUrl;
184+
}
185+
}
186+
}
187+
188+
if (importmapObj.scopes) {
189+
for (const scope in importmapObj.scopes) {
190+
const scopeBaseUrl = new URL(scope, importmapBaseUrl).href;
191+
for (const key in importmapObj.scopes[scope]) {
192+
const value = importmapObj.scopes[scope][key];
193+
if (!value.startsWith("http://") && !value.startsWith("https://")) {
194+
const absoluteUrl = new URL(value, scopeBaseUrl).href;
195+
importmapObj.scopes[scope][key] = absoluteUrl;
196+
}
197+
}
198+
}
199+
}
200+
201+
this.injectImportmap(JSON.stringify(importmapObj));
174202
}
175203

176204
private async loadMainCode(
177205
name: string,
178-
code: string,
206+
url: string,
179207
handlePluginIcon: (plugin: Plugin) => Promise<void>,
180208
) {
181-
const blob = new Blob([code], { type: "application/javascript" });
182-
const blobUrl = URL.createObjectURL(blob);
183209
await Promise.try(async () => {
184-
const module = await import(/*webpackIgnore: true*/ blobUrl);
210+
const module = await import(/*webpackIgnore: true*/ url);
185211
const plugin: Plugin = module.default;
186212
await handlePluginIcon(plugin);
187213
this.registerPlugin(plugin);
188214
this.plugins.set(name, plugin);
189215

190216
Logger.info(`Plugin ${name} loaded successfully`);
191-
})
192-
.catch((err) => {
193-
alert(`Failed to load plugin ${name}: ${err}`);
194-
})
195-
.finally(() => {
196-
URL.revokeObjectURL(blobUrl);
197-
});
217+
}).catch((err) => {
218+
console.log(err);
219+
220+
alert(`Failed to load plugin ${name}: ${err}`);
221+
});
198222
}
199223

200224
private async transformZipCommandIcon(zip: JSZip, plugin: Plugin) {
@@ -233,6 +257,10 @@ export class PluginManager implements IPluginManager {
233257
await this.unregisterPlugin(pluginName, plugin);
234258
this.plugins.delete(pluginName);
235259

260+
this.shouldRevokes.get(pluginName)?.forEach((value) => {
261+
URL.revokeObjectURL(value);
262+
});
263+
236264
Logger.info(`Plugin ${pluginName} unloaded successfully`);
237265
}
238266

@@ -409,20 +437,20 @@ export class PluginManager implements IPluginManager {
409437
}
410438
}
411439

412-
private async getImportMapFromZip(
440+
private async getImportmapFromZip(
413441
zip: JSZip,
414442
manifest: PluginManifest,
415443
): Promise<{ imports: Record<string, string> } | undefined> {
416-
if (!manifest.importMap) return undefined;
444+
if (!manifest.importmap) return undefined;
417445

418-
const codeFile = zip.file(manifest.importMap);
446+
const codeFile = zip.file(manifest.importmap);
419447
if (!codeFile) {
420448
alert(`${manifest.main} not found in plugin archive`);
421449
return undefined;
422450
}
423451

424-
const importMap = await codeFile.async("text");
425-
const json = JSON.parse(importMap);
452+
const importmap = await codeFile.async("text");
453+
const json = JSON.parse(importmap);
426454
for (const key in json.imports) {
427455
const importFile = zip.file(json.imports[key]);
428456
if (!importFile) {
@@ -438,10 +466,10 @@ export class PluginManager implements IPluginManager {
438466
return json;
439467
}
440468

441-
private injectImportMap(importMapJson: string) {
469+
private injectImportmap(importmapJson: string) {
442470
const script = document.createElement("script");
443471
script.type = "importmap";
444-
script.textContent = importMapJson;
472+
script.textContent = importmapJson;
445473
document.head.appendChild(script);
446474
}
447475

packages/core/src/plugin/manifest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@ export type PluginManifest = {
5252
css?: string | string[];
5353

5454
/** Import map for module resolution, see https://developer.mozilla.org/zh-CN/docs/Web/HTML/Reference/Elements/script/type/importmap */
55-
importMap?: string;
55+
importmap?: string;
5656
};

plugins/helloworld-js/README.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,4 @@ Drag and drop the .chiliplugin file into Chili3D.
3737

3838
# Demo Plugin for Chili3D
3939

40-
Change the importMap.json file to point to plugins server(http://localhost:88686), e.g.:
41-
```json
42-
{
43-
"imports": {
44-
"module1": "http://localhost:8686/src/module1.js",
45-
"module2": "http://localhost:8686/src/module2.js"
46-
}
47-
}
48-
```
49-
5040
To run the plugin, you need to start the chili3d server: at the root of the chili3d folder, run: `npm run start`. Then, start the plugin server: at the root of the plugin folder, run: `npm run preview`. The plugin server will start on port 8686. The plugin will be available at http://localhost:8080?plugin=http://localhost:8686.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"imports": {
33
"module1": "src/module1.js",
4-
"module2": "src/module2.js"
4+
"./module2": "src/module2.js"
55
}
66
}

plugins/helloworld-js/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"engines": {
1010
"chili3d": ">=0.6"
1111
},
12-
"importMap": "importMap.json"
12+
"importmap": "importmap.json"
1313
}

plugins/helloworld-js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "module",
77
"scripts": {
88
"preview": "echo 'Debug URL: http://localhost:8080?plugin=http://localhost:8686' && npx servitsy -p 8686 --cors",
9-
"package": "node ../scripts/package-plugin.mjs dist/helloworld-js.chiliplugin src manifest.json importMap.json icons"
9+
"package": "node ../scripts/package-plugin.mjs dist/helloworld-js.chiliplugin src manifest.json importmap.json icons"
1010
},
1111
"keywords": [
1212
"chili3d",

plugins/helloworld-js/src/extension.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { module1_function1 } from "module1";
2-
import { module2_function1 } from "module2";
32

43
const { CommandStore, PubSub } = Chili3dCore;
54

65
class HelloWorldJSCommand {
7-
execute(app) {
6+
async execute(app) {
87
PubSub.default.pub("showToast", "demo.hello.message");
98
module1_function1();
10-
module2_function1();
9+
10+
const module2 = await import("./module2.js");
11+
module2.module2_function1();
12+
1113
return Promise.resolve();
1214
}
1315
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"imports": {
3+
"module1": "dist/module1.js"
4+
}
5+
}

plugins/helloworld-ts/manifest.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
"name": "Demo Plugin",
33
"version": "1.0.0",
44
"description": "A demo plugin for Chili3D showing plugin system capabilities",
5-
"main": "dist/extension.js",
5+
"main": "dist/main.js",
66
"author": {
77
"name": "Chili3D Team"
88
},
99
"engines": {
1010
"chili3d": ">=0.6"
11-
}
11+
},
12+
"importmap": "importmap.json"
1213
}

plugins/helloworld-ts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"type": "module",
77
"scripts": {
88
"build": "rspack build",
9-
"preview": "npm run build && npx servitsy -p 8686 --cors & (sleep 2 && open http://localhost:8080?plugin=http://localhost:8686)",
10-
"package": "npm run build && node ../scripts/package-plugin.mjs dist/helloworld-ts.chiliplugin manifest.json dist/extension.js icons"
9+
"preview": "npm run build && echo 'Debug URL: http://localhost:8080?plugin=http://localhost:8686' && npx servitsy -p 8686 --cors",
10+
"package": "npm run build && node ../scripts/package-plugin.mjs dist/helloworld-ts.chiliplugin manifest.json importmap.json dist icons"
1111
},
1212
"devDependencies": {
1313
"@chili3d/core": "*",

0 commit comments

Comments
 (0)