Skip to content

Commit

Permalink
Merge pull request #81 from georgestagg/webr-0.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
wch authored Nov 28, 2023
2 parents 779949a + 38e9bfe commit 41255f5
Show file tree
Hide file tree
Showing 6 changed files with 1,200 additions and 1,877 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ $(BUILD_DIR)/shinylive/style-resets.css: src/style-resets.css
$(BUILD_DIR)/shinylive/pyodide:
mkdir -p $(BUILD_DIR)/shinylive/pyodide
cd $(BUILD_DIR)/shinylive && \
curl -L https://github.com/pyodide/pyodide/releases/download/$(PYODIDE_VERSION)/$(PYODIDE_DIST_FILENAME) \
curl --fail -L https://github.com/pyodide/pyodide/releases/download/$(PYODIDE_VERSION)/$(PYODIDE_DIST_FILENAME) \
| tar --exclude "*test*.tar" --exclude "node_modules" -xvj

$(BUILD_DIR)/shinylive/webr: webr
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"tsx": "^3.12.7",
"typescript": "^5.1.3",
"vscode-languageserver-protocol": "^3.17.3",
"webr": "^0.2.1",
"webr": "^0.2.2",
"xterm": "^5.2.1",
"xterm-addon-fit": "^0.7.0",
"xterm-readline": "^1.1.1"
Expand Down
14 changes: 9 additions & 5 deletions src/hooks/useWebR.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ export async function initWebR({
channelType,
},
stdout,
stderr
stderr,
);

