webtoon / psd

Fast zero-dependency PSD parser for the web and Node.js
https://webtoon.github.io/psd
MIT License
1.18k stars 49 forks source link

Investigate providing both WebAssembly- and vanilla JS-based decoders #52

Open pastelmind opened 1 year ago

pastelmind commented 1 year ago

When we replaced the asm.js image decoder with a WebAssembly-based one (#20), we gained performance and resilience† at the expense of increased bundle size and breaking compatibility. This was fine for our use case--but what if people want to use our library in a runtime that does not support WebAssembly (or asm.js for that matter)? What if bundle size is critical?

asm.js is dead tech and there are development tools (e.g. esbuild) that do not support it. See https://github.com/webtoon/psd/pull/20#issuecomment-1153571437

In our internal experiments, a properly optimized vanilla JS decoder was ~15% slower in Chrome (V8), and ~50% slower in Firefox and Safari. Since Node.js uses V8, we may expect similar performance differences in Node.js. These differences may be acceptable to those who want a tiny bundle--or don't have the luxury of WebAssembly.

Perhaps we could provide two decoders (vanilla JS and WebAssembly) and let the user choose?

Brainstorming Strategies

Create separate bundles for vanilla JS and WebAssembly, possibly exposing them as separate endpoints:

import Psd from '@webtoon/psd'         // WebAssembly
import Psd from '@webtoon/psd/vanilla' // Vanilla JS

Create two parse() methods and export them:

import { parse, parseWithJs } from '@webtoon/psd'

const psd1 = parse(arrayBuffer)
psd1.composite() // WebAssembly

const psd2 = parseWithJs(arrayBuffer)
psd2.composite() // Vanilla JS

Create two composite() methods:

import { parse, composite, compositeWithJs } from '@webtoon/psd'

const psd = parse(arrayBuffer)
composite(psd)       // WebAssembly
compositeWithJs(psd) // Vanilla JS
pastelmind commented 1 year ago

We might be able to bundle asm.js code while still supporting esbuild (or Vite) by wrapping the asm.js code in an indirect eval (or a Function constructor). Code transformers usually leave string literals as-is.

It would make the code slightly larger (and complicate the build pipeline) but it could work.