From 46cbcf3ff121b3c7e98e8f06639f82cd58718082 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 23 Apr 2024 12:14:06 +0800 Subject: [PATCH] feat(cli): allow sync fs operation between workers/mainThread (#2064) * feat(cli): allow sync fs operation between workers/mainThread * allow sync fs operation between workers/mainThread (#2065) * Fix * Update fixture * flaky test * Fix cross compile target * Update zig * macos-cross test was filtered --------- Co-authored-by: Toyo Li --- renovate.json => .github/renovate.json | 1 - .github/workflows/test-release.yaml | 5 +- .github/workflows/zig.yaml | 12 +- Cargo.toml | 5 + alpine-zig.Dockerfile | 2 +- cli/src/api/artifacts.ts | 4 +- cli/src/api/templates/ci-template.ts | 2 +- cli/src/api/templates/load-wasi-template.ts | 18 +- cli/src/api/templates/wasi-worker-template.ts | 20 +- cli/src/api/universalize.ts | 12 +- debian-zig.Dockerfile | 2 +- .../__snapshots__/typegen.spec.ts.md | 2 + .../__snapshots__/typegen.spec.ts.snap | Bin 4864 -> 4873 bytes .../__tests__/__snapshots__/values.spec.ts.md | 1 + .../__snapshots__/values.spec.ts.snap | Bin 389 -> 405 bytes examples/napi/__tests__/worker-thread.spec.ts | 2 +- examples/napi/browser/values.spec.ts | 16 +- examples/napi/example.wasi-browser.js | 88 ++++---- examples/napi/example.wasi.cjs | 90 ++++---- examples/napi/index.cjs | 1 + examples/napi/index.d.cts | 2 + examples/napi/package.json | 3 +- examples/napi/src/task.rs | 23 +++ examples/napi/vite-entry.js | 16 ++ examples/napi/wasi-worker-browser.mjs | 13 +- examples/napi/wasi-worker.mjs | 7 +- package.json | 3 +- wasm-runtime/fs-proxy.cjs | 192 ++++++++++++++++++ wasm-runtime/fs.js | 2 +- wasm-runtime/package.json | 2 +- wasm-runtime/rollup.config.js | 1 + wasm-runtime/runtime.cjs | 4 + wasm-runtime/runtime.js | 1 + yarn.lock | 24 +-- 34 files changed, 424 insertions(+), 152 deletions(-) rename renovate.json => .github/renovate.json (96%) create mode 100644 wasm-runtime/fs-proxy.cjs diff --git a/renovate.json b/.github/renovate.json similarity index 96% rename from renovate.json rename to .github/renovate.json index 84b17184..b5667d91 100644 --- a/renovate.json +++ b/.github/renovate.json @@ -4,7 +4,6 @@ "**/node_modules/**", "**/bower_components/**", "**/vendor/**", - "**/examples/**", "**/__tests__/**", "**/test/**", "**/__fixtures__/**" diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index f8411108..d0aa34b1 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -388,13 +388,11 @@ jobs: args: '--platform linux/ppc64le' arch: 'ppc64' libc: 'gnu' - without-lerna: true - image: 'node:{:version}-slim' target: s390x-unknown-linux-gnu args: '--platform linux/s390x' arch: 's390x' libc: 'gnu' - without-lerna: true - image: 'node:{:version}-alpine' target: x86_64-unknown-linux-musl args: '' @@ -444,8 +442,7 @@ jobs: with: image: ${{ steps.image-name.outputs.docker-image }} options: ${{ matrix.settings.args }} -v ${{ github.workspace }}/cores:/cores -v ${{ github.workspace }}:/build -w /build - run: >- - ${{ matrix.settings.without-lerna && 'yarn test:without-lerna' || 'yarn test' }} + run: yarn test - name: List files run: | ls -la . diff --git a/.github/workflows/zig.yaml b/.github/workflows/zig.yaml index 4a90f216..ab3e3d5f 100644 --- a/.github/workflows/zig.yaml +++ b/.github/workflows/zig.yaml @@ -23,7 +23,7 @@ jobs: matrix: target: [ - 'x86_64-apple-darwin', + 'aarch64-apple-darwin', 'x86_64-unknown-linux-musl', 'aarch64-unknown-linux-musl', 'armv7-unknown-linux-musleabihf', @@ -53,7 +53,7 @@ jobs: - name: Install ziglang uses: goto-bus-stop/setup-zig@v2 with: - version: 0.11.0 + version: 0.12.0 - name: Install dependencies run: yarn install --immutable --mode=skip-build - name: install MacOS SDK @@ -89,8 +89,8 @@ jobs: settings: - host: ubuntu-latest target: x86_64-unknown-linux-musl - - host: macos-latest - target: x86_64-apple-darwin + - host: macos-14 + target: aarch64-apple-darwin - host: ubuntu-latest target: aarch64-unknown-linux-musl docker-platform: arm64 @@ -128,7 +128,7 @@ jobs: run: yarn test --verbose env: SKIP_UNWIND_TEST: 1 - if: matrix.settings.host == 'macos-latest' + if: matrix.settings.host == 'macos-14' - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes if: matrix.settings.host == 'ubuntu-latest' - name: Test @@ -149,7 +149,7 @@ jobs: options: --platform linux/${{ matrix.settings.docker-platform }} -v ${{ github.workspace }}:/build -w /build run: | set -e - yarn test:without-lerna + yarn test - name: Test uses: addnab/docker-run-action@v3 if: matrix.settings.target == 'x86_64-unknown-linux-musl' diff --git a/Cargo.toml b/Cargo.toml index 9d3a6052..394e95dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,11 @@ exclude = ["./testing"] [profile.release] lto = true +[profile.wasi] +inherits = "release" +opt-level = "z" +debug = 'full' + [profile.napi-rs-custom] inherits = "dev" codegen-units = 1024 diff --git a/alpine-zig.Dockerfile b/alpine-zig.Dockerfile index bb8c9539..22e80c54 100644 --- a/alpine-zig.Dockerfile +++ b/alpine-zig.Dockerfile @@ -1,6 +1,6 @@ FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine -ARG ZIG_VERSION=0.11.0 +ARG ZIG_VERSION=0.12.0 RUN apk add --update --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/edge/testing xz xz-dev && \ rustup target add x86_64-unknown-linux-gnu && \ diff --git a/cli/src/api/artifacts.ts b/cli/src/api/artifacts.ts index 607c939a..862e33de 100644 --- a/cli/src/api/artifacts.ts +++ b/cli/src/api/artifacts.ts @@ -31,8 +31,8 @@ export async function collectArtifacts(userOptions: ArtifactsOptions) { const universalSourceBins = new Set( targets .filter((platform) => platform.arch === 'universal') - .flatMap( - (p) => UniArchsByPlatform[p.platform]?.map((a) => `${p.platform}-${a}`), + .flatMap((p) => + UniArchsByPlatform[p.platform]?.map((a) => `${p.platform}-${a}`), ) .filter(Boolean) as string[], ) diff --git a/cli/src/api/templates/ci-template.ts b/cli/src/api/templates/ci-template.ts index 66c398c1..b38e30e1 100644 --- a/cli/src/api/templates/ci-template.ts +++ b/cli/src/api/templates/ci-template.ts @@ -136,7 +136,7 @@ jobs: - uses: goto-bus-stop/setup-zig@v2 if: \${{ contains(matrix.settings.target, 'musl') }} with: - version: 0.11.0 + version: 0.12.0 - name: Setup toolchain run: \${{ matrix.settings.setup }} diff --git a/cli/src/api/templates/load-wasi-template.ts b/cli/src/api/templates/load-wasi-template.ts index 3418ab3d..a5cda870 100644 --- a/cli/src/api/templates/load-wasi-template.ts +++ b/cli/src/api/templates/load-wasi-template.ts @@ -22,10 +22,15 @@ const __wasi = new __WASI({ version: 'preview1', })` + const workerFsHandler = fs + ? `\n worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))\n` + : '' + return `import { instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync, getDefaultContext as __emnapiGetDefaultContext, WASI as __WASI, + createOnMessage as __wasmCreateOnMessageForFsProxy, } from '@napi-rs/wasm-runtime' ${fsImport} import __wasmUrl from './${wasiFilename}.wasm?url' @@ -50,9 +55,11 @@ const { asyncWorkPoolSize: 4, wasi: __wasi, onCreateWorker() { - return new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), { + const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), { type: 'module', }) + ${workerFsHandler} + return worker }, overwriteImports(importObject) { importObject.env = { @@ -87,7 +94,7 @@ export const createWasiBinding = ( /* auto-generated by NAPI-RS */ -const __nodeFs= require('node:fs') +const __nodeFs = require('node:fs') const __nodePath = require('node:path') const { WASI: __nodeWASI } = require('node:wasi') const { Worker } = require('node:worker_threads') @@ -95,6 +102,7 @@ const { Worker } = require('node:worker_threads') const { instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync, getDefaultContext: __emnapiGetDefaultContext, + createOnMessage: __wasmCreateOnMessageForFsProxy, } = require('@napi-rs/wasm-runtime') const __rootDir = __nodePath.parse(process.cwd()).root @@ -141,10 +149,14 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule })(), wasi: __wasi, onCreateWorker() { - return new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), { + const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), { env: process.env, execArgv: ['--experimental-wasi-unstable-preview1'], }) + worker.onmessage = ({ data }) => { + __wasmCreateOnMessageForFsProxy(__nodeFs)(data) + } + return worker }, overwriteImports(importObject) { importObject.env = { diff --git a/cli/src/api/templates/wasi-worker-template.ts b/cli/src/api/templates/wasi-worker-template.ts index d9a3eb40..682d9ccc 100644 --- a/cli/src/api/templates/wasi-worker-template.ts +++ b/cli/src/api/templates/wasi-worker-template.ts @@ -1,5 +1,6 @@ export const WASI_WORKER_TEMPLATE = `import fs from "node:fs"; import { createRequire } from "node:module"; +import { parse } from "node:path"; import { WASI } from "node:wasi"; import { parentPort, Worker } from "node:worker_threads"; @@ -27,7 +28,9 @@ Object.assign(globalThis, { }, }); -const emnapiContext = getDefaultContext() +const emnapiContext = getDefaultContext(); + +const __rootDir = parse(process.cwd()).root; const handler = new MessageHandler({ onLoad({ wasmModule, wasmMemory }) { @@ -35,7 +38,7 @@ const handler = new MessageHandler({ version: 'preview1', env: process.env, preopens: { - '/': '/', + [__rootDir]: __rootDir, }, }); @@ -62,21 +65,20 @@ globalThis.onmessage = function (e) { export const createWasiBrowserWorkerBinding = (fs: boolean) => { const fsImport = fs - ? `import { Volume, createFsFromVolume } from '@napi-rs/wasm-runtime/fs' + ? `import { memfsExported as __memfsExported } from '@napi-rs/wasm-runtime/fs' -const fs = createFsFromVolume( - Volume.fromJSON({ - '/': null, - }), -)` +const fs = createFsProxy(__memfsExported)` : '\nconst fs = null' - return `import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime' + return `import { instantiateNapiModuleSync, MessageHandler, WASI, createFsProxy } from '@napi-rs/wasm-runtime' ${fsImport} const handler = new MessageHandler({ onLoad({ wasmModule, wasmMemory }) { const wasi = new WASI({ fs, + preopens: { + '/': '/', + }, print: function () { // eslint-disable-next-line no-console console.log.apply(console, arguments) diff --git a/cli/src/api/universalize.ts b/cli/src/api/universalize.ts index f21dca56..c9a32bc2 100644 --- a/cli/src/api/universalize.ts +++ b/cli/src/api/universalize.ts @@ -42,8 +42,12 @@ export async function universalizeBinaries(userOptions: UniversalizeOptions) { ) } - const srcFiles = UniArchsByPlatform[process.platform]?.map( - (arch) => resolve(options.cwd, options.outputDir, `${config.binaryName}.${process.platform}-${arch}.node`), + const srcFiles = UniArchsByPlatform[process.platform]?.map((arch) => + resolve( + options.cwd, + options.outputDir, + `${config.binaryName}.${process.platform}-${arch}.node`, + ), ) if (!srcFiles || !universalizers[process.platform]) { @@ -55,9 +59,7 @@ export async function universalizeBinaries(userOptions: UniversalizeOptions) { debug(`Looking up source binaries to combine: `) debug(' %O', srcFiles) - const srcFileLookup = await Promise.all( - srcFiles.map((f) => fileExists(f)), - ) + const srcFileLookup = await Promise.all(srcFiles.map((f) => fileExists(f))) const notFoundFiles = srcFiles.filter((_, i) => !srcFileLookup[i]) diff --git a/debian-zig.Dockerfile b/debian-zig.Dockerfile index 40e0eb2a..cc188982 100644 --- a/debian-zig.Dockerfile +++ b/debian-zig.Dockerfile @@ -1,6 +1,6 @@ FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian -ARG ZIG_VERSION=0.11.0 +ARG ZIG_VERSION=0.12.0 RUN wget https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ tar -xvf zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index f67fab42..cc1c64f4 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -295,6 +295,8 @@ Generated by [AVA](https://avajs.dev). ␊ export function asyncTaskOptionalReturn(): Promise␊ ␊ + export function asyncTaskReadFile(path: string): Promise␊ + ␊ export function asyncTaskVoidReturn(): Promise␊ ␊ export interface B {␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index b768721ca4b8363a4a695662c5342ffac0148a0a..9dc5d938f76791267ea0aa46b58777b70f4b34d2 100644 GIT binary patch delta 4755 zcmV;E5^U{&CW$70K~_N^Q*L2!b7*gLAa*kf0|1?qKO_#!O6uS-5AGivP5&^O+ed|T z@0B=PgC1R~G)^Cj2mk;800003?L1v?+{lr8cL)Ob?fyaE27yP5u~w1|!#7!57|E6+ zuO%6p@$LqWk>!v*Gi`C$6LwS5tYaW~xc7U$mqYRk?QV?|{E-;Ee9PzlRJeZG4=tRS>Jq0h#1m zmVZQq&q<(%VYaj7D|k6lQ_Sq?k_iI;1B%?$HJCFnC?JO<304%CBvY0wb}EPhG7k`7 zLsnESd89E38AukA8zz^C!ojvPG8=$d3n~@v!H&g_pmwmjD$Yhh!P#a$3AAcD6pa1@^KdW)v$jy=mD^?=V-M!xD?(S}B z+d%1azEB(Aak;No`z=i=3E&^WS3t-zNP&Sg1uEen^GDbl2x<~ip`qk5ki^_lkS;31 zU=6p++|zh7($jwb?)}G4U;lpe<(D7zz^8mGDV158T%ar-qaYz|K&I!-_rQu}MOFVU33ddj>OtFr;fa0nJQ}xC;C|B5%PaJsGEIuyL7%hJZ{~9Dn3yK_nGY zX@9jTW8u&uR5kQb99+?Zqu&O?%8#oL>1J?6;U4H&GkD$^l|cX?TZ4V212nSmGMmp8 zm#;aCD!esV%vw(?h`e;cia?Yf559ZqOsrry4rI8zNUGPd{d&}a4*Eb@O>|=Hi$_2l zww~8a#;As(4DrYZVkM>u`W?&zdr1YTrmZk&`K*FSuqO5W^F-2HeYscY8l6*H>NZ)Bw@t|uw`de+^r`KycD4Bvb8=x)q2?Jl4{X}cq zxM7i8T9q~=OUf2Y*=z%i!F?;}IWQJIbS22fxmM0@bgp1e(A#yCuYEgF%rS5CtE!#n zAa-C69bo&~0X}T71T9ny+lUyp(lBf#!NVuM*niGsRS}%9nL_YxqJocICefVm^gW8m zsd@Ou5{H_9Xh?n5sXGDp!OuW$R6z(~1lS+}ddIQ!h}*Z|LoIn(iuc$#*#s0ugKt)u zJS@%Kxg+^i(q2*t02B3Z<=DntWEin^o1j->Ygh1ISa9mE;MQ28q1H_47` za?l+*R$@UmJWEM}*ojo={|zFi|NL7i5K8FHWw2KMr$dLIjt4ntQkG@ZtER!h)41Wtlnd=5?YF|KjY`0wB=b>Zbvm~N( zh}t7#?sw_}tidKSNaetyXu(i0+o8V&C(>%s?r(*+*wMkAwseuio56NWsUE?E#x!@; zc^c95Xj6B>gqr0US5n}RcLSP`HVL>N{m_qnSV2Cl;I2#VXTREs!RCRrzGX4VgQA%9 zd4H~XH$B51(nU@(dHM|8?u}JOz0sdQ zR217ast6I)))=SRI;P`vA=)Y5E93&6_J4C*s~!*Xi_mXhio99~E&a)CjGTRb=6d|p+i;_twjFR!|)A2+}-WZ~xy&?QK zf*<>S5sg_O)cpdfRzR?x!hk`uM{Zrv*cvQdSIAAHtMGyaJ!}>K^_p6C=_oqS;56tT zUXXaRHwfje&5sXB7{u``2(LhRc6WC{9>DbhA$a$`TO1JE{+8G(dHzP+c)ofYZ$Ojg z2PA)n$^GdKFJ0e)igw^PaVGZn_6F-3KyMV6G|EEiJ@kGErr;1;;k#5W@1O>2R7V?% zOHf27ET)5XfN_5{2e{rHfje%&|F{pW03am)jfkQ8g9qT*9#5jiGF)+tD5LM7Lk!&OuM?h*!&Z zQCuF_8K>G}4%9m30(41-ddzvM^1&lAb-jj$8g@HDU~DJr;3gS-5GT1$vRCp3Dy4r7 zPqD+hR3TF044nUdy3{=o~Z(Z^OuPLpFJQgBH7_K-D8!r4K7Y-)11e;b0POZf9ZQqbP} zXDrEN?pEUHj{d(kz$rgtam+*otnq*372r9+BVY3k`9%+5A89GRxl$vJ;>D(IRu zV+q}KW5XESh{LcZKtPW(xmu-V61>$JXiO?&*p5(oTs^86HWAx`2a$lbU&HY#58xPV z#Q~Kn*6m!a^G+~*PDSY6J_CP-S?%BP46cmot^uGc)gUD#U8}AfEcyotrnA+}1SQBb zp_#txR{x8DRTt&Xv(*cHLO<*xp%}4Z=n7E^{5()U!7SkTLwq>Fl=@Th2!Bu5E&P0p zKVM+Q4j!K1!#PuU{#^mn1X1pJ{W?X!L-me5@!QwaKOA37rZvs(&{!)=I^q5QR&L+N6_D+D5%FTU zQ#>{1N}3EBRb{7#mc34n&s<8Q{X0=S>O=v*;2L)#I)d33X?}4H)&j?%DSl{lFkHMU zX;w%2E>DVS^51tL!+3v6>!jT07z3Ik_~*FA=sLja)OB60QUY`oVG{-|D;H_`Zd5qm zn(lhBT1(TTQ(prnO!?`Vmx;JsGL?K3q$n#^zuR{_n-n9l-bJF(w{&&fg!c;}vi zz~A1Dd3S{3Q5I87VpVFvzRBkl%rvTg5WK?CdG{Xw>jwY>q9^*C6g8oWOPjKn*utoq zcJ;V$j9CTVX>)&S+i1<{vWI%u;#_$gv2wx6x6&IB%wR!Z37#mPu8U{zwu*;#Uc;3u zbZgC{aA(R~3lG`_@vYrWE4uGNbUdwNwW)0Xu}?)oSYlpu9}DxV%F`{vCgxX^IzH;H zDcO?-I-c}JfcJJ4tTACJO~Nv-#`&*f%RR*#5NR(=8!3O~{tDH}Zk=L*+!NZ3By%`_ zvgdb?)kh2V3x=a0y%M@_1iPwCeMf)W5XS;SNjQA{{y31;)%M9F`CZ=xaEC^zW*pYq}hB7s@DSYGqnnx=bWc%6U;@vVL5kTX+@;!E986WOSK zqCWI^1L%3WLQaQE8eVDP{peQTf1@ien#kXfPtAPSG-5jmD&|+V|1B#u>$NAiJ(czr zcHmM|{>tqx%GZe~R!C_QGO8m5FKhBPp$6AVCzF3$n_@bhFQ?0X{Nx75o#&Qxn*5TEg;mCFrfL=i zcO<`YXeib9n>O#I&mqrd|4K=Z#JLriL^5vI?gvr&Y^0dvxMIGpJdKAOL zYH=Xf8rf;0qjPJCa$DS7%r_|o%?0aoXfC^)=IplBeuRH^giW1H@#vec;_zC74BDi_k*9IS>m)Vp zMB!GZGf$i7T#9p+)l^K2SQv!Q*<9tCrT*U0V)W#6ykGuxFL0L0&rWpJPFtg2GSywB z=+fj|lO2v-xZkmvy*A+@5F2Z>j5%*OWZKlf8r+k*$8pBu=#n68( z`zr2Ox7N+-K6SKH_a$4Qwa`{kG)h(zGL)jp5~8%|7(d9zi>@D=0A7jQX74n%Uo-)? zCs)je#wGW#sDas$Cb%A{g383x`Dp@?8;dGA<+i$M6AGhIEvBj+@tT$zZn%~a$iTX9 z-8jvXNeJ`d-l+d`Kuk|S9Y#>8D_(!6$mxMDa@k^6y-Zp79$G9sO~OtyaS_7n(vq6} zCWQc7*QM^;-}f;_f6apk1s=xDPc@7!YWSJyef8tMH->OEL){Z%pKy@>rynprVh_6?NJBtNOs%oTeBpJh zn__JL^_DHPb+oES{0vK}MbnYhCw-c9S}-c(-GH{EfvJuadlPRQj7@?3mbCL5fO<-ALH3QL9%Ne@w%ZJ8W&M18`;3uTu_2+6%LIR7}N2*5q}br0qND5v^JftB^FUt%<22QF9Is z-3}d(W$q-?ny$Njb7Qe=krl{6bl;$GVxmrB>(wsb9}MaNUjHtyFOIFg?(0OB$I@(6 zoL(Cz@DIa`XE20+1SaKi3?k3By h#r9odU7p~#Tn!Vv@bmBwj~}%#@9#w`u^rG-0085MQVRe8 delta 4716 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 diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 5c0cacb5..044f7972 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -16,6 +16,7 @@ Generated by [AVA](https://avajs.dev). '@vitest/browser', '@vitest/ui', 'ava', + 'buffer', 'cross-env', 'electron', 'lodash', diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index 2b1be196c853c9f3064c4e651ba44b589ce03635..af4bee0c20afc6373c22df7951dc70c7a99f4da5 100644 GIT binary patch literal 405 zcmV;G0c!q1RzVF00000000A3l1om*Fc5}4Nz->p%R@p)93kok_GQVI9ZM&1sEcbywv$qc zO;5m%eK`OUci<36oPrfwrh-XDq))N_|I=t=VKru9~qN;onCGEN#} z#oaQomG*KeNDE0D&HTC)ZmLYq-JI$c)$y#BUN}EZty$rfkr0v4C!uPVY*QEVnp4=J zu$@*#u^-f>yq8(yTq1N$RgbE!P$KizXlnY+u>WfhC~w(^MRO%XN+T!re#oeicV9s1 z0i|cVG$~beCv~C?{wt+X>q08k433*Me(!+tG3A4mNOLP|VWs&23;!V6fdT*kC!e-o literal 389 zcmV;00eb#HRzV*l-3e0Vm-COav1|q^DTl_cR(uIV|SJ<@dOMQ9%czR;YZTvK2b3(aI8+F5v!9 z6f(%0=rR@^Q9say$&%idcp~1258{hB0U<7kYvPu8B%X;^;+^;;AQIw?IEMBFk}L1! z4uDT-nhNL(7z-GZ5z)k0Kr(^hj5gAl$7yc#ZwbKdxM_perBTR6#Kvi3Y$dKUzp+8D z6>BkTqgmLM3a7>u5|>=JxK3x44hq9`?%kF?7YhjseHJ#&l5Or{SxF8%9QJbW5Hn>f zuDV=xl~H-{&T`Y^ru~1tKIbj}uxhS8;50#P|Aq`3x!#o15vS*Mnw4gAr)_2({ST!x jyM;EU8GO{N@pD(sQ_crlH7~rbl-KSX>}*6NWdZ;It8}%+ diff --git a/examples/napi/__tests__/worker-thread.spec.ts b/examples/napi/__tests__/worker-thread.spec.ts index c5ec63d4..b50dd127 100644 --- a/examples/napi/__tests__/worker-thread.spec.ts +++ b/examples/napi/__tests__/worker-thread.spec.ts @@ -21,7 +21,7 @@ const concurrency = process.env.WASI_TEST // @ts-expect-error process?.report?.getReport()?.header?.glibcVersionRuntime && !process.env.ASAN_OPTIONS) - ? 50 + ? 20 : 3 t('should be able to require in worker thread', async (t) => { diff --git a/examples/napi/browser/values.spec.ts b/examples/napi/browser/values.spec.ts index af585686..f9bd00d2 100644 --- a/examples/napi/browser/values.spec.ts +++ b/examples/napi/browser/values.spec.ts @@ -1,17 +1,19 @@ +import { Buffer } from 'buffer' + import { describe, it, expect } from 'vitest' +global.Buffer = Buffer + // @ts-expect-error const { // @ts-expect-error __fs, - // @ts-expect-error - __volume, DEFAULT_COST, Bird, GetterSetterWithClosures, tsfnReturnPromise, tsfnReturnPromiseTimeout, - readFileAsync, + asyncTaskReadFile, }: typeof import('../index.cjs') = await import('../example.wasi-browser') describe('NAPI-RS wasi browser test', function () { @@ -59,9 +61,9 @@ describe('NAPI-RS wasi browser test', function () { await new Promise((resolve) => setTimeout(resolve, 400)) }) - it.skip('readFileAsync', async () => { - __volume.writeFileSync('/test.txt', 'hello world') - const value = await readFileAsync('/test.txt') - expect(value).toBe('hello world') + it('readFileAsync', async () => { + __fs.writeFileSync('/test.txt', 'hello world') + const value = await asyncTaskReadFile('/test.txt') + expect(value.toString('utf8')).toBe('hello world') }) }) diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index ecd109d8..6724c1ec 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -2,6 +2,7 @@ import { instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync, getDefaultContext as __emnapiGetDefaultContext, WASI as __WASI, + createOnMessage as __wasmCreateOnMessageForFsProxy, } from '@napi-rs/wasm-runtime' import { memfs } from '@napi-rs/wasm-runtime/fs' import __wasmUrl from './example.wasm32-wasi.wasm?url' @@ -35,9 +36,13 @@ const { asyncWorkPoolSize: 4, wasi: __wasi, onCreateWorker() { - return new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), { + const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), { type: 'module', }) + + worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs)) + + return worker }, overwriteImports(importObject) { importObject.env = { @@ -292,45 +297,47 @@ function __napi_rs_initialize_modules(__napiInstance) { __napiInstance.exports['__napi_register__async_task_void_return_296']?.() __napiInstance.exports['__napi_register__AsyncTaskOptionalReturn_impl_297']?.() __napiInstance.exports['__napi_register__async_task_optional_return_298']?.() - __napiInstance.exports['__napi_register__call_threadsafe_function_299']?.() - __napiInstance.exports['__napi_register__call_long_threadsafe_function_300']?.() - __napiInstance.exports['__napi_register__threadsafe_function_throw_error_301']?.() - __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_302']?.() - __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_303']?.() - __napiInstance.exports['__napi_register__threadsafe_function_closure_capture_304']?.() - __napiInstance.exports['__napi_register__tsfn_call_with_callback_305']?.() - __napiInstance.exports['__napi_register__tsfn_async_call_306']?.() - __napiInstance.exports['__napi_register__accept_threadsafe_function_307']?.() - __napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_308']?.() - __napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_309']?.() - __napiInstance.exports['__napi_register__tsfn_return_promise_310']?.() - __napiInstance.exports['__napi_register__tsfn_return_promise_timeout_311']?.() - __napiInstance.exports['__napi_register__tsfn_throw_from_js_312']?.() - __napiInstance.exports['__napi_register__get_buffer_313']?.() - __napiInstance.exports['__napi_register__append_buffer_314']?.() - __napiInstance.exports['__napi_register__get_empty_buffer_315']?.() - __napiInstance.exports['__napi_register__convert_u32_array_316']?.() - __napiInstance.exports['__napi_register__create_external_typed_array_317']?.() - __napiInstance.exports['__napi_register__mutate_typed_array_318']?.() - __napiInstance.exports['__napi_register__deref_uint8_array_319']?.() - __napiInstance.exports['__napi_register__buffer_pass_through_320']?.() - __napiInstance.exports['__napi_register__array_buffer_pass_through_321']?.() - __napiInstance.exports['__napi_register__accept_slice_322']?.() - __napiInstance.exports['__napi_register__u8_array_to_array_323']?.() - __napiInstance.exports['__napi_register__i8_array_to_array_324']?.() - __napiInstance.exports['__napi_register__u16_array_to_array_325']?.() - __napiInstance.exports['__napi_register__i16_array_to_array_326']?.() - __napiInstance.exports['__napi_register__u32_array_to_array_327']?.() - __napiInstance.exports['__napi_register__i32_array_to_array_328']?.() - __napiInstance.exports['__napi_register__f32_array_to_array_329']?.() - __napiInstance.exports['__napi_register__f64_array_to_array_330']?.() - __napiInstance.exports['__napi_register__u64_array_to_array_331']?.() - __napiInstance.exports['__napi_register__i64_array_to_array_332']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_333']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_334']?.() - __napiInstance.exports['__napi_register__AsyncBuffer_impl_335']?.() - __napiInstance.exports['__napi_register__async_reduce_buffer_336']?.() - __napiInstance.exports['__napi_register__async_buffer_to_array_337']?.() + __napiInstance.exports['__napi_register__AsyncTaskReadFile_impl_299']?.() + __napiInstance.exports['__napi_register__async_task_read_file_300']?.() + __napiInstance.exports['__napi_register__call_threadsafe_function_301']?.() + __napiInstance.exports['__napi_register__call_long_threadsafe_function_302']?.() + __napiInstance.exports['__napi_register__threadsafe_function_throw_error_303']?.() + __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_304']?.() + __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_305']?.() + __napiInstance.exports['__napi_register__threadsafe_function_closure_capture_306']?.() + __napiInstance.exports['__napi_register__tsfn_call_with_callback_307']?.() + __napiInstance.exports['__napi_register__tsfn_async_call_308']?.() + __napiInstance.exports['__napi_register__accept_threadsafe_function_309']?.() + __napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_310']?.() + __napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_311']?.() + __napiInstance.exports['__napi_register__tsfn_return_promise_312']?.() + __napiInstance.exports['__napi_register__tsfn_return_promise_timeout_313']?.() + __napiInstance.exports['__napi_register__tsfn_throw_from_js_314']?.() + __napiInstance.exports['__napi_register__get_buffer_315']?.() + __napiInstance.exports['__napi_register__append_buffer_316']?.() + __napiInstance.exports['__napi_register__get_empty_buffer_317']?.() + __napiInstance.exports['__napi_register__convert_u32_array_318']?.() + __napiInstance.exports['__napi_register__create_external_typed_array_319']?.() + __napiInstance.exports['__napi_register__mutate_typed_array_320']?.() + __napiInstance.exports['__napi_register__deref_uint8_array_321']?.() + __napiInstance.exports['__napi_register__buffer_pass_through_322']?.() + __napiInstance.exports['__napi_register__array_buffer_pass_through_323']?.() + __napiInstance.exports['__napi_register__accept_slice_324']?.() + __napiInstance.exports['__napi_register__u8_array_to_array_325']?.() + __napiInstance.exports['__napi_register__i8_array_to_array_326']?.() + __napiInstance.exports['__napi_register__u16_array_to_array_327']?.() + __napiInstance.exports['__napi_register__i16_array_to_array_328']?.() + __napiInstance.exports['__napi_register__u32_array_to_array_329']?.() + __napiInstance.exports['__napi_register__i32_array_to_array_330']?.() + __napiInstance.exports['__napi_register__f32_array_to_array_331']?.() + __napiInstance.exports['__napi_register__f64_array_to_array_332']?.() + __napiInstance.exports['__napi_register__u64_array_to_array_333']?.() + __napiInstance.exports['__napi_register__i64_array_to_array_334']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_335']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_336']?.() + __napiInstance.exports['__napi_register__AsyncBuffer_impl_337']?.() + __napiInstance.exports['__napi_register__async_reduce_buffer_338']?.() + __napiInstance.exports['__napi_register__async_buffer_to_array_339']?.() } export const Animal = __napiModule.exports.Animal export const AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor @@ -389,6 +396,7 @@ export const asyncMultiTwo = __napiModule.exports.asyncMultiTwo export const asyncPlus100 = __napiModule.exports.asyncPlus100 export const asyncReduceBuffer = __napiModule.exports.asyncReduceBuffer export const asyncTaskOptionalReturn = __napiModule.exports.asyncTaskOptionalReturn +export const asyncTaskReadFile = __napiModule.exports.asyncTaskReadFile export const asyncTaskVoidReturn = __napiModule.exports.asyncTaskVoidReturn export const bigintAdd = __napiModule.exports.bigintAdd export const bigintFromI128 = __napiModule.exports.bigintFromI128 diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index 246780d9..42e72409 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -3,7 +3,7 @@ /* auto-generated by NAPI-RS */ -const __nodeFs= require('node:fs') +const __nodeFs = require('node:fs') const __nodePath = require('node:path') const { WASI: __nodeWASI } = require('node:wasi') const { Worker } = require('node:worker_threads') @@ -11,6 +11,7 @@ const { Worker } = require('node:worker_threads') const { instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync, getDefaultContext: __emnapiGetDefaultContext, + createOnMessage: __wasmCreateOnMessageForFsProxy, } = require('@napi-rs/wasm-runtime') const __rootDir = __nodePath.parse(process.cwd()).root @@ -57,10 +58,14 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule })(), wasi: __wasi, onCreateWorker() { - return new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), { + const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), { env: process.env, execArgv: ['--experimental-wasi-unstable-preview1'], }) + worker.onmessage = ({ data }) => { + __wasmCreateOnMessageForFsProxy(__nodeFs)(data) + } + return worker }, overwriteImports(importObject) { importObject.env = { @@ -315,45 +320,47 @@ function __napi_rs_initialize_modules(__napiInstance) { __napiInstance.exports['__napi_register__async_task_void_return_296']?.() __napiInstance.exports['__napi_register__AsyncTaskOptionalReturn_impl_297']?.() __napiInstance.exports['__napi_register__async_task_optional_return_298']?.() - __napiInstance.exports['__napi_register__call_threadsafe_function_299']?.() - __napiInstance.exports['__napi_register__call_long_threadsafe_function_300']?.() - __napiInstance.exports['__napi_register__threadsafe_function_throw_error_301']?.() - __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_302']?.() - __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_303']?.() - __napiInstance.exports['__napi_register__threadsafe_function_closure_capture_304']?.() - __napiInstance.exports['__napi_register__tsfn_call_with_callback_305']?.() - __napiInstance.exports['__napi_register__tsfn_async_call_306']?.() - __napiInstance.exports['__napi_register__accept_threadsafe_function_307']?.() - __napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_308']?.() - __napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_309']?.() - __napiInstance.exports['__napi_register__tsfn_return_promise_310']?.() - __napiInstance.exports['__napi_register__tsfn_return_promise_timeout_311']?.() - __napiInstance.exports['__napi_register__tsfn_throw_from_js_312']?.() - __napiInstance.exports['__napi_register__get_buffer_313']?.() - __napiInstance.exports['__napi_register__append_buffer_314']?.() - __napiInstance.exports['__napi_register__get_empty_buffer_315']?.() - __napiInstance.exports['__napi_register__convert_u32_array_316']?.() - __napiInstance.exports['__napi_register__create_external_typed_array_317']?.() - __napiInstance.exports['__napi_register__mutate_typed_array_318']?.() - __napiInstance.exports['__napi_register__deref_uint8_array_319']?.() - __napiInstance.exports['__napi_register__buffer_pass_through_320']?.() - __napiInstance.exports['__napi_register__array_buffer_pass_through_321']?.() - __napiInstance.exports['__napi_register__accept_slice_322']?.() - __napiInstance.exports['__napi_register__u8_array_to_array_323']?.() - __napiInstance.exports['__napi_register__i8_array_to_array_324']?.() - __napiInstance.exports['__napi_register__u16_array_to_array_325']?.() - __napiInstance.exports['__napi_register__i16_array_to_array_326']?.() - __napiInstance.exports['__napi_register__u32_array_to_array_327']?.() - __napiInstance.exports['__napi_register__i32_array_to_array_328']?.() - __napiInstance.exports['__napi_register__f32_array_to_array_329']?.() - __napiInstance.exports['__napi_register__f64_array_to_array_330']?.() - __napiInstance.exports['__napi_register__u64_array_to_array_331']?.() - __napiInstance.exports['__napi_register__i64_array_to_array_332']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_333']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_334']?.() - __napiInstance.exports['__napi_register__AsyncBuffer_impl_335']?.() - __napiInstance.exports['__napi_register__async_reduce_buffer_336']?.() - __napiInstance.exports['__napi_register__async_buffer_to_array_337']?.() + __napiInstance.exports['__napi_register__AsyncTaskReadFile_impl_299']?.() + __napiInstance.exports['__napi_register__async_task_read_file_300']?.() + __napiInstance.exports['__napi_register__call_threadsafe_function_301']?.() + __napiInstance.exports['__napi_register__call_long_threadsafe_function_302']?.() + __napiInstance.exports['__napi_register__threadsafe_function_throw_error_303']?.() + __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_304']?.() + __napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_305']?.() + __napiInstance.exports['__napi_register__threadsafe_function_closure_capture_306']?.() + __napiInstance.exports['__napi_register__tsfn_call_with_callback_307']?.() + __napiInstance.exports['__napi_register__tsfn_async_call_308']?.() + __napiInstance.exports['__napi_register__accept_threadsafe_function_309']?.() + __napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_310']?.() + __napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_311']?.() + __napiInstance.exports['__napi_register__tsfn_return_promise_312']?.() + __napiInstance.exports['__napi_register__tsfn_return_promise_timeout_313']?.() + __napiInstance.exports['__napi_register__tsfn_throw_from_js_314']?.() + __napiInstance.exports['__napi_register__get_buffer_315']?.() + __napiInstance.exports['__napi_register__append_buffer_316']?.() + __napiInstance.exports['__napi_register__get_empty_buffer_317']?.() + __napiInstance.exports['__napi_register__convert_u32_array_318']?.() + __napiInstance.exports['__napi_register__create_external_typed_array_319']?.() + __napiInstance.exports['__napi_register__mutate_typed_array_320']?.() + __napiInstance.exports['__napi_register__deref_uint8_array_321']?.() + __napiInstance.exports['__napi_register__buffer_pass_through_322']?.() + __napiInstance.exports['__napi_register__array_buffer_pass_through_323']?.() + __napiInstance.exports['__napi_register__accept_slice_324']?.() + __napiInstance.exports['__napi_register__u8_array_to_array_325']?.() + __napiInstance.exports['__napi_register__i8_array_to_array_326']?.() + __napiInstance.exports['__napi_register__u16_array_to_array_327']?.() + __napiInstance.exports['__napi_register__i16_array_to_array_328']?.() + __napiInstance.exports['__napi_register__u32_array_to_array_329']?.() + __napiInstance.exports['__napi_register__i32_array_to_array_330']?.() + __napiInstance.exports['__napi_register__f32_array_to_array_331']?.() + __napiInstance.exports['__napi_register__f64_array_to_array_332']?.() + __napiInstance.exports['__napi_register__u64_array_to_array_333']?.() + __napiInstance.exports['__napi_register__i64_array_to_array_334']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_335']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_336']?.() + __napiInstance.exports['__napi_register__AsyncBuffer_impl_337']?.() + __napiInstance.exports['__napi_register__async_reduce_buffer_338']?.() + __napiInstance.exports['__napi_register__async_buffer_to_array_339']?.() } module.exports.Animal = __napiModule.exports.Animal module.exports.AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor @@ -412,6 +419,7 @@ module.exports.asyncMultiTwo = __napiModule.exports.asyncMultiTwo module.exports.asyncPlus100 = __napiModule.exports.asyncPlus100 module.exports.asyncReduceBuffer = __napiModule.exports.asyncReduceBuffer module.exports.asyncTaskOptionalReturn = __napiModule.exports.asyncTaskOptionalReturn +module.exports.asyncTaskReadFile = __napiModule.exports.asyncTaskReadFile module.exports.asyncTaskVoidReturn = __napiModule.exports.asyncTaskVoidReturn module.exports.bigintAdd = __napiModule.exports.bigintAdd module.exports.bigintFromI128 = __napiModule.exports.bigintFromI128 diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index ea72b660..e4fd00d3 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -418,6 +418,7 @@ module.exports.asyncMultiTwo = nativeBinding.asyncMultiTwo module.exports.asyncPlus100 = nativeBinding.asyncPlus100 module.exports.asyncReduceBuffer = nativeBinding.asyncReduceBuffer module.exports.asyncTaskOptionalReturn = nativeBinding.asyncTaskOptionalReturn +module.exports.asyncTaskReadFile = nativeBinding.asyncTaskReadFile module.exports.asyncTaskVoidReturn = nativeBinding.asyncTaskVoidReturn module.exports.bigintAdd = nativeBinding.bigintAdd module.exports.bigintFromI128 = nativeBinding.bigintFromI128 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index a08e3f2d..e3b775c4 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -285,6 +285,8 @@ export function asyncReduceBuffer(buf: Buffer): Promise export function asyncTaskOptionalReturn(): Promise +export function asyncTaskReadFile(path: string): Promise + export function asyncTaskVoidReturn(): Promise export interface B { diff --git a/examples/napi/package.json b/examples/napi/package.json index 48282d78..ca34e9f7 100644 --- a/examples/napi/package.json +++ b/examples/napi/package.json @@ -29,6 +29,7 @@ "@vitest/browser": "^1.2.2", "@vitest/ui": "^1.2.2", "ava": "^6.1.1", + "buffer": "^6.0.3", "cross-env": "7.0.3", "electron": "^30.0.0", "lodash": "^4.17.21", @@ -52,6 +53,6 @@ "timeout": "10m" }, "dependencies": { - "@emnapi/core": "1.1.0" + "@emnapi/core": "^1.1.1" } } diff --git a/examples/napi/src/task.rs b/examples/napi/src/task.rs index 0b23f986..0b3168ec 100644 --- a/examples/napi/src/task.rs +++ b/examples/napi/src/task.rs @@ -70,3 +70,26 @@ impl Task for AsyncTaskOptionalReturn { fn async_task_optional_return() -> AsyncTask { AsyncTask::new(AsyncTaskOptionalReturn {}) } + +pub struct AsyncTaskReadFile { + path: String, +} + +#[napi] +impl Task for AsyncTaskReadFile { + type Output = Vec; + type JsValue = Buffer; + + fn compute(&mut self) -> Result { + std::fs::read(&self.path).map_err(|e| Error::new(Status::GenericFailure, format!("{}", e))) + } + + fn resolve(&mut self, _: Env, output: Self::Output) -> Result { + Ok(output.into()) + } +} + +#[napi] +pub fn async_task_read_file(path: String) -> AsyncTask { + AsyncTask::new(AsyncTaskReadFile { path }) +} diff --git a/examples/napi/vite-entry.js b/examples/napi/vite-entry.js index a825a7d4..75116e7b 100644 --- a/examples/napi/vite-entry.js +++ b/examples/napi/vite-entry.js @@ -1,10 +1,16 @@ +import { Buffer } from 'buffer' + import { Animal, Kind, asyncMultiTwo, tsfnReturnPromise, + __fs, + asyncTaskReadFile, } from './example.wasi-browser' +global.Buffer = Buffer + console.info(new Animal(Kind.Cat, 'Tom')) asyncMultiTwo(200).then((res) => { console.info(res) @@ -17,3 +23,13 @@ const value = await tsfnReturnPromise((err, value) => { }) console.info(value) + +__fs.writeFileSync('/test.txt', 'Hello, World!') + +asyncTaskReadFile('/test.txt') + .then((res) => { + console.log(`readFileAsync: ${res}`) + }) + .catch((err) => { + console.error(err) + }) diff --git a/examples/napi/wasi-worker-browser.mjs b/examples/napi/wasi-worker-browser.mjs index 9f5e224a..cc4469bb 100644 --- a/examples/napi/wasi-worker-browser.mjs +++ b/examples/napi/wasi-worker-browser.mjs @@ -1,16 +1,15 @@ -import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime' -import { Volume, createFsFromVolume } from '@napi-rs/wasm-runtime/fs' +import { instantiateNapiModuleSync, MessageHandler, WASI, createFsProxy } from '@napi-rs/wasm-runtime' +import { memfsExported as __memfsExported } from '@napi-rs/wasm-runtime/fs' -const fs = createFsFromVolume( - Volume.fromJSON({ - '/': null, - }), -) +const fs = createFsProxy(__memfsExported) const handler = new MessageHandler({ onLoad({ wasmModule, wasmMemory }) { const wasi = new WASI({ fs, + preopens: { + '/': '/', + }, print: function () { // eslint-disable-next-line no-console console.log.apply(console, arguments) diff --git a/examples/napi/wasi-worker.mjs b/examples/napi/wasi-worker.mjs index 4824a274..84b448fc 100644 --- a/examples/napi/wasi-worker.mjs +++ b/examples/napi/wasi-worker.mjs @@ -1,5 +1,6 @@ import fs from "node:fs"; import { createRequire } from "node:module"; +import { parse } from "node:path"; import { WASI } from "node:wasi"; import { parentPort, Worker } from "node:worker_threads"; @@ -27,7 +28,9 @@ Object.assign(globalThis, { }, }); -const emnapiContext = getDefaultContext() +const emnapiContext = getDefaultContext(); + +const __rootDir = parse(process.cwd()).root; const handler = new MessageHandler({ onLoad({ wasmModule, wasmMemory }) { @@ -35,7 +38,7 @@ const handler = new MessageHandler({ version: 'preview1', env: process.env, preopens: { - '/': '/', + [__rootDir]: __rootDir, }, }); diff --git a/package.json b/package.json index 4d1b4c78..df41ddbf 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,7 @@ "format:rs": "cargo fmt", "format:toml": "taplo format", "lint": "oxlint --import-plugin --ignore-path=./.oxlintignore --deny-warnings -D correctness -A no-export", - "test": "lerna run test --concurrency=1 --ignore @napi-rs/cli", - "test:without-lerna": "yarn workspaces foreach -A --exclude \"{cli,napi-rs}\" run test", + "test": "yarn workspaces foreach -A --exclude \"{cli,napi-rs}\" run test", "test:bun": "bun test examples/napi/__tests__/values.spec.ts", "test:cli": "yarn workspace @napi-rs/cli test", "test:electron": "electron examples/napi/electron.cjs", diff --git a/wasm-runtime/fs-proxy.cjs b/wasm-runtime/fs-proxy.cjs new file mode 100644 index 00000000..76b1338c --- /dev/null +++ b/wasm-runtime/fs-proxy.cjs @@ -0,0 +1,192 @@ +// @ts-check + +/** + * @param {unknown} value + */ +const getType = (value) => { + if (value === undefined) return 0 + if (value === null) return 1 + const t = typeof value + if (t === 'boolean') return 2 + if (t === 'number') return 3 + if (t === 'string') return 4 + if (t === 'object') return 6 + if (t === 'bigint') return 9 + return -1 +} + +/** + * @param {import('memfs').IFs} memfs + * @param {any} value + * @param {ReturnType} type + * @returns {Uint8Array} + */ +const encodeValue = (memfs, value, type) => { + switch (type) { + case 0: + case 1: + return new Uint8Array(0) + case 2: { + const view = new Int32Array(1) + view[0] = value ? 1 : 0 + return new Uint8Array(view.buffer) + } + case 3: { + const view = new Float64Array(1) + view[0] = value + return new Uint8Array(view.buffer) + } + case 4: { + const view = new TextEncoder().encode(value) + return view + } + case 6: { + const [entry] = Object.entries(memfs).filter(([_, v]) => v === value.constructor)[0] ?? [] + if (entry) { + Object.defineProperty(value, '__constructor__', { + configurable: true, + writable: true, + enumerable: true, + value: entry + }) + } + + const json = JSON.stringify(value, (_, value) => { + if (typeof value === 'bigint') { + return `BigInt(${String(value)})` + } + return value + }) + const view = new TextEncoder().encode(json) + return view + } + case 9: { + const view = new BigInt64Array(1) + view[0] = value + return new Uint8Array(view.buffer) + } + case -1: + default: + throw new Error('unsupported data') + } +} + +/** + * @param {import('memfs').IFs} fs + * @returns {(e: { data: { __fs__: { sab: Int32Array, type: keyof import('memfs').IFs, payload: any[] } } }) => void} + */ +module.exports.createOnMessage = (fs) => function onMessage(e) { + if (e.data.__fs__) { + /** + * 0..4 status(int32_t): 21(waiting) 0(success) 1(error) + * 5..8 type(napi_valuetype): 0(undefined) 1(null) 2(boolean) 3(number) 4(string) 6(jsonstring) 9(bigint) -1(unsupported) + * 9..16 payload_size(uint32_t) <= 1024 + * 16..16 + payload_size payload_content + */ + const { sab, type, payload } = e.data.__fs__ + const fn = fs[type] + const args = payload ? payload.map((value) => { + if (value instanceof Uint8Array) { + // buffer polyfill bug + // @ts-expect-error + value._isBuffer = true + } + return value + }) : payload + try { + const ret = fn.apply(fs, args) + const t = getType(ret) + const v = encodeValue(fs, ret, t) + Atomics.store(sab, 0, 0) + Atomics.store(sab, 1, t) + Atomics.store(sab, 2, v.length) + new Uint8Array(sab.buffer).set(v, 16) + + } catch (/** @type {any} */ err) { + Atomics.store(sab, 0, 1) + Atomics.store(sab, 1, 6) + const payloadContent = new TextEncoder().encode(JSON.stringify({ + ...err, + message: err.message, + stack: err.stack + })) + Atomics.store(sab, 2, payloadContent.length) + new Uint8Array(sab.buffer).set(payloadContent, 16) + } finally { + Atomics.notify(sab, 0) + } + } +} + +/** + * @param {import('memfs').IFs} memfs + */ +module.exports.createFsProxy = (memfs) => new Proxy({}, { + get (_target, p, _receiver) { + /** + * @param {any[]} args + */ + return function (...args) { + const sab = new SharedArrayBuffer(16 + 1024) + const i32arr = new Int32Array(sab) + Atomics.store(i32arr, 0, 21) + + // @ts-expect-error + postMessage({ + __fs__: { + sab: i32arr, + type: p, + payload: args + } + }) + + Atomics.wait(i32arr, 0, 21) + + const status = Atomics.load(i32arr, 0) + const type = Atomics.load(i32arr, 1) + const size = Atomics.load(i32arr, 2) + const content = new Uint8Array(sab, 16, size) + if (status === 1) { + const errobj = JSON.parse(new TextDecoder().decode(content.slice())) + const err = new Error(errobj.message) + Object.defineProperty(err, 'stack', { + configurable: true, + enumerable: false, + writable: true, + value: errobj.stack + }) + for (const [k, v] of Object.entries(errobj)) { + if (k === 'message' || k === 'stack') continue + // @ts-expect-error + err[k] = v + } + throw err + } + if (type === 0) return undefined + if (type === 1) return null + if (type === 2) return Boolean(content[0]) + if (type === 3) return new Float64Array(sab, 16, 1)[0] + if (type === 4) return new TextDecoder().decode(content.slice()) + if (type === 6) { + const obj = JSON.parse(new TextDecoder().decode(content.slice()), (_key, value) => { + if (typeof value === 'string') { + const matched = value.match(/^BigInt\((-?\d+)\)$/) + if (matched && matched[1]) { + return BigInt(matched[1]) + } + } + return value + }) + if (obj.__constructor__) { + const ctor = obj.__constructor__ + delete obj.__constructor__ + // @ts-expect-error + Object.setPrototypeOf(obj, memfs[ctor].prototype) + } + return obj + } + if (type === 9) return new BigInt64Array(sab, 16, 1)[0] + throw new Error('unsupported data') + } + } +}) diff --git a/wasm-runtime/fs.js b/wasm-runtime/fs.js index d75b0b43..368f1f0a 100644 --- a/wasm-runtime/fs.js +++ b/wasm-runtime/fs.js @@ -2,4 +2,4 @@ import * as memfsExported from 'memfs' const { createFsFromVolume, Volume, fs, memfs } = memfsExported -export { createFsFromVolume, Volume, fs, memfs } +export { createFsFromVolume, Volume, fs, memfs, memfsExported } diff --git a/wasm-runtime/package.json b/wasm-runtime/package.json index 009ed5da..4939c10a 100644 --- a/wasm-runtime/package.json +++ b/wasm-runtime/package.json @@ -41,7 +41,7 @@ "dependencies": { "@emnapi/core": "^1.1.0", "@emnapi/runtime": "^1.1.0", - "@tybys/wasm-util": "^0.8.2" + "@tybys/wasm-util": "^0.8.3" }, "scripts": { "build": "rollup -c rollup.config.js" diff --git a/wasm-runtime/rollup.config.js b/wasm-runtime/rollup.config.js index ea0abe68..ecd2f33b 100644 --- a/wasm-runtime/rollup.config.js +++ b/wasm-runtime/rollup.config.js @@ -56,6 +56,7 @@ export default defineConfig([ __webpack_public_path__: undefined, preventAssignment: false, }), + commonjs(), nodeResolve({ preferBuiltins: false, mainFields: ['browser', 'module', 'main'], diff --git a/wasm-runtime/runtime.cjs b/wasm-runtime/runtime.cjs index f1262dab..14495daa 100644 --- a/wasm-runtime/runtime.cjs +++ b/wasm-runtime/runtime.cjs @@ -2,10 +2,14 @@ const { MessageHandler, instantiateNapiModuleSync, instantiateNapiModule } = req const { getDefaultContext } = require('@emnapi/runtime') const { WASI } = require('@tybys/wasm-util') +const { createFsProxy, createOnMessage } = require('./fs-proxy.cjs') + module.exports = { MessageHandler, instantiateNapiModule, instantiateNapiModuleSync, getDefaultContext, WASI, + createFsProxy, + createOnMessage, } diff --git a/wasm-runtime/runtime.js b/wasm-runtime/runtime.js index 582987db..eb0412c1 100644 --- a/wasm-runtime/runtime.js +++ b/wasm-runtime/runtime.js @@ -5,3 +5,4 @@ export { } from '@emnapi/core' export { getDefaultContext } from '@emnapi/runtime' export { WASI } from '@tybys/wasm-util' +export { createOnMessage, createFsProxy } from './fs-proxy.cjs' diff --git a/yarn.lock b/yarn.lock index c2e8aaa8..41728e4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -120,16 +120,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:1.1.0": - version: 1.1.0 - resolution: "@emnapi/core@npm:1.1.0" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/4ce4d214c863ba63eb93532c018b5d8fcf3c39a0a78e29aca76046f06584c0696dabaeb8922a31d876ed20d6b9a34615bbbb0cc1585707ea70972d4e431d546d - languageName: node - linkType: hard - -"@emnapi/core@npm:^1.1.0": +"@emnapi/core@npm:^1.1.0, @emnapi/core@npm:^1.1.1": version: 1.1.1 resolution: "@emnapi/core@npm:1.1.1" dependencies: @@ -322,7 +313,7 @@ __metadata: version: 0.0.0-use.local resolution: "@examples/napi@workspace:examples/napi" dependencies: - "@emnapi/core": "npm:1.1.0" + "@emnapi/core": "npm:^1.1.1" "@napi-rs/cli": "workspace:*" "@napi-rs/triples": "workspace:*" "@napi-rs/wasm-runtime": "workspace:*" @@ -330,6 +321,7 @@ __metadata: "@vitest/browser": "npm:^1.2.2" "@vitest/ui": "npm:^1.2.2" ava: "npm:^6.1.1" + buffer: "npm:^6.0.3" cross-env: "npm:7.0.3" electron: "npm:^30.0.0" lodash: "npm:^4.17.21" @@ -904,7 +896,7 @@ __metadata: "@rollup/plugin-json": "npm:^6.1.0" "@rollup/plugin-node-resolve": "npm:^15.2.3" "@rollup/plugin-replace": "npm:^5.0.5" - "@tybys/wasm-util": "npm:^0.8.2" + "@tybys/wasm-util": "npm:^0.8.3" buffer: "npm:^6.0.3" memfs: "npm:^4.8.2" node-inspect-extracted: "npm:^3.0.0" @@ -2207,12 +2199,12 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.8.1, @tybys/wasm-util@npm:^0.8.2": - version: 0.8.2 - resolution: "@tybys/wasm-util@npm:0.8.2" +"@tybys/wasm-util@npm:^0.8.1, @tybys/wasm-util@npm:^0.8.3": + version: 0.8.3 + resolution: "@tybys/wasm-util@npm:0.8.3" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/3d2c1afa8623625311655b19c28ba6efa30070b86c9e7ad7d7e68295b2bc855f34a383ef39e4be3ee159db70ce28d28343df7885072e831416f16962229ce758 + checksum: 10c0/62ec238710453713a207a2cbcfa74ae5bebc5cd4d718dc34370a7fd5263e8213fe7e24436ec6544105331006f6d0806115956bed030672268dad4a3c123e0f2d languageName: node linkType: hard