let initError = false;
try {
await webRProxy.runRAsync('webr::install("codetools")');
await webRProxy.runRAsync('webr::install(c("codetools", "renv", "shiny"))');
await webRProxy.runRAsync(load_r_pre);
} catch (e) {
initError = true;
Expand Down Expand Up @@ -87,8 +87,6 @@ export async function initRShiny({
throw new Error("webRProxyHandle is not ready");
}

await webRProxyHandle.webRProxy.runRAsync('webr::install("renv")');
await webRProxyHandle.webRProxy.runRAsync('webr::install("shiny")');
await webRProxyHandle.webRProxy.runRAsync("library(shiny)");
// Increase webR expressions limit for deep call stack required for Shiny
await webRProxyHandle.webRProxy.runRAsync("options(expressions=1000)");
Expand All @@ -110,7 +108,7 @@ export function useWebR({
ready: false,
shinyReady: false,
initError: false,
}
},
);

useEffect(() => {
Expand Down Expand Up @@ -144,6 +142,12 @@ function ensureOpenChannelListener(webRProxy: WebRProxy): void {
}

const load_r_pre = `
# Force internal tar - silence renv warning
Sys.setenv(TAR = "internal")
# Shim R functions with webR versions (e.g. install.packages())
webr::shim_install()
.shiny_app_registry <- new.env()
# Create a httpuv app from a Shiny app directory
Expand Down
41 changes: 22 additions & 19 deletions src/messageporthttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export async function fetchASGI(
resource: RequestInfo,
init?: RequestInit,
filter: (bodyChunk: Uint8Array, response: Response) => Uint8Array = (
bodyChunk
) => bodyChunk
bodyChunk,
) => bodyChunk,
): Promise<Response> {
if (typeof resource === "string" || typeof init !== "undefined") {
resource = new Request(resource, init);
Expand All @@ -27,7 +27,7 @@ export async function fetchASGI(
type: "makeRequest",
scope: reqToASGI(resource),
},
[channel.port2]
[channel.port2],
);

const blob = await resource.blob();
Expand Down Expand Up @@ -98,21 +98,21 @@ export async function makeRequest(
scope: ASGIHTTPRequestScope,
appName: string,
clientPort: MessagePort,
pyodide: Pyodide
pyodide: Pyodide,
) {
// We could _almost_ use app(), but unfortunately pyodide's implicit proxying
// behavior isn't compatible with ASGI (which wants dict, not JsProxy); we
// need to explicitly convert stuff first, which is what call_pyodide does.
const asgiFunc = pyodide.runPython(
`_shiny_app_registry["${appName}"].app.call_pyodide`
`_shiny_app_registry["${appName}"].app.call_pyodide`,
) as PyProxyCallable;
await connect(scope, clientPort, asgiFunc);
}

async function connect(
scope: ASGIHTTPRequestScope,
clientPort: MessagePort,
asgiFunc: PyProxyCallable
asgiFunc: PyProxyCallable,
) {
const fromClientQueue = new AwaitableQueue<Record<string, any>>();

Expand Down Expand Up @@ -230,7 +230,7 @@ export async function makeHttpuvRequest(
scope: ASGIHTTPRequestScope,
appName: string,
clientPort: MessagePort,
webRProxy: WebRProxy
webRProxy: WebRProxy,
) {
const fromClientQueue = new AwaitableQueue<Record<string, any>>();

Expand Down Expand Up @@ -265,8 +265,8 @@ export async function makeHttpuvRequest(
Object.fromEntries(
[...Array(event.headers.names.length).keys()].map((i) => {
return [event.headers.names[i], event.headers.values[i].values[0]];
})
)
}),
),
);

clientPort.postMessage({
Expand All @@ -289,7 +289,7 @@ async function handleHttpuvRequests(
appName: string,
webRProxy: WebRProxy,
fromClient: () => Promise<Record<string, any>>,
toClient: (event: Record<string, any>) => Promise<void>
toClient: (event: Record<string, any>) => Promise<void>,
) {
let body = new Uint8Array(0);
const shelter = await new webRProxy.webR.Shelter();
Expand Down Expand Up @@ -318,26 +318,29 @@ async function handleHttpuvRequests(
tryCatch(
{
app <- get(appName, env = .shiny_app_registry)
app$call(
list(
PATH_INFO = "${scope.path}",
REQUEST_METHOD = "${scope.method}",
QUERY_STRING = "${scope.query_string}",
rook.input = reader
if (!is.null(app)) {
app$call(
list(
PATH_INFO = "${scope.path}",
REQUEST_METHOD = "${scope.method}",
QUERY_STRING = "${scope.query_string}",
rook.input = reader
)
)
)
}
},
finally = {
reader$destroy()
}
)
`,
{ env, captureConditions: false, captureStreams: false }
{ env, captureConditions: false, captureStreams: false },
);

if (!isRList(httpuvResp)) {
const type = await httpuvResp.type();
throw new Error(
`Unexpected response type: "${httpuvResp.type()}", expected "list".`
`Unexpected response type: "${type}", expected "list".`,
);
}

Expand Down
13 changes: 7 additions & 6 deletions src/webr-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export interface WebRProxy {
openChannel(
path: string,
appName: string,
clientPort: MessagePort
clientPort: MessagePort,
): Promise<void>;

makeRequest(
scope: ASGIHTTPRequestScope,
appName: string,
clientPort: MessagePort
clientPort: MessagePort,
): Promise<void>;
}

Expand All @@ -38,7 +38,7 @@ class WebRWorkerProxy implements WebRProxy {
constructor(
config: WebROptions,
private stdoutCallback: (text: string) => void,
private stderrCallback: (text: string) => void
private stderrCallback: (text: string) => void,
) {
this.webR = new WebR(config);
}
Expand Down Expand Up @@ -67,6 +67,7 @@ class WebRWorkerProxy implements WebRProxy {
return await this.shelter.evalR(code, options);
} catch (e) {
this.stderrCallback((e as Error).message);
throw e;
} finally {
await this.shelter.purge();
}
Expand Down Expand Up @@ -111,15 +112,15 @@ class WebRWorkerProxy implements WebRProxy {
async openChannel(
path: string,
appName: string,
clientPort: MessagePort
clientPort: MessagePort,
): Promise<void> {
await openChannelHttpuv(path, appName, clientPort, this);
}

async makeRequest(
scope: ASGIHTTPRequestScope,
appName: string,
clientPort: MessagePort
clientPort: MessagePort,
): Promise<void> {
await makeHttpuvRequest(scope, appName, clientPort, this);
}
Expand All @@ -128,7 +129,7 @@ class WebRWorkerProxy implements WebRProxy {
export async function loadWebRProxy(
config: WebROptions,
stdoutCallback: (text: string) => void = console.log,
stderrCallback: (text: string) => void = console.error
stderrCallback: (text: string) => void = console.error,
): Promise<WebRProxy> {
const webRProxy = new WebRWorkerProxy(config, stdoutCallback, stderrCallback);
await webRProxy.webR.init();
Expand Down
Loading

0 comments on commit 41255f5

Please sign in to comment.