Skip to content

Commit

Permalink
feat: add wasm build
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Feb 8, 2024
1 parent eceba75 commit 0effb1b
Show file tree
Hide file tree
Showing 30 changed files with 1,442 additions and 362 deletions.
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ rustflags = ["-C", "target-feature=+crc"]
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]
[target.wasm32-wasi-preview1-threads]
rustflags = ["-C", "target-feature=+simd128"]
53 changes: 49 additions & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ jobs:
target: armv7-unknown-linux-gnueabihf
setup: |
sudo apt-get update
sudo apt-get install meson gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
sudo apt-get install meson
build: |
yarn workspace @napi-rs/image build --target armv7-unknown-linux-gnueabihf --features oxipng_libdeflater --zig --zig-link-only
yarn workspace @napi-rs/image build --target armv7-unknown-linux-gnueabihf --features oxipng_libdeflater --use-napi-cross
- host: ubuntu-latest
target: aarch64-linux-android
build: |
Expand All @@ -121,7 +121,6 @@ jobs:
yarn workspace @napi-rs/image build --target aarch64-linux-android --features with_simd
- host: ubuntu-latest
target: aarch64-unknown-linux-musl
downloadTarget: aarch64-unknown-linux-musl
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: >-
set -e &&
Expand All @@ -131,6 +130,23 @@ jobs:
apk add --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing --no-cache aom-dev perl meson &&
yarn workspace @napi-rs/image build --target aarch64-unknown-linux-musl --features with_simd &&
chmod -R 777 target
- host: macos-14
target: wasm32-wasi-preview1-threads
setup: |
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-macos.tar.gz
tar -xvf wasi-sdk-21.0-macos.tar.gz
build: |
export WASI_SDK_PATH="$(pwd)/wasi-sdk-21.0"
export CC="${WASI_SDK_PATH}/bin/clang"
export CXX="${WASI_SDK_PATH}/bin/clang++"
export AR="${WASI_SDK_PATH}/bin/ar"
export CFLAGS="--target=wasm32-wasi-threads -pthread --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
export CXXFLAGS="--target=wasm32-wasi-threads -pthread --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
export LDFLAGS="-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads"
export CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER="${WASI_SDK_PATH}/bin/wasm-ld"
export PATH="$WASI_SDK_PATH/bin:$PATH"
yarn workspace @napi-rs/image build --target wasm32-wasi-preview1-threads
name: stable - ${{ matrix.settings.target }} - node@20
runs-on: ${{ matrix.settings.host }}
env:
Expand Down Expand Up @@ -159,6 +175,7 @@ jobs:
~/.cargo/git/db/
.cargo-cache
.xwin
~/.napi-rs
target/
key: ${{ matrix.settings.target }}-cargo-cache
- uses: goto-bus-stop/setup-zig@v2
Expand Down Expand Up @@ -186,7 +203,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.settings.target }}
path: packages/*/*.node
path: |
packages/*/*.node
packages/*/*.wasm
if-no-files-found: error
build-freebsd:
runs-on: macos-12
Expand Down Expand Up @@ -438,6 +457,31 @@ jobs:
set -e
yarn test
ls -la
test-wasi-on-nodejs:
name: Test wasi on Node.js
runs-on: macos-14
needs:
- build
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: bindings-wasm32-wasi-preview1-threads
path: artifacts
- name: Install dependencies
run: yarn install --immutable --mode=skip-build
- name: Move artifacts
run: yarn artifacts
shell: bash
- name: List packages
run: ls -R packages
shell: bash
- name: Run tests
run: yarn test packages/binding/__test__/transformer.spec.mjs -s
env:
NAPI_RS_FORCE_WASI: '1'

publish:
name: Publish
runs-on: ubuntu-latest
Expand All @@ -448,6 +492,7 @@ jobs:
- test-linux-x64-musl-binding
- test-linux-aarch64-gnu-binding
- test-linux-arm-gnueabihf-binding
- test-wasi-on-nodejs
steps:
- uses: actions/checkout@v4
- name: Setup node
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ output-exif.*
nasa-small.*
output-overlay-png.png
output-debian.jpeg
*.wasm
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"website"
],
"devDependencies": {
"@napi-rs/cli": "^2.18.0",
"@napi-rs/cli": "^3.0.0-alpha.36",
"@taplo/cli": "^0.7.0",
"@types/node": "^20.11.16",
"@types/sharp": "^0.31.1",
Expand Down
13 changes: 10 additions & 3 deletions packages/binding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ image = { version = "0.24", default-features = false, features = [
"openexr",
] }
jpeg-decoder = "0.3"
libavif = { version = "0.12", default-features = false, features = [
"codec-aom",
] }
libc = "0.2"
lodepng = "3"
napi = { version = "2", default-features = false, features = ["napi3"] }
Expand Down Expand Up @@ -67,5 +64,15 @@ libwebp-sys = { version = "0.9", default-features = false, features = ["std", "p
[target.'cfg(all(target_os = "macos", target_arch = "x86_64"))'.dependencies]
libwebp-sys = { version = "0.9", default-features = false, features = ["std", "parallel"] }

[target.'cfg(target_family = "wasm")'.dependencies]
libavif = { git="https://github.com/Brooooooklyn/libavif-rs.git", rev = "4e108c4", version = "0.12", default-features = false, features = [
"codec-rav1e",
] }

[target.'cfg(not(target_family = "wasm"))'.dependencies]
libavif = { git="https://github.com/Brooooooklyn/libavif-rs.git", rev = "4e108c4", version = "0.12", default-features = false, features = [
"codec-aom",
] }

[build-dependencies]
napi-build = "2"
6 changes: 3 additions & 3 deletions packages/binding/__test__/optimize.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { promises as fs } from 'fs'
import { join } from 'path'
import { fileURLToPath } from 'url'
import { promises as fs } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'

import test from 'ava'

Expand Down
22 changes: 19 additions & 3 deletions packages/binding/__test__/transformer.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { promises as fs } from 'fs'
import { join } from 'path'
import { fileURLToPath } from 'url'
import { promises as fs } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'

import test from 'ava'
import { decode } from 'blurhash'
Expand Down Expand Up @@ -38,25 +38,41 @@ test('should be able to get exif from jpg', async (t) => {
})

test('should be able to encode into webp', async (t) => {
if (process.env.NAPI_RS_FORCE_WASI) {
t.pass()
return
}
const decoder = new Transformer(PNG)
await t.notThrowsAsync(() => decoder.webp(75))
})

test('should be able to decode from avif', async (t) => {
if (process.env.NAPI_RS_FORCE_WASI) {
t.pass()
return
}
const decoder = new Transformer(PNG)
const AVIF = await decoder.avif()
const avifDecoder = new Transformer(AVIF)
await t.notThrowsAsync(() => avifDecoder.png())
})

test('should be able to decode from webp', async (t) => {
if (process.env.NAPI_RS_FORCE_WASI) {
t.pass()
return
}
const decoder = new Transformer(PNG)
const WEBP = await decoder.webpLossless()
const webpDecoder = new Transformer(WEBP)
await t.notThrowsAsync(() => webpDecoder.png())
})

test('should be able to create transformer from raw rgba pixels', async (t) => {
if (process.env.NAPI_RS_FORCE_WASI) {
t.pass()
return
}
const pixels = decode('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32)
await t.notThrowsAsync(() => Transformer.fromRgbaPixels(pixels, 32, 32).webpLossless())
})
Expand Down
1 change: 1 addition & 0 deletions packages/binding/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '@napi-rs/image-wasm32-wasi'
105 changes: 105 additions & 0 deletions packages/binding/image.wasi-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync,
getDefaultContext as __emnapiGetDefaultContext,
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
import { Volume as __Volume, createFsFromVolume as __createFsFromVolume } from '@napi-rs/wasm-runtime/fs'

import __wasmUrl from './image.wasm32-wasi.wasm?url'

const __fs = __createFsFromVolume(
__Volume.fromJSON({
'/': null,
}),
)

const __wasi = new __WASI({
version: 'preview1',
fs: __fs,
})

const __emnapiContext = __emnapiGetDefaultContext()

const __sharedMemory = new WebAssembly.Memory({
initial: 1024,
maximum: 10240,
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() {
return new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
type: 'module',
})
},
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__AvifConfig_struct_0']?.()
__napiInstance.exports['__napi_register__ChromaSubsampling_1']?.()
__napiInstance.exports['__napi_register__FastResizeFilter_2']?.()
__napiInstance.exports['__napi_register__ResizeFit_3']?.()
__napiInstance.exports['__napi_register__FastResizeOptions_struct_4']?.()
__napiInstance.exports['__napi_register__JpegCompressOptions_struct_5']?.()
__napiInstance.exports['__napi_register__compress_jpeg_sync_6']?.()
__napiInstance.exports['__napi_register__CompressJpegTask_impl_7']?.()
__napiInstance.exports['__napi_register__compress_jpeg_8']?.()
__napiInstance.exports['__napi_register__CompressionType_9']?.()
__napiInstance.exports['__napi_register__FilterType_10']?.()
__napiInstance.exports['__napi_register__PngEncodeOptions_struct_11']?.()
__napiInstance.exports['__napi_register__PngRowFilter_12']?.()
__napiInstance.exports['__napi_register__PNGLosslessOptions_struct_13']?.()
__napiInstance.exports['__napi_register__lossless_compress_png_sync_14']?.()
__napiInstance.exports['__napi_register__LosslessPngTask_impl_15']?.()
__napiInstance.exports['__napi_register__lossless_compress_png_16']?.()
__napiInstance.exports['__napi_register__PngQuantOptions_struct_17']?.()
__napiInstance.exports['__napi_register__png_quantize_sync_18']?.()
__napiInstance.exports['__napi_register__PngQuantTask_impl_19']?.()
__napiInstance.exports['__napi_register__png_quantize_20']?.()
__napiInstance.exports['__napi_register__Orientation_21']?.()
__napiInstance.exports['__napi_register__ResizeFilterType_22']?.()
__napiInstance.exports['__napi_register__JsColorType_23']?.()
__napiInstance.exports['__napi_register__Metadata_struct_24']?.()
__napiInstance.exports['__napi_register__MetadataTask_impl_25']?.()
__napiInstance.exports['__napi_register__ResizeOptions_struct_26']?.()
__napiInstance.exports['__napi_register__EncodeTask_impl_27']?.()
__napiInstance.exports['__napi_register__Transformer_struct_28']?.()
__napiInstance.exports['__napi_register__Transformer_impl_70']?.()
}
export const Transformer = __napiModule.exports.Transformer
export const ChromaSubsampling = __napiModule.exports.ChromaSubsampling
export const CompressionType = __napiModule.exports.CompressionType
export const compressJpeg = __napiModule.exports.compressJpeg
export const compressJpegSync = __napiModule.exports.compressJpegSync
export const FastResizeFilter = __napiModule.exports.FastResizeFilter
export const FilterType = __napiModule.exports.FilterType
export const JsColorType = __napiModule.exports.JsColorType
export const losslessCompressPng = __napiModule.exports.losslessCompressPng
export const losslessCompressPngSync = __napiModule.exports.losslessCompressPngSync
export const Orientation = __napiModule.exports.Orientation
export const pngQuantize = __napiModule.exports.pngQuantize
export const pngQuantizeSync = __napiModule.exports.pngQuantizeSync
export const PngRowFilter = __napiModule.exports.PngRowFilter
export const ResizeFilterType = __napiModule.exports.ResizeFilterType
export const ResizeFit = __napiModule.exports.ResizeFit
Loading

0 comments on commit 0effb1b

Please sign in to comment.