Skip to content

Commit e327ade

Browse files
committed
fix: resolve resource loading issues in packaged desktop app
- replace inline script WASM loading with external loader to fix CSP - load esbuild.wasm locally instead of from CDN - add wasm-unsafe-eval, unsafe-eval, blob: to CSP
1 parent b9bb6bc commit e327ade

9 files changed

Lines changed: 36 additions & 34 deletions

File tree

.github/workflows/release-desktop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ jobs:
4747
- name: Install dependencies
4848
run: pnpm install
4949

50+
- name: Copy esbuild.wasm to desktop public
51+
shell: bash
52+
run: cp node_modules/.pnpm/esbuild-wasm@0.27.2/node_modules/esbuild-wasm/esbuild.wasm desktop/public/esbuild.wasm
53+
5054
- name: Build engine and sync to desktop
5155
run: node build-tools/cli.js build -t all
5256

desktop/public/wasm/wasm-loader.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import createModule from './esengine.js';
2+
window.__ESEngineModule = createModule;
3+
window.dispatchEvent(new Event('esengine-loaded'));

desktop/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
}
2424
],
2525
"security": {
26-
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost data:; style-src 'self' 'unsafe-inline'",
26+
"csp": "default-src 'self' ipc: http://ipc.localhost; script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval' blob:; img-src 'self' asset: http://asset.localhost data:; style-src 'self' 'unsafe-inline'",
2727
"assetProtocol": {
2828
"enable": true,
2929
"scope": ["*/**"]

desktop/src/main.ts

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,28 @@ import type { App, ESEngineModule } from 'esengine';
1313
let currentLauncher: ProjectLauncher | null = null;
1414
let wasmModule: ESEngineModule | null = null;
1515

16-
function loadScript(src: string): Promise<void> {
17-
return new Promise((resolve) => {
16+
function loadWasmScript(): Promise<void> {
17+
return new Promise((resolve, reject) => {
18+
if (window.__ESEngineModule) {
19+
resolve();
20+
return;
21+
}
22+
1823
const script = document.createElement('script');
1924
script.type = 'module';
20-
script.innerHTML = `
21-
import createModule from '${src}';
22-
window.__ESEngineModule = createModule;
23-
window.dispatchEvent(new Event('esengine-loaded'));
24-
`;
25+
script.src = '/wasm/wasm-loader.js';
26+
script.onerror = () => reject(new Error('Failed to load WASM loader script'));
2527
document.head.appendChild(script);
26-
resolve();
28+
29+
window.addEventListener('esengine-loaded', () => resolve(), { once: true });
2730
});
2831
}
2932

3033
async function loadWasmModule(): Promise<ESEngineModule | null> {
3134
if (wasmModule) return wasmModule;
3235

3336
try {
34-
await loadScript('/wasm/esengine.js');
35-
36-
await new Promise<void>((resolve, reject) => {
37-
if (window.__ESEngineModule) {
38-
resolve();
39-
return;
40-
}
41-
42-
const timeout = setTimeout(() => {
43-
reject(new Error('WASM module load timeout'));
44-
}, 10000);
45-
46-
window.addEventListener('esengine-loaded', () => {
47-
clearTimeout(timeout);
48-
resolve();
49-
}, { once: true });
50-
});
37+
await loadWasmScript();
5138

5239
const createModule = window.__ESEngineModule;
5340
if (typeof createModule !== 'function') {
@@ -92,6 +79,7 @@ async function init(): Promise<void> {
9279
fs: nativeFS,
9380
invoke,
9481
shell: nativeShell,
82+
esbuildWasmURL: '/esbuild.wasm',
9583
});
9684

9785
const container = document.getElementById('editor-root');

editor/src/builder/PlayableBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as esbuild from 'esbuild-wasm/esm/browser';
1515
import type { BuildResult, BuildContext } from './BuildService';
1616
import { BuildProgressReporter } from './BuildProgress';
1717
import { BuildCache, type BuildCacheData } from './BuildCache';
18-
import { getEditorContext } from '../context/EditorContext';
18+
import { getEditorContext, getEsbuildWasmURL } from '../context/EditorContext';
1919
import { findTsFiles, EDITOR_ONLY_DIRS } from '../scripting/ScriptLoader';
2020
import { BuildAssetCollector, AssetExportConfigService } from './AssetCollector';
2121
import { TextureAtlasPacker, type AtlasResult } from './TextureAtlas';
@@ -548,7 +548,7 @@ ${imports}
548548
private async initializeEsbuild(): Promise<void> {
549549
try {
550550
await esbuild.initialize({
551-
wasmURL: 'https://cdn.jsdelivr.net/npm/esbuild-wasm@0.27.2/esbuild.wasm',
551+
wasmURL: getEsbuildWasmURL(),
552552
});
553553
} catch (err) {
554554
if (!String(err).includes('Cannot call "initialize" more than once')) {

editor/src/builder/WeChatBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as esbuild from 'esbuild-wasm/esm/browser';
77
import type { BuildResult, BuildContext } from './BuildService';
88
import { BuildProgressReporter } from './BuildProgress';
99
import { BuildCache } from './BuildCache';
10-
import { getEditorContext } from '../context/EditorContext';
10+
import { getEditorContext, getEsbuildWasmURL } from '../context/EditorContext';
1111
import { findTsFiles, EDITOR_ONLY_DIRS } from '../scripting/ScriptLoader';
1212
import { BuildAssetCollector, AssetExportConfigService } from './AssetCollector';
1313
import { TextureAtlasPacker, type AtlasResult } from './TextureAtlas';
@@ -518,7 +518,7 @@ function updateMaterials(world, sceneData, materialCache, entityMap) {
518518

519519
try {
520520
await esbuild.initialize({
521-
wasmURL: 'https://cdn.jsdelivr.net/npm/esbuild-wasm@0.27.2/esbuild.wasm',
521+
wasmURL: getEsbuildWasmURL(),
522522
});
523523
} catch (err) {
524524
if (!String(err).includes('Cannot call "initialize" more than once')) {

editor/src/context/EditorContext.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface EditorContextConfig {
1616
fs?: NativeFS;
1717
invoke?: (cmd: string, args?: Record<string, unknown>) => Promise<unknown>;
1818
shell?: NativeShell;
19+
esbuildWasmURL?: string;
1920
}
2021

2122
let ctx: EditorContextConfig = {};
@@ -36,3 +37,9 @@ export function setEditorInstance(editor: Editor): void {
3637
export function getEditorInstance(): Editor | null {
3738
return editorInstance;
3839
}
40+
41+
const ESBUILD_WASM_CDN = 'https://cdn.jsdelivr.net/npm/esbuild-wasm@0.27.2/esbuild.wasm';
42+
43+
export function getEsbuildWasmURL(): string {
44+
return ctx.esbuildWasmURL ?? ESBUILD_WASM_CDN;
45+
}

editor/src/extension/ExtensionLoader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as esbuild from 'esbuild-wasm/esm/browser';
77
import { editorShimPlugin, esengineShimPlugin, virtualFsPlugin } from '../scripting/esbuildPlugins';
88
import { findTsFiles } from '../scripting/ScriptLoader';
99
import type { NativeFS, CompileError } from '../scripting/types';
10-
import { getEditorContext } from '../context/EditorContext';
10+
import { getEditorContext, getEsbuildWasmURL } from '../context/EditorContext';
1111
import { ExtensionContext } from './ExtensionContext';
1212
import { setEditorAPI } from './editorAPI';
1313
import { normalizePath, joinPath, getProjectDir } from '../utils/path';
@@ -53,7 +53,7 @@ export class ExtensionLoader {
5353

5454
try {
5555
await esbuild.initialize({
56-
wasmURL: 'https://cdn.jsdelivr.net/npm/esbuild-wasm@0.27.2/esbuild.wasm',
56+
wasmURL: getEsbuildWasmURL(),
5757
});
5858
} catch {
5959
// esbuild is a singleton; already initialized by ScriptLoader is fine

editor/src/scripting/ScriptLoader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { defineComponent, defineTag, clearUserComponents } from 'esengine';
88
import { virtualFsPlugin } from './esbuildPlugins';
99
import type { NativeFS, ScriptLoaderOptions, CompileError } from './types';
1010
import { clearScriptComponents } from '../schemas/ComponentSchemas';
11-
import { getEditorContext } from '../context/EditorContext';
11+
import { getEditorContext, getEsbuildWasmURL } from '../context/EditorContext';
1212
import { normalizePath, joinPath, getProjectDir } from '../utils/path';
1313

1414
// =============================================================================
@@ -44,7 +44,7 @@ export class ScriptLoader {
4444

4545
try {
4646
await esbuild.initialize({
47-
wasmURL: 'https://cdn.jsdelivr.net/npm/esbuild-wasm@0.27.2/esbuild.wasm',
47+
wasmURL: getEsbuildWasmURL(),
4848
});
4949
} catch {
5050
// esbuild is a singleton; already initialized is fine

0 commit comments

Comments
 (0)