From 3e9e4c99124b577839030596c748189cee1f9419 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Sun, 30 Jun 2024 19:49:02 +0800 Subject: [PATCH] chore(napi/transform): support wasm build (#3933) ~~Encountered the same problem as https://github.com/oxc-project/oxc-resolver/issues/190. Fixed in napi@2.16.7, but oxc use `3.x`~~ Fixed in napi@3.0.0-alpha.3 --- .github/actions/pnpm/action.yml | 16 + .github/workflows/release_napi_transform.yml | 179 +++--- napi/transform/browser.js | 1 + napi/transform/index.d.ts | 66 +- napi/transform/index.js | 617 ++++++++++--------- napi/transform/package.json | 30 +- napi/transform/transform.wasi-browser.js | 66 ++ napi/transform/transform.wasi.cjs | 97 +++ napi/transform/wasi-worker-browser.mjs | 32 + napi/transform/wasi-worker.mjs | 63 ++ npm/oxc-transform/package.json | 24 +- 11 files changed, 763 insertions(+), 428 deletions(-) create mode 100644 .github/actions/pnpm/action.yml create mode 100644 napi/transform/browser.js create mode 100644 napi/transform/transform.wasi-browser.js create mode 100644 napi/transform/transform.wasi.cjs create mode 100644 napi/transform/wasi-worker-browser.mjs create mode 100644 napi/transform/wasi-worker.mjs diff --git a/.github/actions/pnpm/action.yml b/.github/actions/pnpm/action.yml new file mode 100644 index 0000000000000..349f1ac28e0cc --- /dev/null +++ b/.github/actions/pnpm/action.yml @@ -0,0 +1,16 @@ +name: Pnpm + +description: Install pnpm + +runs: + using: composite + steps: + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm + + - run: pnpm install --frozen-lockfile + shell: bash \ No newline at end of file diff --git a/.github/workflows/release_napi_transform.yml b/.github/workflows/release_napi_transform.yml index 68ab38bf672c4..04664f42a6e25 100644 --- a/.github/workflows/release_napi_transform.yml +++ b/.github/workflows/release_napi_transform.yml @@ -52,109 +52,113 @@ jobs: include: - os: windows-latest target: x86_64-pc-windows-msvc - code-target: win32-x64-msvc - os: windows-latest target: aarch64-pc-windows-msvc - code-target: win32-arm64-msvc - os: ubuntu-latest target: x86_64-unknown-linux-gnu - code-target: linux-x64-gnu - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - code-target: linux-arm64-gnu + target: x86_64-unknown-linux-musl - os: ubuntu-latest - target: x86_64-unknown-linux-musl - code-target: linux-x64-musl + target: aarch64-unknown-linux-gnu - os: ubuntu-latest target: aarch64-unknown-linux-musl - code-target: linux-arm64-musl - - os: macos-13 + - os: ubuntu-latest + target: armv7-unknown-linux-gnueabihf + + - os: macos-latest target: x86_64-apple-darwin - code-target: darwin-x64 - - os: macos-14 # M1 + - os: macos-latest target: aarch64-apple-darwin - code-target: darwin-arm64 + + - os: ubuntu-latest + target: wasm32-wasip1-threads name: Package ${{ matrix.target }} runs-on: ${{ matrix.os }} steps: - uses: taiki-e/checkout-action@v1 - ### install musl dependencies ### - - - uses: goto-bus-stop/setup-zig@v2 - if: ${{ contains(matrix.target, 'musl') }} - with: - version: 0.11.0 - - - name: Install cargo-zigbuild - if: ${{ contains(matrix.target, 'musl') }} - uses: taiki-e/install-action@v2 - with: - tool: cargo-zigbuild - - ### install non-musl dependencies ### - - - name: Install cross - if: ${{ !contains(matrix.target, 'musl') }} - uses: taiki-e/install-action@cross - - ### Build + - uses: ./.github/actions/pnpm - name: Add Rust Target run: rustup target add ${{ matrix.target }} - - name: Build with cross - if: ${{ !contains(matrix.target, 'musl') }} - run: cross build --release -p oxc_transform_napi --target=${{ matrix.target }} + - uses: goto-bus-stop/setup-zig@v2 + if: ${{ contains(matrix.target, 'musl') }} + with: + version: 0.13.0 - - name: Build with zig + - name: Build with zig cross if: ${{ contains(matrix.target, 'musl') }} - env: - RUSTFLAGS: "-C target-feature=-crt-static" - run: cargo zigbuild --release -p oxc_transform_napi --target=${{ matrix.target }} + run: pnpm build -x --target ${{ matrix.target }} - ### Build Done + - name: Build with napi cross + if: ${{ contains(matrix.target, 'gnu') }} + run: pnpm build --use-napi-cross --target ${{ matrix.target }} - - name: Move file on ${{ matrix.os }} - run: | - shopt -s extglob - ls target/${{ matrix.target }}/release/*.@(so|dll|dylib) - mv target/${{ matrix.target }}/release/*.@(so|dll|dylib) napi/transform/transform.${{ matrix.code-target }}.node - ls napi/transform + - name: Build + if: ${{ !contains(matrix.target, 'gnu') && !contains(matrix.target, 'musl') }} + run: pnpm build --target ${{ matrix.target }} - name: Test - working-directory: napi/transform - if: ${{ contains(matrix.target, 'x86') && !contains(matrix.target, 'musl') }} # Need docker for aarch64 + if: matrix.target == 'x86_64-pc-windows-msvc' || matrix.target == 'aarch64-apple-darwin' || matrix.target == 'wasm32-wasip1-threads' + run: pnpm test + + - name: Output docker params + if: ${{ contains(matrix.target, 'linux') }} + id: docker run: | - ls - node test.mjs + node -e " + if ('${{ matrix.target }}'.startsWith('aarch64')) { + console.log('PLATFORM=linux/arm64') + } else if ('${{ matrix.target }}'.startsWith('armv7')) { + console.log('PLATFORM=linux/arm/v7') + } else { + console.log('PLATFORM=linux/amd64') + } + " >> $GITHUB_OUTPUT + node -e " + if ('${{ matrix.target }}'.endsWith('-musl')) { + console.log('IMAGE=node:lts-alpine') + } else { + console.log('IMAGE=node:lts-slim') + } + " >> $GITHUB_OUTPUT + echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + if: ${{ contains(matrix.target, 'linux') }} + with: + platforms: all - # The binary is zipped to fix permission loss https://github.com/actions/upload-artifact#permission-loss - - name: Archive Binary - if: runner.os == 'Windows' - run: 7z a ${{ matrix.code-target }}.zip napi/transform/transform.${{ matrix.code-target }}.node + - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + if: ${{ contains(matrix.target, 'linux') }} - # The binary is zipped to fix permission loss https://github.com/actions/upload-artifact#permission-loss - - name: Archive Binary - if: runner.os != 'Windows' - run: tar czf ${{ matrix.code-target }}.tar.gz napi/transform/transform.${{ matrix.code-target }}.node + - name: Run linux test + uses: addnab/docker-run-action@v3 + if: ${{ contains(matrix.target, 'linux') }} + with: + image: ${{ steps.docker.outputs.IMAGE }} + options: -v ${{ steps.docker.outputs.PNPM_STORE_PATH }}:${{ steps.docker.outputs.PNPM_STORE_PATH }} -v ${{ github.workspace }}:/oxc_resolver -w /oxc_resolver --platform ${{ steps.docker.outputs.PLATFORM }} + run: | + corepack enable + pnpm test - - name: Upload artifact + - name: Upload artifacts uses: actions/upload-artifact@v4 with: - if-no-files-found: error - name: binaries-${{ matrix.code-target }} + name: bindings-${{ matrix.target }} path: | - *.zip - *.tar.gz + napi/transform/*.node + napi/transform/*.wasm publish: name: Publish NAPI @@ -166,47 +170,32 @@ jobs: steps: - uses: taiki-e/checkout-action@v1 - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version-file: .node-version - registry-url: 'https://registry.npmjs.org' + - uses: ./.github/actions/pnpm - name: Download Artifacts uses: actions/download-artifact@v4 with: - merge-multiple: true + path: /npm/oxc-transform/artifacts - - name: Unzip - uses: montudor/action-zip@v1 - with: - args: unzip -qq *.zip -d . - - - name: Untar - run: ls *.gz | xargs -i tar xvf {} - - name: Generate npm packages + - name: Prepare dirs and artifacts run: | - ls - ls napi/transform - node npm/oxc-transform/scripts/generate-packages.mjs - cat npm/oxc-transform/package.json - for package in npm/oxc-transform* - do - ls $package - cat $package/package.json - echo '----' - done + pnpm i + pnpm napi create-npm-dirs --cwd ../../npm/oxc-transform + pnpm napi artifacts --cwd ../../npm/oxc-transform + working-directory: /napi/transform + - name: Publish npm packages as latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # NOTE: The trailing slash on $package/ changes it to publishing the directory + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + shell: bash run: | - # publish subpackages first - for package in npm/oxc-transform-* - do - npm publish $package/ --tag latest --provenance --access public - done - # publish root package last - npm publish npm/oxc-transform/ --provenance --access public --tag latest + cp index.js ../../npm/oxc-transform/index.js + cp index.d.ts ../../npm/oxc-transform/index.d.ts + cp browser.js ../../npm/oxc-transform/browser.js + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + pnpm napi pre-publish --cwd ../../npm/oxc-transform --no-gh-release -t npm + npm publish ../../npm/oxc-transform --tag latest --provenance --access public + working-directory: /napi/transform \ No newline at end of file diff --git a/napi/transform/browser.js b/napi/transform/browser.js new file mode 100644 index 0000000000000..7f68e26bb79ec --- /dev/null +++ b/napi/transform/browser.js @@ -0,0 +1 @@ +export * from '@oxc-transform/binding-wasm32-wasi' diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index 42f04ad75ba0f..d0347c62c5982 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -1,15 +1,21 @@ -/* tslint:disable */ +/* auto-generated by NAPI-RS */ /* eslint-disable */ +export interface ArrowFunctionsBindingOptions { + spec: boolean +} -/* auto-generated by NAPI-RS */ +export interface Es2015BindingOptions { + arrowFunction?: ArrowFunctionsBindingOptions +} -export interface TypeScriptBindingOptions { - jsxPragma: string - jsxPragmaFrag: string - onlyRemoveTypeImports: boolean - allowNamespaces: boolean - allowDeclareFields: boolean +/** TypeScript Isolated Declarations for Standalone DTS Emit */ +export function isolatedDeclaration(filename: string, sourceText: string): IsolatedDeclarationsResult + +export interface IsolatedDeclarationsResult { + sourceText: string + errors: Array } + export interface ReactBindingOptions { runtime: 'classic' | 'automatic' development: boolean @@ -21,17 +27,7 @@ export interface ReactBindingOptions { useBuiltIns?: boolean useSpread?: boolean } -export interface ArrowFunctionsBindingOptions { - spec: boolean -} -export interface Es2015BindingOptions { - arrowFunction?: ArrowFunctionsBindingOptions -} -export interface TransformBindingOptions { - typescript: TypeScriptBindingOptions - react: ReactBindingOptions - es2015: Es2015BindingOptions -} + export interface Sourcemap { file?: string mappings?: string @@ -40,15 +36,35 @@ export interface Sourcemap { sourcesContent?: Array names?: Array } + +export function transform(filename: string, sourceText: string, options: TransformBindingOptions): TransformResult + +export interface TransformBindingOptions { + typescript: TypeScriptBindingOptions + react: ReactBindingOptions + es2015: Es2015BindingOptions + /** + * Enable Sourcemaps + * + * * `true` to generate a sourcemap for the code and include it in the result object. + * + * Default: false + */ + sourcemaps: boolean +} + export interface TransformResult { sourceText: string + /** Sourcemap */ map?: Sourcemap errors: Array } -export function transform(filename: string, sourceText: string, options: TransformBindingOptions): TransformResult -export interface IsolatedDeclarationsResult { - sourceText: string - errors: Array + +export interface TypeScriptBindingOptions { + jsxPragma: string + jsxPragmaFrag: string + onlyRemoveTypeImports: boolean + allowNamespaces: boolean + allowDeclareFields: boolean } -/** TypeScript Isolated Declarations for Standalone DTS Emit */ -export function isolatedDeclaration(filename: string, sourceText: string): IsolatedDeclarationsResult + diff --git a/napi/transform/index.js b/napi/transform/index.js index 5220f9bbc7906..e1092b640ae6c 100644 --- a/napi/transform/index.js +++ b/napi/transform/index.js @@ -1,316 +1,365 @@ -/* tslint:disable */ +// prettier-ignore /* eslint-disable */ -/* prettier-ignore */ - /* auto-generated by NAPI-RS */ -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') - -const { platform, arch } = process +const { readFileSync } = require('fs') let nativeBinding = null -let localFileExisted = false -let loadError = null +const loadErrors = [] -function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - const lddPath = require('child_process').execSync('which ldd').toString().trim() - return readFileSync(lddPath, 'utf8').includes('musl') - } catch (e) { +const isMusl = () => { + let musl = false + if (process.platform === 'linux') { + musl = isMuslFromFilesystem() + if (musl === null) { + musl = isMuslFromReport() + } + if (musl === null) { + musl = isMuslFromChildProcess() + } + } + return musl +} + +const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') + +const isMuslFromFilesystem = () => { + try { + return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') + } catch { + return null + } +} + +const isMuslFromReport = () => { + const report = typeof process.report.getReport === 'function' ? process.report.getReport() : null + if (!report) { + return null + } + if (report.header && report.header.glibcVersionRuntime) { + return false + } + if (Array.isArray(report.sharedObjects)) { + if (report.sharedObjects.some(isFileMusl)) { return true } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime } + return false } -switch (platform) { - case 'android': - switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'transform.android-arm64.node')) +const isMuslFromChildProcess = () => { + try { + return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') + } catch (e) { + // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false + return false + } +} + +function requireNative() { + if (process.platform === 'android') { + if (process.arch === 'arm64') { + try { + return require('./transform.android-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-android-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm') { + try { + return require('./transform.android-arm-eabi.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-android-arm-eabi') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) + } + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + try { + return require('./transform.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-win32-x64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'ia32') { + try { + return require('./transform.win32-ia32-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-win32-ia32-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./transform.win32-arm64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-win32-arm64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) + } + } else if (process.platform === 'darwin') { + try { + return require('./transform.darwin-universal.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-darwin-universal') + } catch (e) { + loadErrors.push(e) + } + + if (process.arch === 'x64') { + try { + return require('./transform.darwin-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-darwin-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./transform.darwin-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-darwin-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) + } + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { + try { + return require('./transform.freebsd-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-freebsd-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./transform.freebsd-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-freebsd-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) + } + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { + if (isMusl()) { try { - if (localFileExisted) { - nativeBinding = require('./transform.android-arm64.node') - } else { - nativeBinding = require('@oxc-transform/binding-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'transform.android-arm-eabi.node')) + return require('./transform.linux-x64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-x64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { try { - if (localFileExisted) { - nativeBinding = require('./transform.android-arm-eabi.node') - } else { - nativeBinding = require('@oxc-transform/binding-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Android ${arch}`) - } - break - case 'win32': - switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'transform.win32-x64-msvc.node') - ) + return require('./transform.linux-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-x64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm64') { + if (isMusl()) { try { - if (localFileExisted) { - nativeBinding = require('./transform.win32-x64-msvc.node') - } else { - nativeBinding = require('@oxc-transform/binding-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'transform.win32-ia32-msvc.node') - ) + return require('./transform.linux-arm64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-arm64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { try { - if (localFileExisted) { - nativeBinding = require('./transform.win32-ia32-msvc.node') - } else { - nativeBinding = require('@oxc-transform/binding-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'transform.win32-arm64-msvc.node') - ) + return require('./transform.linux-arm64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-arm64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm') { + if (isMusl()) { try { - if (localFileExisted) { - nativeBinding = require('./transform.win32-arm64-msvc.node') - } else { - nativeBinding = require('@oxc-transform/binding-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) - } - break - case 'darwin': - localFileExisted = existsSync(join(__dirname, 'transform.darwin-universal.node')) - try { - if (localFileExisted) { - nativeBinding = require('./transform.darwin-universal.node') + return require('./transform.linux-arm-musleabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-arm-musleabihf') + } catch (e) { + loadErrors.push(e) + } + } else { - nativeBinding = require('@oxc-transform/binding-darwin-universal') + try { + return require('./transform.linux-arm-gnueabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-arm-gnueabihf') + } catch (e) { + loadErrors.push(e) + } + } - break - } catch {} - switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'transform.darwin-x64.node')) + } else if (process.arch === 'riscv64') { + if (isMusl()) { try { - if (localFileExisted) { - nativeBinding = require('./transform.darwin-x64.node') - } else { - nativeBinding = require('@oxc-transform/binding-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'transform.darwin-arm64.node') - ) + return require('./transform.linux-riscv64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-riscv64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { try { - if (localFileExisted) { - nativeBinding = require('./transform.darwin-arm64.node') - } else { - nativeBinding = require('@oxc-transform/binding-darwin-arm64') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) + return require('./transform.linux-riscv64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-riscv64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'ppc64') { + try { + return require('./transform.linux-ppc64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-ppc64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 's390x') { + try { + return require('./transform.linux-s390x-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@oxc-transform/binding-linux-s390x-gnu') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) } - break - case 'freebsd': - if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) + } else { + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) + } +} + +nativeBinding = requireNative() + +if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + try { + nativeBinding = require('./transform.wasi.cjs') + } catch (err) { + if (process.env.NAPI_RS_FORCE_WASI) { + console.error(err) } - localFileExisted = existsSync(join(__dirname, 'transform.freebsd-x64.node')) + } + if (!nativeBinding) { try { - if (localFileExisted) { - nativeBinding = require('./transform.freebsd-x64.node') - } else { - nativeBinding = require('@oxc-transform/binding-freebsd-x64') + nativeBinding = require('@oxc-transform/binding-wasm32-wasi') + } catch (err) { + if (process.env.NAPI_RS_FORCE_WASI) { + console.error(err) } - } catch (e) { - loadError = e - } - break - case 'linux': - switch (arch) { - case 'x64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-x64-musl.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-x64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-x64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-x64-gnu.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-x64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-arm64-musl.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-arm64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-arm64-gnu.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-arm64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-arm-musleabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-arm-musleabihf.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-arm-musleabihf') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-arm-gnueabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-arm-gnueabihf.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-arm-gnueabihf') - } - } catch (e) { - loadError = e - } - } - break - case 'riscv64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-riscv64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-riscv64-musl.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-riscv64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'transform.linux-riscv64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-riscv64-gnu.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-riscv64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 's390x': - localFileExisted = existsSync( - join(__dirname, 'transform.linux-s390x-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./transform.linux-s390x-gnu.node') - } else { - nativeBinding = require('@oxc-transform/binding-linux-s390x-gnu') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) } - break - default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + } } if (!nativeBinding) { - if (loadError) { - throw loadError + if (loadErrors.length > 0) { + // TODO Link to documentation with potential fixes + // - The package owner could build/publish bindings for this arch + // - The user may need to bundle the correct files + // - The user may need to re-install node_modules to get new packages + throw new Error('Failed to load native binding', { cause: loadErrors }) } throw new Error(`Failed to load native binding`) } -const { transform, isolatedDeclaration } = nativeBinding - -module.exports.transform = transform -module.exports.isolatedDeclaration = isolatedDeclaration +module.exports.isolatedDeclaration = nativeBinding.isolatedDeclaration +module.exports.transform = nativeBinding.transform diff --git a/napi/transform/package.json b/napi/transform/package.json index 63099b0d565cc..90cf26c949893 100644 --- a/napi/transform/package.json +++ b/napi/transform/package.json @@ -1,30 +1,16 @@ { "name": "@oxc-transform/binding", "private": true, + "packageManager": "pnpm@9.4.0", "scripts": { - "build": "napi build --platform --release", + "build": "napi build --platform --release --package-json-path ../../npm/oxc-transform/package.json --manifest-path ./Cargo.toml", + "build:debug": "napi build --platform --package-json-path ../../npm/oxc-transform/package.json --manifest-path ./Cargo.toml", "test": "node test.mjs" }, "devDependencies": { - "@napi-rs/cli": "^2.18.0" - }, - "engines": { - "node": ">=14.*" - }, - "napi": { - "name": "transform", - "triples": { - "defaults": false, - "additional": [ - "x86_64-pc-windows-msvc", - "aarch64-pc-windows-msvc", - "x86_64-unknown-linux-gnu", - "aarch64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", - "aarch64-unknown-linux-musl", - "x86_64-apple-darwin", - "aarch64-apple-darwin" - ] - } + "@napi-rs/cli": "^3.0.0-alpha.43", + "@napi-rs/wasm-runtime": "^0.2.0", + "@types/node": "^20.10.4", + "emnapi": "^1.0.0" } -} +} \ No newline at end of file diff --git a/napi/transform/transform.wasi-browser.js b/napi/transform/transform.wasi-browser.js new file mode 100644 index 0000000000000..a09cba40846ad --- /dev/null +++ b/napi/transform/transform.wasi-browser.js @@ -0,0 +1,66 @@ +import { + instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync, + getDefaultContext as __emnapiGetDefaultContext, + WASI as __WASI, + createOnMessage as __wasmCreateOnMessageForFsProxy, +} from '@napi-rs/wasm-runtime' + +import __wasmUrl from './transform.wasm32-wasi.wasm?url' + +const __wasi = new __WASI({ + version: 'preview1', +}) + +const __emnapiContext = __emnapiGetDefaultContext() + +const __sharedMemory = new WebAssembly.Memory({ + initial: 4000, + maximum: 65536, + shared: true, +}) + +const __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer()) + +const { + instance: __napiInstance, + module: __wasiModule, + napiModule: __napiModule, +} = __emnapiInstantiateNapiModuleSync(__wasmFile, { + context: __emnapiContext, + asyncWorkPoolSize: 4, + wasi: __wasi, + onCreateWorker() { + const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), { + type: 'module', + }) + + return worker + }, + overwriteImports(importObject) { + importObject.env = { + ...importObject.env, + ...importObject.napi, + ...importObject.emnapi, + memory: __sharedMemory, + } + return importObject + }, + beforeInit({ instance }) { + __napi_rs_initialize_modules(instance) + }, +}) + +function __napi_rs_initialize_modules(__napiInstance) { + __napiInstance.exports['__napi_register__TypeScriptBindingOptions_struct_0']?.() + __napiInstance.exports['__napi_register__ReactBindingOptions_struct_1']?.() + __napiInstance.exports['__napi_register__ArrowFunctionsBindingOptions_struct_2']?.() + __napiInstance.exports['__napi_register__ES2015BindingOptions_struct_3']?.() + __napiInstance.exports['__napi_register__TransformBindingOptions_struct_4']?.() + __napiInstance.exports['__napi_register__Sourcemap_struct_5']?.() + __napiInstance.exports['__napi_register__TransformResult_struct_6']?.() + __napiInstance.exports['__napi_register__transform_7']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_8']?.() + __napiInstance.exports['__napi_register__isolated_declaration_9']?.() +} +export const isolatedDeclaration = __napiModule.exports.isolatedDeclaration +export const transform = __napiModule.exports.transform diff --git a/napi/transform/transform.wasi.cjs b/napi/transform/transform.wasi.cjs new file mode 100644 index 0000000000000..d6c4a84fbab36 --- /dev/null +++ b/napi/transform/transform.wasi.cjs @@ -0,0 +1,97 @@ +/* eslint-disable */ +/* prettier-ignore */ + +/* auto-generated by NAPI-RS */ + +const __nodeFs = require('node:fs') +const __nodePath = require('node:path') +const { WASI: __nodeWASI } = require('node:wasi') +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 + +const __wasi = new __nodeWASI({ + version: 'preview1', + env: process.env, + preopens: { + [__rootDir]: __rootDir, + } +}) + +const __emnapiContext = __emnapiGetDefaultContext() + +const __sharedMemory = new WebAssembly.Memory({ + initial: 4000, + maximum: 65536, + shared: true, +}) + +let __wasmFilePath = __nodePath.join(__dirname, 'transform.wasm32-wasi.wasm') +const __wasmDebugFilePath = __nodePath.join(__dirname, 'transform.wasm32-wasi.debug.wasm') + +if (__nodeFs.existsSync(__wasmDebugFilePath)) { + __wasmFilePath = __wasmDebugFilePath +} else if (!__nodeFs.existsSync(__wasmFilePath)) { + try { + __wasmFilePath = __nodePath.resolve('@oxc-transform/binding-wasm32-wasi') + } catch { + throw new Error('Cannot find transform.wasm32-wasi.wasm file, and @oxc-transform/binding-wasm32-wasi package is not installed.') + } +} + +const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule } = __emnapiInstantiateNapiModuleSync(__nodeFs.readFileSync(__wasmFilePath), { + context: __emnapiContext, + asyncWorkPoolSize: (function() { + const threadsSizeFromEnv = Number(process.env.NAPI_RS_ASYNC_WORK_POOL_SIZE ?? process.env.UV_THREADPOOL_SIZE) + // NaN > 0 is false + if (threadsSizeFromEnv > 0) { + return threadsSizeFromEnv + } else { + return 4 + } + })(), + wasi: __wasi, + onCreateWorker() { + 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 = { + ...importObject.env, + ...importObject.napi, + ...importObject.emnapi, + memory: __sharedMemory, + } + return importObject + }, + beforeInit({ instance }) { + __napi_rs_initialize_modules(instance) + } +}) + +function __napi_rs_initialize_modules(__napiInstance) { + __napiInstance.exports['__napi_register__TypeScriptBindingOptions_struct_0']?.() + __napiInstance.exports['__napi_register__ReactBindingOptions_struct_1']?.() + __napiInstance.exports['__napi_register__ArrowFunctionsBindingOptions_struct_2']?.() + __napiInstance.exports['__napi_register__ES2015BindingOptions_struct_3']?.() + __napiInstance.exports['__napi_register__TransformBindingOptions_struct_4']?.() + __napiInstance.exports['__napi_register__Sourcemap_struct_5']?.() + __napiInstance.exports['__napi_register__TransformResult_struct_6']?.() + __napiInstance.exports['__napi_register__transform_7']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_8']?.() + __napiInstance.exports['__napi_register__isolated_declaration_9']?.() +} +module.exports.isolatedDeclaration = __napiModule.exports.isolatedDeclaration +module.exports.transform = __napiModule.exports.transform diff --git a/napi/transform/wasi-worker-browser.mjs b/napi/transform/wasi-worker-browser.mjs new file mode 100644 index 0000000000000..8b1b1722177fc --- /dev/null +++ b/napi/transform/wasi-worker-browser.mjs @@ -0,0 +1,32 @@ +import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime' + +const handler = new MessageHandler({ + onLoad({ wasmModule, wasmMemory }) { + const wasi = new WASI({ + print: function () { + // eslint-disable-next-line no-console + console.log.apply(console, arguments) + }, + printErr: function() { + // eslint-disable-next-line no-console + console.error.apply(console, arguments) + }, + }) + return instantiateNapiModuleSync(wasmModule, { + childThread: true, + wasi, + overwriteImports(importObject) { + importObject.env = { + ...importObject.env, + ...importObject.napi, + ...importObject.emnapi, + memory: wasmMemory, + } + }, + }) + }, +}) + +globalThis.onmessage = function (e) { + handler.handle(e) +} diff --git a/napi/transform/wasi-worker.mjs b/napi/transform/wasi-worker.mjs new file mode 100644 index 0000000000000..84b448fcc52ab --- /dev/null +++ b/napi/transform/wasi-worker.mjs @@ -0,0 +1,63 @@ +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"; + +const require = createRequire(import.meta.url); + +const { instantiateNapiModuleSync, MessageHandler, getDefaultContext } = require("@napi-rs/wasm-runtime"); + +if (parentPort) { + parentPort.on("message", (data) => { + globalThis.onmessage({ data }); + }); +} + +Object.assign(globalThis, { + self: globalThis, + require, + Worker, + importScripts: function (f) { + ;(0, eval)(fs.readFileSync(f, "utf8") + "//# sourceURL=" + f); + }, + postMessage: function (msg) { + if (parentPort) { + parentPort.postMessage(msg); + } + }, +}); + +const emnapiContext = getDefaultContext(); + +const __rootDir = parse(process.cwd()).root; + +const handler = new MessageHandler({ + onLoad({ wasmModule, wasmMemory }) { + const wasi = new WASI({ + version: 'preview1', + env: process.env, + preopens: { + [__rootDir]: __rootDir, + }, + }); + + return instantiateNapiModuleSync(wasmModule, { + childThread: true, + wasi, + context: emnapiContext, + overwriteImports(importObject) { + importObject.env = { + ...importObject.env, + ...importObject.napi, + ...importObject.emnapi, + memory: wasmMemory + }; + }, + }); + }, +}); + +globalThis.onmessage = function (e) { + handler.handle(e); +}; diff --git a/npm/oxc-transform/package.json b/npm/oxc-transform/package.json index 6b8a8240aa71f..929aa3d7542f8 100644 --- a/npm/oxc-transform/package.json +++ b/npm/oxc-transform/package.json @@ -5,6 +5,7 @@ "keywords": [ "transform" ], + "packageManager": "pnpm@9.4.0", "author": "Boshen and oxc contributors", "license": "MIT", "homepage": "https://oxc.rs", @@ -18,8 +19,27 @@ "url": "https://github.com/sponsors/Boshen" }, "main": "index.js", + "types": "index.d.ts", + "browser": "browser.js", "files": [ "index.d.ts", - "index.js" - ] + "index.js", + "browser.js" + ], + "napi": { + "binaryName": "transform", + "packageName": "@oxc-transform/binding", + "targets": [ + "x86_64-pc-windows-msvc", + "aarch64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "armv7-unknown-linux-gnueabihf", + "x86_64-apple-darwin", + "aarch64-apple-darwin", + "wasm32-wasip1-threads" + ] + } } \ No newline at end of file