Skip to content

Commit f717e34

Browse files
committed
Adopt multi-module compose wasm compilation
1 parent 645e75c commit f717e34

File tree

6 files changed

+52
-170
lines changed

6 files changed

+52
-170
lines changed

examples.md

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -144,45 +144,48 @@ You can use Compose Wasm.
144144
<div class="kotlin-code" data-target-platform="compose-wasm">
145145

146146
```kotlin
147-
import androidx.compose.ui.ExperimentalComposeUiApi
148-
import androidx.compose.ui.window.CanvasBasedWindow
149147
import androidx.compose.animation.AnimatedVisibility
150-
import androidx.compose.foundation.Image
151148
import androidx.compose.foundation.layout.Column
149+
import androidx.compose.foundation.layout.fillMaxSize
152150
import androidx.compose.foundation.layout.fillMaxWidth
153151
import androidx.compose.material.Button
154152
import androidx.compose.material.MaterialTheme
155153
import androidx.compose.material.Text
156-
import androidx.compose.runtime.Composable
157-
import androidx.compose.runtime.getValue
158-
import androidx.compose.runtime.mutableStateOf
159-
import androidx.compose.runtime.remember
160-
import androidx.compose.runtime.setValue
154+
import androidx.compose.runtime.*
161155
import androidx.compose.ui.Alignment
156+
import androidx.compose.ui.ExperimentalComposeUiApi
162157
import androidx.compose.ui.Modifier
158+
import androidx.compose.ui.window.ComposeViewport
159+
import kotlinx.browser.document
163160

164161
//sampleStart
165162
@OptIn(ExperimentalComposeUiApi::class)
166163
fun main() {
167-
CanvasBasedWindow { App() }
164+
ComposeViewport(viewportContainer = document.body!!, content = {
165+
App()
166+
})
168167
}
169168

170169
@Composable
171170
fun App() {
172171
MaterialTheme {
173-
var greetingText by remember { mutableStateOf("Hello World!") }
174-
var showImage by remember { mutableStateOf(false) }
175-
var counter by remember { mutableStateOf(0) }
176-
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
177-
Button(onClick = {
178-
counter++
179-
greetingText = "Compose: ${Greeting().greet()}"
180-
showImage = !showImage
181-
}) {
182-
Text(greetingText)
172+
var showContent by remember { mutableStateOf(false) }
173+
Column(
174+
modifier = Modifier
175+
.fillMaxSize(),
176+
horizontalAlignment = Alignment.CenterHorizontally,
177+
) {
178+
Button(onClick = { showContent = !showContent }) {
179+
Text("Click me!")
183180
}
184-
AnimatedVisibility(showImage) {
185-
Text(counter.toString())
181+
AnimatedVisibility(showContent) {
182+
val greeting = remember { Greeting().greet() }
183+
Column(
184+
modifier = Modifier.fillMaxWidth(),
185+
horizontalAlignment = Alignment.CenterHorizontally,
186+
) {
187+
Text("Compose: $greeting")
188+
}
186189
}
187190
}
188191
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"copy-examples": "node utils/copy-examples",
8181
"release:ci": "rm -rf dist && npm run build:all && $NPM_TOKEN=%env.NPM_TOKEN% npm publish",
8282
"start": "webpack-dev-server --port 9002",
83-
"start-with-local-compiler": "webpack-dev-server --port 9002 --env webDemoUrl='//localhost:8080' webDemoResourcesUrl='//localhost:8081'",
83+
"start-with-local-compiler": "webpack-dev-server --port 9002 --env webDemoUrl='http://localhost:8080' webDemoResourcesUrl='http://localhost:8081'",
8484
"lint": "eslint . --ext .ts",
8585
"fix": "eslint --fix --ext .ts .",
8686
"test": "npm run build:all && npm run test:run",

src/config.js

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ export const RUNTIME_CONFIG = { ...getConfigFromElement(currentScript) };
1111
* @type {{COMPILE: string, COMPLETE: string, VERSIONS: string, JQUERY: string, KOTLIN_JS: string}}
1212
*/
1313
export const API_URLS = {
14-
server: (RUNTIME_CONFIG.server || __WEBDEMO_URL__).replace(/\/$/, ''),
15-
composeServer: 'https://compose-stage.sandbox.intellij.net'.replace(
14+
server: (__WEBDEMO_URL__ || RUNTIME_CONFIG.server).replace(/\/$/, ''),
15+
composeServer: (__WEBDEMO_URL__ || 'https://compose-stage.sandbox.intellij.net').replace(
1616
/\/$/,
1717
'',
1818
),
19+
composeResources: (__WEBDEMO_RESOURCES_URL__ || 'https://compose-stage.sandbox.intellij.net').replace(
20+
/\/$/, ''
21+
),
1922

2023
COMPILE(platform, version) {
2124
let url;
@@ -63,21 +66,6 @@ export const API_URLS = {
6366
get VERSIONS() {
6467
return `${this.server}/versions`;
6568
},
66-
RESOURCE_VERSIONS() {
67-
return `${this.composeServer}/api/resource/compose-wasm-versions`;
68-
},
69-
SKIKO_MJS(version) {
70-
return `${this.composeServer}/api/resource/skiko-${version}.mjs`;
71-
},
72-
SKIKO_WASM(version) {
73-
return `${this.composeServer}/api/resource/skiko-${version}.wasm`;
74-
},
75-
STDLIB_MJS(hash) {
76-
return `${this.composeServer}/api/resource/stdlib-${hash}.mjs`;
77-
},
78-
STDLIB_WASM(hash) {
79-
return `${this.composeServer}/api/resource/stdlib-${hash}.wasm`;
80-
},
8169
get JQUERY() {
8270
return `https://cdn.jsdelivr.net/npm/jquery@1/dist/jquery.min.js`;
8371
},

