Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ScriptLoader #2417

Open
wants to merge 18 commits into
base: dev/1.4
Choose a base branch
from
4 changes: 3 additions & 1 deletion packages/core/src/asset/AssetType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,7 @@ export enum AssetType {
/** Source Font, include ttf、 otf and woff. */
SourceFont = "SourceFont",
/** Project asset. */
Project = "project"
Project = "project",
/** Script in ES module */
Script = "Script"
}
15 changes: 15 additions & 0 deletions packages/loader/src/ScriptLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AssetPromise, AssetType, Loader, LoadItem, resourceLoader, Script } from "@galacean/engine-core";

@resourceLoader(AssetType.Script, ["js", "mjs"], false)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider adding input validation for script URLs

The loader handles both .js and .mjs files, but there's no URL validation before dynamic importing. This could pose security risks if untrusted URLs are processed.

Consider adding URL validation before processing:

+ import { isValidUrl } from "@galacean/engine-core";
 import { AssetPromise, AssetType, Loader, LoadItem, resourceLoader, Script } from "@galacean/engine-core";

 @resourceLoader(AssetType.Script, ["js", "mjs"], false)

Committable suggestion was skipped due to low confidence.

class ScriptLoader extends Loader<Script> {
load(item: LoadItem): AssetPromise<Script> {
return new AssetPromise((resolve, reject) => {
const { url } = item;
(import(url) as Promise<Script>)
.then((esModule) => {
resolve(esModule);
})
.catch(reject);
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance browser compatibility and error handling

The current implementation has several areas for improvement:

  1. No polyfill for dynamic import in older browsers
  2. Basic error handling without specific error types
  3. No cleanup mechanism for failed loads

Consider implementing these improvements:

 class ScriptLoader extends Loader<Script> {
+  private loadingScripts: Map<string, Promise<Script>> = new Map();
+
   load(item: LoadItem): AssetPromise<Script> {
     return new AssetPromise((resolve, reject) => {
       const { url } = item;
-      (import(url) as Promise<Script>)
-        .then((esModule) => {
-          resolve(esModule);
-        })
-        .catch(reject);
+      
+      // Reuse existing load promise if available
+      const existing = this.loadingScripts.get(url);
+      if (existing) {
+        return existing.then(resolve).catch(reject);
+      }
+
+      const loadPromise = (async () => {
+        try {
+          // Use polyfill for dynamic import
+          const moduleLoader = await import('dynamic-import-polyfill');
+          const esModule = await moduleLoader.load(url);
+          return esModule;
+        } catch (error) {
+          throw new Error(`Failed to load script ${url}: ${error.message}`);
+        } finally {
+          this.loadingScripts.delete(url);
+        }
+      })();
+
+      this.loadingScripts.set(url, loadPromise);
+      loadPromise.then(resolve).catch(reject);
     });
   }
+
+  // Cleanup method for resource management
+  destroy(): void {
+    this.loadingScripts.clear();
+    super.destroy?.();
+  }
 }

Committable suggestion was skipped due to low confidence.

}
Loading