Skip to content

Commit

Permalink
[js/web] optimize bundle file size (#9817)
Browse files Browse the repository at this point in the history
* es2017 by default for ort-common

* add visualizer and define plugin

* es2017 for ort-web. also add build target for es5

* add multiple reduced size build for ort-web

* resolve comments, add e2e tests and add docs
  • Loading branch information
fs-eire authored Nov 22, 2021
1 parent bcc6ab2 commit 74ca417
Showing 18 changed files with 382 additions and 70 deletions.
18 changes: 17 additions & 1 deletion js/README.md
Original file line number Diff line number Diff line change
@@ -298,6 +298,22 @@ In folder `<ORT_ROOT>/js/web`, use command `npm run build:doc` to generate the l
It should be able to consumed by both from projects that uses NPM packages (through a Node.js folder structure of `node_modules` folder that generated by `npm install onnxruntime-web`) and from a CDN service that serves a `ort.min.js` file and one or multiple `.wasm` file(s).
#### Reduced WebAssembly artifacts
By default, the WebAssembly artifacts from onnxruntime-web package allows use of both standard ONNX models (.onnx) and ORT format models (.ort). There is an option to use a minimal build of ONNX Runtime to reduce the binary size, which only supports ORT format models. See also [ORT format model](https://onnxruntime.ai/docs/tutorials/mobile/overview.html) for more information.
#### Reduced JavaScript bundle file fize
By default, the main bundle file `ort.min.js` of ONNX Runtime Web contains all features. However, its size is over 500kB and for some scenarios we want a smaller sized bundle file, if we don't use all the features. The following table lists all available bundles with their support status of features.
|bundle file name|file size|file size (gzipped)|WebGL|WASM-core|WASM-proxy|WASM-threads|ES5 backward compatibility|
|-|-|-|-|------|-----|---|-|
|ort.es5.min.js|594.15KB|134.25KB|O|O|O|O|O|
|ort.min.js|526.02KB|125.07KB|O|O|O|O|X|
|ort.webgl.min.js|385.25KB|83.83KB|O|X|X|X|X|
|ort.wasm.min.js|148.56|44KB|X|O|O|O|X|
|ort.wasm-core.min.js|40.56KB|12.74KB|X|O|X|X|X|
## onnxruntime-react-native
> language: typescript, java, objective-c
@@ -319,7 +335,7 @@ This project provides an ONNX Runtime React Native JavaScript library to run ONN
### Models with ORT format
By default, ONNX Runtime React Native leverages ONNX Runtime Mobile package with ORT format. Follow the [instruciton](https://www.onnxruntime.ai/docs/how-to/mobile/model-conversion.html#converting-onnx-models-to-ort-format) to covert ONNX model to ORT format.
By default, ONNX Runtime React Native leverages ONNX Runtime Mobile package with ORT format. Follow the [instruciton](https://onnxruntime.ai/docs/tutorials/mobile/model-conversion.html) to covert ONNX model to ORT format.
### Build
2 changes: 2 additions & 0 deletions js/common/tsconfig.json
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "dist/lib",
"esModuleInterop": false,
"noUnusedParameters": true,
},
"include": ["lib"]
}
27 changes: 21 additions & 6 deletions js/common/webpack.config.js
Original file line number Diff line number Diff line change
@@ -7,7 +7,21 @@ const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require("terser-webpack-plugin");

function addCopyrightBannerPlugin(mode) {
function terserEcmaVersionFromWebpackTarget(target) {
switch (target) {
case 'es5':
return 5;
case 'es6':
case 'es2015':
return 2015;
case 'es2017':
return 2017;
default:
throw new RangeError(`not supported ECMA version: ${target}`);
}
}

function addCopyrightBannerPlugin(mode, target) {
const VERSION = require(path.join(__dirname, 'package.json')).version;
const COPYRIGHT_BANNER = `/*!
* ONNX Runtime Common v${VERSION}
@@ -19,6 +33,7 @@ function addCopyrightBannerPlugin(mode) {
return new TerserPlugin({
extractComments: false,
terserOptions: {
ecma: terserEcmaVersionFromWebpackTarget(target),
format: {
preamble: COPYRIGHT_BANNER,
comments: false,
@@ -36,7 +51,7 @@ function addCopyrightBannerPlugin(mode) {
function buildConfig({
suffix = '',
format = 'umd',
target = 'es5',
target = 'es2017',
mode = 'production',
devtool = 'source-map'
}) {
@@ -54,7 +69,7 @@ function buildConfig({
resolve: { extensions: ['.ts', '.js'] },
plugins: [
new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }),
addCopyrightBannerPlugin(mode),
addCopyrightBannerPlugin(mode, target),
],
module: {
rules: [{
@@ -63,7 +78,7 @@ function buildConfig({
{
loader: 'ts-loader',
options: {
compilerOptions: { target: target }
compilerOptions: { target }
}
}
]
@@ -76,10 +91,10 @@ function buildConfig({

module.exports = (env, argv) => {
return [
buildConfig({ suffix: '.es6', mode: 'development', devtool: 'inline-source-map', target: 'es6' }),
buildConfig({ mode: 'development', devtool: 'inline-source-map' }),
buildConfig({ suffix: '.es5.min', target: 'es5' }),
buildConfig({ suffix: '.es6.min', target: 'es6' }),
buildConfig({ suffix: '.min' }),
buildConfig({ mode: 'development', devtool: 'inline-source-map' }),
buildConfig({ format: 'commonjs', suffix: '.node' }),
];
};
2 changes: 0 additions & 2 deletions js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -16,8 +16,6 @@
"strictNullChecks": true,
"pretty": true,
"allowUnreachableCode": false,
"experimentalDecorators": true,
"downlevelIteration": true,
"incremental": true
}
}
2 changes: 2 additions & 0 deletions js/web/.npmignore
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
/script/
/test/

/dist/**/*.report.html

/types/**/*.d.ts
!/types/lib/**/*.d.ts

31 changes: 31 additions & 0 deletions js/web/lib/build-def.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/naming-convention */

/**
* The interface BuildDefinitions contains a set of flags which are defined at build time.
*
* Those flags are processed in terser for tree shaking to remove unused code.
* No flags in this file should present in production build.
*/
interface BuildDefinitions {
/**
* defines whether to disable the whole WebGL backend in the build.
*/
DISABLE_WEBGL: boolean;
/**
* defines whether to disable the whole WebAssembly backend in the build.
*/
DISABLE_WASM: boolean;
/**
* defines whether to disable proxy feature in WebAssembly backend in the build.
*/
DISABLE_WASM_PROXY: boolean;
/**
* defines whether to disable multi-threading feature in WebAssembly backend in the build.
*/
DISABLE_WASM_THREAD: boolean;
}

declare let BUILD_DEFS: BuildDefinitions;
17 changes: 13 additions & 4 deletions js/web/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */
// We use "require" instead of "import" here because import statement must be put in top level. Our current code does
// not allow terser to tree-shaking code as expected because some codes are treated as having side effects.
// So we import code inside the if-clause to allow terser remove the code safely.

export * from 'onnxruntime-common';
import {registerBackend} from 'onnxruntime-common';
import {onnxjsBackend} from './backend-onnxjs';
import {wasmBackend} from './backend-wasm';

registerBackend('webgl', onnxjsBackend, -1);
registerBackend('wasm', wasmBackend, 0);
if (!BUILD_DEFS.DISABLE_WEBGL) {
const onnxjsBackend = require('./backend-onnxjs').onnxjsBackend;
registerBackend('webgl', onnxjsBackend, -1);
}
if (!BUILD_DEFS.DISABLE_WASM) {
const wasmBackend = require('./backend-wasm').wasmBackend;
registerBackend('wasm', wasmBackend, 0);
}
12 changes: 6 additions & 6 deletions js/web/lib/wasm/proxy-wrapper.ts
Original file line number Diff line number Diff line change
@@ -82,7 +82,7 @@ const onProxyWorkerMessage = (ev: MessageEvent<OrtWasmMessage>): void => {
const scriptSrc = typeof document !== 'undefined' ? (document?.currentScript as HTMLScriptElement)?.src : undefined;

export const initWasm = async(): Promise<void> => {
if (isProxy()) {
if (!BUILD_DEFS.DISABLE_WASM_PROXY && isProxy()) {
if (initialized) {
return;
}
@@ -118,7 +118,7 @@ export const initWasm = async(): Promise<void> => {
};

export const initOrt = async(numThreads: number, loggingLevel: number): Promise<void> => {
if (isProxy()) {
if (!BUILD_DEFS.DISABLE_WASM_PROXY && isProxy()) {
ensureWorker();
return new Promise<void>((resolve, reject) => {
initOrtCallbacks = [resolve, reject];
@@ -132,7 +132,7 @@ export const initOrt = async(numThreads: number, loggingLevel: number): Promise<

export const createSession =
async(model: Uint8Array, options?: InferenceSession.SessionOptions): Promise<SerializableSessionMetadata> => {
if (isProxy()) {
if (!BUILD_DEFS.DISABLE_WASM_PROXY && isProxy()) {
ensureWorker();
return new Promise<SerializableSessionMetadata>((resolve, reject) => {
createSessionCallbacks.push([resolve, reject]);
@@ -145,7 +145,7 @@ export const createSession =
};

export const releaseSession = async(sessionId: number): Promise<void> => {
if (isProxy()) {
if (!BUILD_DEFS.DISABLE_WASM_PROXY && isProxy()) {
ensureWorker();
return new Promise<void>((resolve, reject) => {
releaseSessionCallbacks.push([resolve, reject]);
@@ -160,7 +160,7 @@ export const releaseSession = async(sessionId: number): Promise<void> => {
export const run = async(
sessionId: number, inputIndices: number[], inputs: SerializableTensor[], outputIndices: number[],
options: InferenceSession.RunOptions): Promise<SerializableTensor[]> => {
if (isProxy()) {
if (!BUILD_DEFS.DISABLE_WASM_PROXY && isProxy()) {
ensureWorker();
return new Promise<SerializableTensor[]>((resolve, reject) => {
runCallbacks.push([resolve, reject]);
@@ -173,7 +173,7 @@ export const run = async(
};

export const endProfiling = async(sessionId: number): Promise<void> => {
if (isProxy()) {
if (!BUILD_DEFS.DISABLE_WASM_PROXY && isProxy()) {
ensureWorker();
return new Promise<void>((resolve, reject) => {
endProfilingCallbacks.push([resolve, reject]);
12 changes: 7 additions & 5 deletions js/web/lib/wasm/wasm-factory.ts
Original file line number Diff line number Diff line change
@@ -6,8 +6,10 @@ import * as path from 'path';

import {OrtWasmModule} from './binding/ort-wasm';
import {OrtWasmThreadedModule} from './binding/ort-wasm-threaded';
import ortWasmFactoryThreaded from './binding/ort-wasm-threaded.js';
import ortWasmFactory from './binding/ort-wasm.js';
const ortWasmFactoryThreaded: EmscriptenModuleFactory<OrtWasmModule> =
// eslint-disable-next-line @typescript-eslint/no-require-imports
!BUILD_DEFS.DISABLE_WASM_THREAD ? require('./binding/ort-wasm-threaded.js') : ortWasmFactory;

let wasm: OrtWasmModule|undefined;
let initialized = false;
@@ -116,7 +118,8 @@ export const initializeWebAssembly = async(flags: Env.WebAssemblyFlags): Promise
const factory = useThreads ? ortWasmFactoryThreaded : ortWasmFactory;
const config: Partial<OrtWasmModule> = {
locateFile: (fileName: string, scriptDirectory: string) => {
if (fileName.endsWith('.worker.js') && typeof Blob !== 'undefined') {
if (!BUILD_DEFS.DISABLE_WASM_THREAD && useThreads && fileName.endsWith('.worker.js') &&
typeof Blob !== 'undefined') {
return URL.createObjectURL(new Blob(
[
// This require() function is handled by webpack to load file content of the corresponding .worker.js
@@ -135,12 +138,11 @@ export const initializeWebAssembly = async(flags: Env.WebAssemblyFlags): Promise
}
};

if (useThreads) {
if (!BUILD_DEFS.DISABLE_WASM_THREAD && useThreads) {
if (typeof Blob === 'undefined') {
config.mainScriptUrlOrBlob = path.join(__dirname, 'ort-wasm-threaded.js');
} else {
const scriptSourceCode =
`var ortWasmThreaded=(function(){var _scriptDir;return ${ortWasmFactoryThreaded.toString()}})();`;
const scriptSourceCode = `var ortWasmThreaded=(function(){var _scriptDir;return ${factory.toString()}})();`;
config.mainScriptUrlOrBlob = new Blob([scriptSourceCode], {type: 'text/javascript'});
}
}
Loading

0 comments on commit 74ca417

Please sign in to comment.