Wrangling TypeScript and webpack to work with Emscripten wasm stuff

This commit is contained in:
Surma
2018-05-17 11:24:40 +01:00
parent 634dfe3717
commit 7edb7f0de8
8 changed files with 170 additions and 1 deletions

1
codecs/mozjpeg_enc/mozjpeg_enc.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export default function(): EmscriptenWasm.Module;

99
emscripten-wasm.d.ts vendored Normal file
View File

@@ -0,0 +1,99 @@
// These types roughly model the object that the JS files generated by Emscripten define. Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten/index.d.ts and turned into a type definition rather than a global to support our way of using Emscripten.
// TODO(surma@): Upstream this?
declare namespace EmscriptenWasm {
type EnvironmentType = "WEB" | "NODE" | "SHELL" | "WORKER";
interface Module {
print(str: string): void;
printErr(str: string): void;
arguments: string[];
environment: EnvironmentType;
preInit: { (): void }[];
preRun: { (): void }[];
postRun: { (): void }[];
preinitializedWebGLContext: WebGLRenderingContext;
noInitialRun: boolean;
noExitRuntime: boolean;
logReadFiles: boolean;
filePackagePrefixURL: string;
wasmBinary: ArrayBuffer;
destroy(object: object): void;
getPreloadedPackage(remotePackageName: string, remotePackageSize: number): ArrayBuffer;
instantiateWasm(
imports: WebAssembly.Imports,
successCallback: (module: WebAssembly.Module) => void
): WebAssembly.Exports;
locateFile(url: string): string;
onCustomMessage(event: MessageEvent): void;
Runtime: any;
ccall(ident: string, returnType: string | null, argTypes: string[], args: any[]): any;
cwrap(ident: string, returnType: string | null, argTypes: string[]): any;
setValue(ptr: number, value: any, type: string, noSafe?: boolean): void;
getValue(ptr: number, type: string, noSafe?: boolean): number;
ALLOC_NORMAL: number;
ALLOC_STACK: number;
ALLOC_STATIC: number;
ALLOC_DYNAMIC: number;
ALLOC_NONE: number;
allocate(slab: any, types: string, allocator: number, ptr: number): number;
allocate(slab: any, types: string[], allocator: number, ptr: number): number;
Pointer_stringify(ptr: number, length?: number): string;
UTF16ToString(ptr: number): string;
stringToUTF16(str: string, outPtr: number): void;
UTF32ToString(ptr: number): string;
stringToUTF32(str: string, outPtr: number): void;
// USE_TYPED_ARRAYS == 1
HEAP: Int32Array;
IHEAP: Int32Array;
FHEAP: Float64Array;
// USE_TYPED_ARRAYS == 2
HEAP8: Int8Array;
HEAP16: Int16Array;
HEAP32: Int32Array;
HEAPU8: Uint8Array;
HEAPU16: Uint16Array;
HEAPU32: Uint32Array;
HEAPF32: Float32Array;
HEAPF64: Float64Array;
TOTAL_STACK: number;
TOTAL_MEMORY: number;
FAST_MEMORY: number;
addOnPreRun(cb: () => any): void;
addOnInit(cb: () => any): void;
addOnPreMain(cb: () => any): void;
addOnExit(cb: () => any): void;
addOnPostRun(cb: () => any): void;
// Tools
intArrayFromString(stringy: string, dontAddNull?: boolean, length?: number): number[];
intArrayToString(array: number[]): string;
writeStringToMemory(str: string, buffer: number, dontAddNull: boolean): void;
writeArrayToMemory(array: number[], buffer: number): void;
writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void;
addRunDependency(id: any): void;
removeRunDependency(id: any): void;
preloadedImages: any;
preloadedAudios: any;
_malloc(size: number): number;
_free(ptr: number): void;
// Augmentations below by surma@
onRuntimeInitialized: () => void | null;
}
}

24
package-lock.json generated
View File

