webtoon / psd

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

feat!: use Vite, Vitest; use Rust/WebAssembly instead of Asm.js #20

Closed pastelmind closed 2 years ago

pastelmind commented 2 years ago

For the full story, see the second comment.

Summary of changes

For users

For developers

pastelmind commented 2 years ago

Motivation

One of our long-term goals has been using WebAssembly to improve image decoding performance. We initially implemented an asm.js decoder to explore possible performance gains. While we did see a reasonable improvement, we cannot continue to use asm.js for the following reasons:

  1. asm.js is deprecated technology. It has stopped evolving, and is superseded by WebAssembly, which is being actively developed. JavaScript engines may eventually stop supporting it altogether.
  2. Several development tools do not support asm.js. In particular, esbuild does not recognize asm.js syntax; it will mangle asm.js code while bundling, deoptimizing it into vanilla JavaScript code. Anyone who uses esbuild cannot benefit from the performance gains of asm.js. Since esbuild powers several popular tools, such as Vite, we can expect asm.js to become less and less useful over time.

These problems meant that we must adopt WebAssembly at some point; that is now.

The following is a detailed explanation of this pull request. It's long, so hold tight.

Note: This pull request depends on #19.

Use Vite & Vitest instead of Webpack & Jest

Vite is a modern build tool. It supports TypeScript and native ESM better than Webpack, and does not require as many plugins to work. It's also very fast for development builds.

Vitest is a test framework which integrates well with Vite, and handles native ESM better than Jest.

Migrate to Rust/WebAssembly

We rewrote the asm.js decoder in Rust. This is compiled to WebAssembly using wasm-pack. The WASM binary is bundled into the build artifact (dist/index.js) as a base64-encoded string. This means that users can continue to use whatever build tool they were using w/o fear of breaking the library. The downside is that the bundle is considerably larger (~73 KiB), but this shouldn't be a problem if your server uses HTTP compression.

When the library is imported, the WASM binary is decoded asynchronously, then the rest of the module is initialized. @webtoon/psd now exports a Promise named init, which resolves when the initialization is done. Users must ensure that the promise is resolved before accessing other values exported by @webtoon/psd--this includes the default export, too!

import PSD, {init} from '@webtoon/psd';

PSD.parse(/* ... */); // error, the library has not been initialized yet

(async () => {
  await init; // Wait for the library to initialize itself
              // Note that 'init' is NOT a function!
              // await init() <-- this is invalid
  PSD.parse(/* ... */);
})();

// If you can use top-level await, this becomes considerably simpler
await init;
PSD.parse(/* ... */);

// Since 'init' is added to the task queue when the library is imported,
// it should probably be loaded by the time an event handler fires.
// Thus, this should _probably_ be safe.
elem.addEventListener('someEvent', () => {
  PSD.parse(/* ... */);
})

From now on, developers must install wasm-pack on their system to build @webtoon/psd.

Preliminary testing indicates that the new WASM decoder performs comparably to the old decoder. In the future, we hope to gain further performance improvements.