Skip to content

Commit

Permalink
feat: add wasm build (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Feb 14, 2024
1 parent 96f8279 commit 352e7ad
Show file tree
Hide file tree
Showing 33 changed files with 1,697 additions and 585 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"]
65 changes: 46 additions & 19 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,11 @@ jobs:
export LIB_AOM_PKG_CONFIG_PATH=/usr/lib/pkgconfig &&
yarn workspace @napi-rs/image build --target x86_64-unknown-linux-musl --features with_simd &&
chmod -R 777 target
- host: macos-14
- host: macos-latest
target: aarch64-apple-darwin
setup: |
brew install meson llvm
brew install meson
build: |
export cc=clang
export cxx=clang++
MACOSX_DEPLOYMENT_TARGET=11.0 yarn workspace @napi-rs/image build --target aarch64-apple-darwin --features with_simd
- host: ubuntu-latest
target: aarch64-unknown-linux-gnu
Expand All @@ -110,9 +108,8 @@ 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
build: |
yarn workspace @napi-rs/image build --target armv7-unknown-linux-gnueabihf --features oxipng_libdeflater --zig --zig-link-only
sudo apt-get install meson gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
build: CC="arm-linux-gnueabihf-gcc" 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 +118,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 +127,15 @@ 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"
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,10 +164,11 @@ jobs:
~/.cargo/git/db/
.cargo-cache
.xwin
~/.napi-rs
target/
key: ${{ matrix.settings.target }}-cargo-cache
- uses: goto-bus-stop/setup-zig@v2
if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}
if: ${{ contains(matrix.settings.target, 'musl') }}
with:
version: 0.11.0
- name: Setup toolchain
Expand All @@ -186,7 +192,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 @@ -366,7 +374,6 @@ jobs:
fail-fast: false
matrix:
node:
- '18'
- '20'
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -394,10 +401,7 @@ jobs:
with:
image: node:${{ matrix.node }}-slim
options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build'
run: |
set -e
yarn test
ls -la
run: yarn test
test-linux-arm-gnueabihf-binding:
name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}
needs:
Expand Down Expand Up @@ -434,10 +438,32 @@ jobs:
with:
image: node:${{ matrix.node }}-bullseye-slim
options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build'
run: |
set -e
yarn test
ls -la
run: yarn test
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 +474,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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ resolver = "2"
[profile.release]
lto = 'fat'
codegen-units = 1
strip = 'symbols'
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.38",
"@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/njaard/libavif-rs.git", rev = "678bd68", version = "0.12", default-features = false, features = [
"codec-rav1e",
] }

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

[build-dependencies]
napi-build = "2"
12 changes: 8 additions & 4 deletions packages/binding/__test__/optimize.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
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 { losslessCompressPng, pngQuantize, compressJpeg, Transformer } from '../index.js'

const ROOT_DIR = join(fileURLToPath(import.meta.url), '..', '..', '..', '..')

const PNG = await fs.readFile(join(ROOT_DIR, 'un-optimized.png'))
const PNG = await fs.readFile(join(ROOT_DIR, 'nasa-4928x3279.png'))
const JPEG = await fs.readFile(join(ROOT_DIR, 'un-optimized.jpg'))

test('should be able to lossless optimize png image', async (t) => {
if (process.env.NAPI_RS_FORCE_WASI) {
t.pass()
return
}
const dest = await losslessCompressPng(PNG)
t.true(dest.length < PNG.length)
})
Expand Down
16 changes: 10 additions & 6 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 @@ -43,6 +43,10 @@ test('should be able to encode into webp', async (t) => {
})

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)
Expand All @@ -51,16 +55,16 @@ test('should be able to decode from avif', async (t) => {

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

test('should be able to create transformer from raw rgba pixels', async (t) => {
const pixels = decode('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32)
await t.notThrowsAsync(() => Transformer.fromRgbaPixels(pixels, 32, 32).webpLossless())
await t.notThrowsAsync(() => Transformer.fromRgbaPixels(pixels, 32, 32).webp())
})

test('should be able to create transformer from SVG', async (t) => {
await t.notThrowsAsync(() => Transformer.fromSvg(SVG).png())
await t.notThrowsAsync(() => Transformer.fromSvg(SVG).jpeg())
})
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

0 comments on commit 352e7ad

Please sign in to comment.