@@ -385,6 +385,12 @@
"integrity": "sha512-4Ba90mWNx8ddbafuyGGwjkZMigi+AWfYLSDCpovwsE63ia8w93r3oJ8PIAQc3y8U+XHcnMOHPIzNe3o438Ywcw==",
"dev": true
},
"@types/webassembly-js-api": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@types/webassembly-js-api/-/webassembly-js-api-0.0.1.tgz",
"integrity": "sha1-YtULIBB319TMEJuxytoi/f1FI/s=",
"dev": true
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -4529,6 +4535,24 @@
"homedir-polyfill": "^1.0.1"
}
},
"exports-loader": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.7.0.tgz",
"integrity": "sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0",
"source-map": "0.5.0"
},
"dependencies": {
"source-map": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz",
"integrity": "sha1-D+llA6yGpa213mP05BKuSHLNvoY=",
"dev": true
}
}
},
"express": {
"version": "4.16.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz",

View File

@@ -4,6 +4,8 @@
"version": "0.0.0",
"license": "apache-2.0",
"scripts": {
"build:mozjpeg_enc": "cd codecs/mozjpeg_enc && npm run build",
"build:codecs": "npm run build:mozjpeg_enc",
"start": "webpack serve --hot",
"build": "webpack -p",
"lint": "eslint src"
@@ -30,6 +32,7 @@
],
"devDependencies": {
"@types/node": "^9.4.7",
"@types/webassembly-js-api": "0.0.1",
"babel-loader": "^7.1.4",
"babel-plugin-jsx-pragmatic": "^1.0.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
@@ -52,6 +55,7 @@
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-standard": "^3.0.1",
"exports-loader": "^0.7.0",
"html-webpack-plugin": "^3.0.6",
"if-env": "^1.0.4",
"loader-utils": "^1.1.0",

View File

@@ -3,6 +3,8 @@ import { bind } from '../../lib/util';
import * as style from './style.scss';
import Output from '../output';
import {MozJpegEncoder} from '../../lib/codec-wrappers/mozjpeg-enc';
type Props = {};
type State = {
@@ -29,7 +31,11 @@ export default class App extends Component<Props, State> {
if (!fileInput.files || !fileInput.files[0]) return;
// TODO: handle decode error
const img = await createImageBitmap(fileInput.files[0]);
this.setState({ img });
const encoder = new MozJpegEncoder();
const compressedData = await encoder.encode(img);
const blob = new Blob([compressedData], {type: 'image/jpeg'});
const compressedImage = await createImageBitmap(blob);
this.setState({ img: compressedImage });
}
render({ }: Props, { img }: State) {

View File

@@ -0,0 +1,7 @@
export interface Encoder {
encode(image: ImageBitmap): Promise<ArrayBuffer>;
}
export interface Decoder {
decode(data: ArrayBuffer): Promise<ImageBitmap>;
}

View File

@@ -0,0 +1,21 @@
import {Encoder} from './codec';
import mozjpeg_enc from '../../../codecs/mozjpeg_enc/mozjpeg_enc';
export class MozJpegEncoder implements Encoder {
private emscriptenModule: Promise<EmscriptenWasm.Module>;
constructor() {
this.emscriptenModule = new Promise(resolve => {
console.log(mozjpeg_enc);
const m = mozjpeg_enc();
m.onRuntimeInitialized = () => resolve(m);
});
}
async encode(bitmap: ImageBitmap): Promise<ArrayBuffer> {
console.log('encoding!');
const m = await this.emscriptenModule;
console.log(m);
return Promise.resolve(<ArrayBuffer>new Uint8Array([1,2,3]).buffer);
}
}

View File

@@ -107,10 +107,17 @@ module.exports = function (_, env) {
loader: 'babel-loader',
// Don't respect any Babel RC files found on the filesystem:
options: Object.assign(readJson('.babelrc'), { babelrc: false })
},
{
// All the codec files define a global with the same name as their file name. `exports-loader` attaches those to `module.exports`.
test: /\/codec\/.*\.js$/,
loader: 'exports-loader',
}
]
},
plugins: [
// Ignore some of the native Node modules for any of the codecs. These files are generated by Emscripten and are supposed to also work in Node, which we dont care about.
new webpack.IgnorePlugin(/(fs)/, /\/codecs\//),
// Pretty progressbar showing build progress:
new ProgressBarPlugin({
format: '\u001b[90m\u001b[44mBuild\u001b[49m\u001b[39m [:bar] \u001b[32m\u001b[1m:percent\u001b[22m\u001b[39m (:elapseds) \u001b[2m:msg\u001b[22m\r',