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

Support wasm-pack frontend #707

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions compiler/wasm-pack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "hello_world"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "^0.2"
web-sys = "^0.3"
js-sys = "^0.3"
wasm-bindgen-futures = "^0.4"
yew = "0.17"
seed = "0.8.0"

[package.metadata.wasm-pack.profile.release]
wasm-opt = false
17 changes: 17 additions & 0 deletions compiler/wasm-pack/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# syntax = docker/dockerfile:experimental

# fetch dependencies to local
FROM shepmaster/rust-nightly as sources
RUN cargo install wasm-pack
ADD --chown=playground src/lib.rs /playground/src/lib.rs
# TODO support top 100 crates
ADD --chown=playground Cargo.toml /playground/Cargo.toml
RUN cargo fetch

# build dependencies
FROM sources
RUN wasm-pack build --target web --out-name package --dev
RUN rm src/*.rs

ADD --chown=playground cargo-pack /playground/.cargo/bin/
ENTRYPOINT ["/playground/tools/entrypoint.sh"]
28 changes: 28 additions & 0 deletions compiler/wasm-pack/cargo-pack
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash

set -eu

# Rewrite our arguments to be `cargo build` instead of `cargo wasm`;
# this assumes that the command will always be `cargo wasm ...`. We
# capture the output directory in order to place the result file.
shift # Ignore "wasm"
args=()
while (( "$#" )); do
if [[ "$1" == "--" ]] ; then
: # Ignore
elif [[ "$1" == "-o" ]] ; then
shift
output="$1"
else
args+="$1"
fi

shift
done
# Greatly inspired from https://gitlab.com/strwrite/seed-playground
# --dev flag disables the wasm-opt for optimization downloaded from networks
wasm-pack build --target web --out-name package --dev

cat pkg/package_bg.wasm | base64 > "${output}.wasm"
cat pkg/package.js | base64 > "${output}.js"

19 changes: 19 additions & 0 deletions compiler/wasm-pack/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use wasm_bindgen::prelude::*;

// Called by our JS entry point to run the example
#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
// Use `web_sys`'s global `window` function to get a handle on the global
// window object.
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let body = document.body().expect("document should have a body");

// Manufacture the element we're gonna append
let val = document.create_element("p")?;
val.set_text_content(Some("Hello from Rust!"));

body.append_child(&val)?;

Ok(())
}
2 changes: 0 additions & 2 deletions ui/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions ui/frontend/BuildMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ const useDispatchAndClose = (action: () => void, close: () => void) => {
const BuildMenu: React.SFC<BuildMenuProps> = props => {
const isHirAvailable = useSelector(selectors.isHirAvailable);
const isWasmAvailable = useSelector(selectors.isWasmAvailable);
const isWasmPackAvailable = useSelector(selectors.isWasmPackAvailable);

const compile = useDispatchAndClose(actions.performCompile, props.close);
const compileToAssembly = useDispatchAndClose(actions.performCompileToAssembly, props.close);
const compileToLLVM = useDispatchAndClose(actions.performCompileToLLVM, props.close);
const compileToMir = useDispatchAndClose(actions.performCompileToMir, props.close);
const compileToHir = useDispatchAndClose(actions.performCompileToNightlyHir, props.close);
const compileToWasm = useDispatchAndClose(actions.performCompileToNightlyWasm, props.close);
const compileToWasmPack = useDispatchAndClose(actions.performCompileToNightlyWasmPack, props.close);
const execute = useDispatchAndClose(actions.performExecute, props.close);
const test = useDispatchAndClose(actions.performTest, props.close);

Expand Down Expand Up @@ -67,6 +69,10 @@ const BuildMenu: React.SFC<BuildMenuProps> = props => {
Build a WebAssembly module for web browsers, in the .WAT textual representation.
{!isWasmAvailable && <WasmAside />}
</ButtonMenuItem>
<ButtonMenuItem name="WASM PACK" onClick={compileToWasmPack}>
Build a WebAssembly frontend for web browsers. Rendering in Iframe
{!isWasmPackAvailable && <WasmPackAside />}
</ButtonMenuItem>
</MenuGroup>
);
};
Expand All @@ -85,4 +91,10 @@ const WasmAside: React.SFC = () => (
</p>
);

const WasmPackAside: React.SFC = () => (
<p className="build-menu__aside">
Note: WASM PACK currently requires using the Nightly channel, selecting this
option will switch to Nightly.
</p>
);
export default BuildMenu;
12 changes: 11 additions & 1 deletion ui/frontend/Output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Gist from './Output/Gist';
import Section from './Output/Section';
import SimplePane, { SimplePaneProps } from './Output/SimplePane';
import PaneWithMir from './Output/PaneWithMir';
import PaneWithWasmPack from './Output/PaneWithWasmPack';
import * as selectors from './selectors';

const Tab: React.SFC<TabProps> = ({ kind, focus, label, onClick, tabProps }) => {
Expand Down Expand Up @@ -46,7 +47,10 @@ interface PaneWithCodeProps extends SimplePaneProps {

const Output: React.SFC = () => {
const somethingToShow = useSelector(selectors.getSomethingToShow);
const { meta: { focus }, execute, format, clippy, miri, macroExpansion, assembly, llvmIr, mir, hir, wasm, gist } =
const { meta: { focus },
execute, format, clippy, miri,
macroExpansion, assembly, llvmIr, mir,
hir, wasm, gist, wasmPack } =
useSelector((state: State) => state.output);

const dispatch = useDispatch();
Expand All @@ -62,6 +66,7 @@ const Output: React.SFC = () => {
const focusHir = useCallback(() => dispatch(actions.changeFocus(Focus.Hir)), [dispatch]);
const focusWasm = useCallback(() => dispatch(actions.changeFocus(Focus.Wasm)), [dispatch]);
const focusGist = useCallback(() => dispatch(actions.changeFocus(Focus.Gist)), [dispatch]);
const focusWasmPack = useCallback(() => dispatch(actions.changeFocus(Focus.WasmPack)), [dispatch]);

if (!somethingToShow) {
return null;
Expand All @@ -88,6 +93,7 @@ const Output: React.SFC = () => {
{focus === Focus.Hir && <PaneWithMir {...hir} kind="hir" />}
{focus === Focus.Wasm && <PaneWithCode {...wasm} kind="wasm" />}
{focus === Focus.Gist && <Gist />}
{focus === Focus.WasmPack && <PaneWithWasmPack {...wasmPack} kind="wasm-pack" />}
</div>
);
}
Expand Down Expand Up @@ -139,6 +145,10 @@ const Output: React.SFC = () => {
label="Share"
onClick={focusGist}
tabProps={gist} />
<Tab kind={Focus.WasmPack} focus={focus}
label="WASM PACK"
onClick={focusWasmPack}
tabProps={wasmPack} />
{close}
</div>
{body}
Expand Down
35 changes: 35 additions & 0 deletions ui/frontend/Output/Container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react'
import { createPortal } from 'react-dom'

export const FunctionalIFrameComponent = ({
children,
url,
...props
}) => {
const [contentRef, setContentRef] = useState(null)
const document =
contentRef?.contentWindow?.document;
const mountNode = document?.body;

useEffect(() => {
if (document) {
const script = document.createElement('script');
script.src = url;
script.type = 'module';
script.async = true;
mountNode && mountNode.appendChild(script);
}

return () => {
if (mountNode) {
mountNode.innerHTML = ''
}
};
}, [mountNode, document, url]);

return (
<iframe {...props} ref={setContentRef} scrolling='no' className='container'>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
52 changes: 52 additions & 0 deletions ui/frontend/Output/PaneWithWasmPack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';

import Header from './Header';
import SimplePane, { SimplePaneProps } from './SimplePane';
import { FunctionalIFrameComponent } from './Container';

interface PaneWithWasmPackProps extends SimplePaneProps {
success?: boolean;
wasm_js?: string;
wasm_bg?: string;
}

function base64ToByteArray(src: string) {
const decode = atob(src);
const byteNumbers = new Array(decode.length);
for (let i = 0; i < decode.length; i++) {
byteNumbers[i] = decode.charCodeAt(i);
}
return new Uint8Array(byteNumbers);
}

function createObjectURL(src: ArrayBuffer | string, mime: string) {
return URL.createObjectURL(new Blob([src], { type: mime }));
}

function createEntryJS(wasm_js: string, wasm_bg: string, success: boolean) {
if (!success) {
return '';
}
const wasmJS = atob(wasm_js);
const bgWasm = base64ToByteArray(wasm_bg);
const wasmJSBlob = createObjectURL(wasmJS, 'application/javascript');
const bgWasmBlob = createObjectURL(bgWasm, 'application/wasm');
const entryJS = `
import init from '${wasmJSBlob}';
await init('${bgWasmBlob}');
`;

return createObjectURL(entryJS, 'application/javascript');
}

const PaneWithWasmPack: React.SFC<PaneWithWasmPackProps> = ({ wasm_js, wasm_bg, success, ...rest }) => (
<SimplePane {...rest}>
<div className="output-result">
<Header label="Result" />
<FunctionalIFrameComponent url={createEntryJS(wasm_js, wasm_bg, success)}>
</FunctionalIFrameComponent>
</div>
</SimplePane>
);

export default PaneWithWasmPack;
Loading