src/executable-code/executable-fragment.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -411,24 +411,15 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
411411
targetPlatform,
412412
compilerVersion,
413413
);
414-
const additionalRequests = [];
415-
if (targetPlatform === TargetPlatforms.COMPOSE_WASM) {
416-
if (this.jsExecutor.stdlibExports) {
417-
additionalRequests.push(this.jsExecutor.stdlibExports);
418-
}
419-
}
420414

421-
Promise.all([
422-
WebDemoApi.translateKotlinToJs(
415+
WebDemoApi.translateKotlinToJs(
423416
this.getCode(),
424417
compilerVersion,
425418
targetPlatform,
426419
args,
427420
hiddenDependencies,
428-
),
429-
...additionalRequests,
430-
]).then(
431-
([state, ...additionalRequestsResults]) => {
421+
).then(
422+
(state) => {
432423
state.waitingForOutput = false;
433424
const jsCode = state.jsCode;
434425
const wasm = state.wasm;
@@ -453,7 +444,6 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
453444
outputHeight,
454445
theme,
455446
onError,
456-
additionalRequestsResults,
457447
)
458448
.then((output) => {
459449
const originState = state.openConsole;

src/js-executor/execute-es-module.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1+
import {API_URLS} from "../config";
2+
13
export async function executeWasmCode(container, jsCode, wasmCode) {
24
const newCode = prepareJsCode(jsCode);
35
return execute(container, newCode, wasmCode);
46
}
57

6-
export async function executeWasmCodeWithSkiko(container, jsCode) {
7-
return executeJs(container, prepareJsCode(jsCode));
8-
}
9-
10-
export async function executeWasmCodeWithStdlib(container, jsCode, wasmCode) {
11-
return execute(container, prepareJsCode(jsCode), wasmCode);
12-
}
13-
148
function execute(container, jsCode, wasmCode) {
159
container.wasmCode = Uint8Array.from(atob(wasmCode), c => c.charCodeAt(0));
1610
return executeJs(container, jsCode);
@@ -21,6 +15,8 @@ export function executeJs(container, jsCode) {
2115
}
2216

2317
function prepareJsCode(jsCode) {
18+
const re = /instantiateStreaming\(fetch\(new URL\('([^']*)',\s*import\.meta\.url\)\.href\),\s*importObject\s*,\s*\{\s*builtins\s*:\s*\[''\]\s*\}\s*\)\)\.instance;/g;
19+
2420
return `
2521
class BufferedOutput {
2622
constructor() {
@@ -30,12 +26,20 @@ function prepareJsCode(jsCode) {
3026
export const bufferedOutput = new BufferedOutput()
3127
` +
3228
jsCode
29+
.replaceAll(
30+
"await import('./",
31+
"await import('" + API_URLS.composeResources + "/"
32+
)
33+
.replaceAll(
34+
"%3",
35+
"%253"
36+
)
3337
.replace(
3438
"instantiateStreaming(fetch(wasmFilePath), importObject)).instance;",
3539
"instantiate(window.wasmCode, importObject)).instance;\nwindow.wasmCode = undefined;"
3640
)
3741
.replace(
38-
"instantiateStreaming(fetch(new URL('./playground.wasm',import.meta.url).href), importObject)).instance;",
42+
re,
3943
"instantiate(window.wasmCode, importObject)).instance;\nwindow.wasmCode = undefined;"
4044
)
4145
.replace(

src/js-executor/index.js

Lines changed: 6 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import {API_URLS} from '../config';
33
import {showJsException} from '../view/output-view';
44
import {processingHtmlBrackets} from '../utils';
55
import {isWasmRelated, TargetPlatforms} from '../utils/platforms';
6-
import {executeJs, executeWasmCode, executeWasmCodeWithSkiko, executeWasmCodeWithStdlib} from './execute-es-module';
7-
import {fetch} from "whatwg-fetch";
6+
import {executeWasmCode} from './execute-es-module';
87

98
const INIT_SCRIPT =
109
'if(kotlin.BufferedOutput!==undefined){kotlin.out = new kotlin.BufferedOutput()}' +
@@ -26,7 +25,6 @@ const normalizeJsVersion = (version) => {
2625
export default class JsExecutor {
2726
constructor(kotlinVersion) {
2827
this.kotlinVersion = kotlinVersion;
29-
this.stdlibExports = undefined;
3028
}
3129

3230
async executeJsCode(
@@ -37,7 +35,6 @@ export default class JsExecutor {
3735
outputHeight,
3836
theme,
3937
onError,
40-
additionalRequestsResults,
4138
) {
4239
if (platform === TargetPlatforms.SWIFT_EXPORT) {
4340
return `<span class="standard-output ${theme}"><div class="result-code">${jsCode}</span>`;
@@ -69,15 +66,12 @@ export default class JsExecutor {
6966
// for some reason resize function in Compose does not work in Firefox in invisible block
7067
this.iframe.style.display = 'block';
7168

72-
const additionalRequestsResult = additionalRequestsResults[0];
7369
const result = await this.executeWasm(
7470
jsCode,
7571
wasm,
76-
executeWasmCodeWithStdlib,
72+
executeWasmCode,
7773
theme,
78-
processError,
79-
additionalRequestsResult.stdlib,
80-
additionalRequestsResult.output,
74+
processError
8175
);
8276

8377
if (exception) {
@@ -123,15 +117,15 @@ export default class JsExecutor {
123117
return await this.execute(jsCode, jsLibs, theme, onError, platform);
124118
}
125119

126-
async executeWasm(jsCode, wasmCode, executor, theme, onError, imports, output) {
120+
async executeWasm(jsCode, wasmCode, executor, theme, onError) {
127121
try {
128122
const exports = await executor(
129123
this.iframe.contentWindow,
130124
jsCode,
131125
wasmCode,
132126
);
133-
await exports.instantiate({"playground.master": imports});
134-
const bufferedOutput = output ?? exports.bufferedOutput;
127+
await exports.instantiate();
128+
const bufferedOutput = this.iframe.contentWindow.bufferedOutput ?? exports.bufferedOutput;
135129
const outputString = bufferedOutput.buffer;
136130
bufferedOutput.buffer = '';
137131
return outputString
@@ -182,107 +176,10 @@ export default class JsExecutor {
182176
}
183177
}
184178
if (targetPlatform === TargetPlatforms.COMPOSE_WASM) {
185-
186-
const skikoStdlib = fetch(API_URLS.RESOURCE_VERSIONS(),{
187-
method: 'GET'
188-
}).then(response => response.json())
189-
.then(versions => {
190-
const skikoVersion = versions["skiko"];
191-
192-
const skikoExports = fetch(API_URLS.SKIKO_MJS(skikoVersion), {
193-
method: 'GET',
194-
headers: {
195-
'Content-Type': 'text/javascript',
196-
}
197-
}).then(script => script.text())
198-
.then(script => script.replace(
199-
"new URL(\"skiko.wasm\",import.meta.url).href",
200-
`'${API_URLS.SKIKO_WASM(skikoVersion)}'`
201-
))
202-
.then(skikoCode =>
203-
executeJs(
204-
this.iframe.contentWindow,
205-
skikoCode,
206-
))
207-
.then(skikoExports => fixedSkikoExports(skikoExports))
208-
209-
const stdlibVersion = versions["stdlib"];
210-
211-
const stdlibExports = fetch(API_URLS.STDLIB_MJS(stdlibVersion), {
212-
method: 'GET',
213-
headers: {
214-
'Content-Type': 'text/javascript',
215-
}
216-
}).then(script => script.text())
217-
.then(script =>
218-
// necessary to load stdlib.wasm before its initialization to parallelize
219-
// language=JavaScript
220-
(`const stdlibWasm = fetch('${API_URLS.STDLIB_WASM(stdlibVersion)}');\n` + script).replace(
221-
"fetch(new URL('./stdlib_master.wasm',import.meta.url).href)",
222-
"stdlibWasm"
223-
).replace(
224-
"(extends) => { return { extends }; }",
225-
"(extends_) => { return { extends_ }; }"
226-
))
227-
.then(stdlibCode =>
228-
executeWasmCodeWithSkiko(
229-
this.iframe.contentWindow,
230-
stdlibCode,
231-
)
232-
)
233-
234-
return Promise.all([skikoExports, stdlibExports])
235-
})
236-
237-
this.stdlibExports = skikoStdlib
238-
.then(async ([skikoExportsResult, stdlibExportsResult]) => {
239-
return [
240-
await stdlibExportsResult.instantiate({
241-
"./skiko.mjs": skikoExportsResult
242-
}),
243-
stdlibExportsResult
244-
]
245-
}
246-
)
247-
.then(([stdlibResult, outputResult]) => {
248-
return {
249-
"stdlib": stdlibResult.exports,
250-
"output": outputResult.bufferedOutput
251-
}
252-
}
253-
)
254-
255179
this.iframe.height = "1000"
256180
iframeDoc.write(`<canvas height="1000" id="ComposeTarget"></canvas>`);
257181
}
258182
iframeDoc.write('<body style="margin: 0; overflow: hidden;"></body>');
259183
iframeDoc.close();
260184
}
261185
}
262-
263-
function fixedSkikoExports(skikoExports) {
264-
return {
265-
...skikoExports,
266-
org_jetbrains_skia_Bitmap__1nGetPixmap: function () {
267-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
268-
},
269-
org_jetbrains_skia_Bitmap__1nIsVolatile: function () {
270-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
271-
},
272-
org_jetbrains_skia_Bitmap__1nSetVolatile: function () {
273-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
274-
},
275-
org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer: function () {
276-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
277-
},
278-
org_jetbrains_skia_TextBlobBuilderRunHandler__1nMake: function () {
279-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
280-
},
281-
org_jetbrains_skia_TextBlobBuilderRunHandler__1nMakeBlob: function () {
282-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
283-
},
284-
org_jetbrains_skia_svg_SVGCanvasKt__1nMake: function () {
285-
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
286-
}
287-
}
288-
}

0 commit comments

Comments
 (0)