[js/web] optimize bundle file size (#9817)

* 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
This commit is contained in:
Yulong Wang 2021-11-22 13:56:55 -08:00 committed by GitHub
parent bcc6ab29f6
commit 74ca417c0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 382 additions and 70 deletions

View file

@ -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

View file

@ -2,6 +2,8 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "dist/lib",
"esModuleInterop": false,
"noUnusedParameters": true,
},
"include": ["lib"]
}

View file

@ -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' }),
];
};

View file

@ -16,8 +16,6 @@
"strictNullChecks": true,
"pretty": true,
"allowUnreachableCode": false,
"experimentalDecorators": true,
"downlevelIteration": true,
"incremental": true
}
}

View file

@ -2,6 +2,8 @@
/script/
/test/
/dist/**/*.report.html
/types/**/*.d.ts
!/types/lib/**/*.d.ts

31
js/web/lib/build-def.ts Normal file
View file

@ -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;

View file

@ -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);
}

View file

@ -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]);

View file

@ -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'});
}
}

147
js/web/package-lock.json generated
View file

@ -87,6 +87,12 @@
"fastq": "^1.6.0"
}
},
"@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -485,6 +491,12 @@
"integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==",
"dev": true
},
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true
},
"agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
@ -918,9 +930,9 @@
"dev": true
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"buffer-xor": {
@ -1574,9 +1586,9 @@
"dev": true
},
"electron": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-12.1.0.tgz",
"integrity": "sha512-joQlYI/nTIrTUldO3GENZ2j225eKar9nTQBSEwSUSWN4h65QGDmXNQ7dbWPmLlkUQWtHhz8lXhFk30OLG9ZjLw==",
"version": "12.2.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-12.2.3.tgz",
"integrity": "sha512-B27c7eqx1bC5kea6An8oVhk1pShNC4VGqWarHMhD47MDtmg54KepHO5AbAvmKKZK/jWN7NTC7wyCYTDElJNtQA==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@ -2280,6 +2292,15 @@
"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="
},
"gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
"dev": true,
"requires": {
"duplexer": "^0.1.2"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -3615,6 +3636,12 @@
"onnxruntime-common": {
"version": "file:../common"
},
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true
},
"os-browserify": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
@ -4258,6 +4285,17 @@
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
"sirv": {
"version": "1.0.18",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.18.tgz",
"integrity": "sha512-f2AOPogZmXgJ9Ma2M22ZEhc1dNtRIzcEkiflMFeVTRq+OViOZMvH1IPMVOwrKaxpSaHioBJiDR0SluRqGa7atA==",
"dev": true,
"requires": {
"@polka/url": "^1.0.0-next.20",
"mime": "^2.3.1",
"totalist": "^1.0.0"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -4311,9 +4349,9 @@
"dev": true
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
@ -4573,14 +4611,14 @@
}
},
"terser": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz",
"integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==",
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
"source-map-support": "~0.5.20"
},
"dependencies": {
"commander": {
@ -4656,6 +4694,12 @@
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
"dev": true
},
"totalist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==",
"dev": true
},
"ts-loader": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.1.2.tgz",
@ -4933,6 +4977,80 @@
"webpack-sources": "^2.1.1"
}
},
"webpack-bundle-analyzer": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz",
"integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==",
"dev": true,
"requires": {
"acorn": "^8.0.4",
"acorn-walk": "^8.0.0",
"chalk": "^4.1.0",
"commander": "^7.2.0",
"gzip-size": "^6.0.0",
"lodash": "^4.17.20",
"opener": "^1.5.2",
"sirv": "^1.0.7",
"ws": "^7.3.1"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"webpack-cli": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.0.tgz",
@ -5245,8 +5363,7 @@
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"resolved": "",
"dev": true
},
"is-fullwidth-code-point": {

View file

@ -45,7 +45,7 @@
"@types/platform": "^1.3.3",
"chai": "^4.3.4",
"dir-compare": "^3.3.0",
"electron": "^12.0.2",
"electron": "^12.2.3",
"fs-extra": "^9.1.0",
"globby": "^11.0.3",
"jszip": "^3.7.1",
@ -67,9 +67,11 @@
"npmlog": "^4.1.2",
"numpy-parser": "^1.2.3",
"strip-json-comments": "^3.1.1",
"terser": "^5.10.0",
"ts-loader": "^9.1.2",
"typescript": "^4.2.4",
"webpack": "^5.36.2",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.7.0",
"worker-loader": "^3.0.8"
},

View file

@ -23,6 +23,12 @@ if (['prod', 'dev', 'perf', 'node'].indexOf(MODE) === -1) {
// --no-wasm
const WASM = typeof args.wasm === 'undefined' ? true : !!args.wasm;
// -a; --analyzer
const ANALYZER = !!args.a || !!args.analyzer;
// -f; --filter=<regex>
const FILTER = args.f || args.filter;
// Path variables
const WASM_BINDING_FOLDER = path.join(__dirname, '..', 'lib', 'wasm', 'binding');
const WASM_BINDING_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm.js');
@ -134,6 +140,12 @@ npmlog.info('Build', 'Building bundle...');
npmlog.info('Build.Bundle', 'Running webpack to generate bundles...');
const webpackCommand = path.join(npmBin, 'webpack');
const webpackArgs = ['--env', `--bundle-mode=${MODE}`];
if (ANALYZER) {
webpackArgs.push('--env', '-a');
}
if (FILTER) {
webpackArgs.push('--env', `-f=${FILTER}`);
}
npmlog.info('Build.Bundle', `CMD: ${webpackCommand} ${webpackArgs.join(' ')}`);
const webpack = spawnSync(webpackCommand, webpackArgs, {shell: true, stdio: 'inherit'});
if (webpack.status !== 0) {

View file

@ -3,7 +3,7 @@
'use strict';
it('Browser E2E testing - WebAssembly backend (no threads, proxy)', async function () {
it('Browser E2E testing - WebAssembly backend (proxy, no threads)', async function () {
ort.env.wasm.numThreads = 1;
ort.env.wasm.proxy = true;
await testFunction(ort, { executionProviders: ['wasm'] });

View file

@ -5,6 +5,7 @@
const args = require('minimist')(process.argv.slice(2));
const SELF_HOST = !!args['self-host'];
const ORT_MAIN = args['ort-main'];
const TEST_MAIN = args['test-main'];
if (typeof TEST_MAIN !== 'string') {
throw new Error('flag --test-main=<TEST_MAIN_JS_FILE> is required');
@ -19,7 +20,7 @@ module.exports = function (config) {
config.set({
frameworks: ['mocha'],
files: [
{ pattern: distPrefix + 'ort.min.js' },
{ pattern: distPrefix + ORT_MAIN },
{ pattern: './common.js' },
{ pattern: TEST_MAIN },
{ pattern: './node_modules/onnxruntime-web/dist/*.wasm', included: false, nocache: true },

View file

@ -96,19 +96,24 @@ async function testAllNodejsCases() {
}
async function testAllBrowserCases({ hostInKarma }) {
await runKarma({ hostInKarma, main: './browser-test-webgl.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-wasm.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-wasm-no-threads.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-wasm-proxy.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-wasm-no-threads-proxy.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-wasm-path-override-filename.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-wasm-path-override-prefix.js', browser: 'Chrome_default' });
await runKarma({ hostInKarma, main: './browser-test-webgl.js'});
await runKarma({ hostInKarma, main: './browser-test-webgl.js', ortMain: 'ort.webgl.min.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm.js', ortMain: 'ort.wasm.min.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-no-threads.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-no-threads.js', ortMain: 'ort.wasm-core.min.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-proxy.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-proxy-no-threads.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-path-override-filename.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-path-override-filename.js', ortMain: 'ort.wasm.min.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-path-override-prefix.js'});
await runKarma({ hostInKarma, main: './browser-test-wasm-path-override-prefix.js', ortMain: 'ort.wasm.min.js'});
}
async function runKarma({ hostInKarma, main, browser }) {
async function runKarma({ hostInKarma, main, browser = 'Chrome_default', ortMain = 'ort.min.js' }) {
const selfHostFlag = hostInKarma ? '--self-host' : '';
await runInShell(
`npx karma start --single-run --browsers ${browser} ${selfHostFlag} --test-main=${main} --user-data=${getNextUserDataDir()}`);
`npx karma start --single-run --browsers ${browser} ${selfHostFlag} --ort-main=${ortMain} --test-main=${main} --user-data=${getNextUserDataDir()}`);
}
async function runInShell(cmd) {

View file

@ -24,6 +24,9 @@ const validRequests = {
// .js files
'/dist/ort.min.js': ['dist/ort.min.js', 'text/javascript'],
'/dist/ort.js': ['dist/ort.js', 'text/javascript'],
'/dist/ort.webgl.min.js': ['dist/ort.webgl.min.js', 'text/javascript'],
'/dist/ort.wasm.min.js': ['dist/ort.wasm.min.js', 'text/javascript'],
'/dist/ort.wasm-core.min.js': ['dist/ort.wasm-core.min.js', 'text/javascript'],
};
module.exports = function (dir) {

View file

@ -2,6 +2,7 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"downlevelIteration": true,
"declarationDir": "./types",
"lib": ["DOM"]
},

View file

@ -5,10 +5,17 @@
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");
const minimist = require('minimist');
// commandline args
const args = minimist(process.argv);
const bundleMode = args['bundle-mode'] || 'prod'; // 'prod'|'dev'|'perf'|'node'|undefined;
const useAnalyzer = !!args.a || !!args['use-analyzer']; // -a, --use-analyzer
const filter = args.f || args.filter;
const VERSION = require(path.join(__dirname, 'package.json')).version;
const COPYRIGHT_BANNER = `/*!
* ONNX Runtime Web v${VERSION}
@ -16,10 +23,25 @@ const COPYRIGHT_BANNER = `/*!
* Licensed under the MIT License.
*/`;
function defaultTerserPluginOptions() {
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 defaultTerserPluginOptions(target) {
return {
extractComments: false,
terserOptions: {
ecma: terserEcmaVersionFromWebpackTarget(target),
format: {
comments: false,
},
@ -33,8 +55,15 @@ function defaultTerserPluginOptions() {
};
}
const DEFAULT_BUILD_DEFS = {
DISABLE_WEBGL: false,
DISABLE_WASM: false,
DISABLE_WASM_PROXY: false,
DISABLE_WASM_THREAD: false,
};
// common config for release bundle
function buildConfig({ filename, format, target, mode, devtool }) {
function buildConfig({ filename, format, target, mode, devtool, build_defs }) {
const config = {
target: [format === 'commonjs' ? 'node' : 'web', target],
entry: path.resolve(__dirname, 'lib/index.ts'),
@ -59,7 +88,10 @@ function buildConfig({ filename, format, target, mode, devtool }) {
"perf_hooks": false,
}
},
plugins: [new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] })],
plugins: [
new webpack.DefinePlugin({ BUILD_DEFS: build_defs }),
new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] })
],
module: {
rules: [{
test: /\.ts$/,
@ -80,13 +112,47 @@ function buildConfig({ filename, format, target, mode, devtool }) {
devtool
};
if (useAnalyzer) {
config.plugins.unshift(new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: `${filename}.report.html`
}));
}
if (mode === 'production') {
config.resolve.alias['./binding/ort-wasm-threaded.js'] = './binding/ort-wasm-threaded.min.js';
config.resolve.alias['./binding/ort-wasm-threaded.worker.js'] = './binding/ort-wasm-threaded.min.worker.js';
const options = defaultTerserPluginOptions();
const options = defaultTerserPluginOptions(target);
options.terserOptions.format.preamble = COPYRIGHT_BANNER;
config.plugins.push(new TerserPlugin(options));
// add a custom plugin to check whether code contains 'BUILD_DEFS'
config.plugins.push({
apply: (compiler) => {
compiler.hooks.afterCompile.tap(
'Check BUILD_DEFS',
(compilation) => {
for (const filename of compilation.assetsInfo.keys()) {
if (filename.endsWith('.js')) {
const asset = compilation.assets[filename];
if (asset) {
const content = asset.source();
if (typeof content !== 'string') {
throw new Error(`content for target file '${filename}' is not string.`);
}
if (content.includes('DISABLE_WEBGL')
|| content.includes('DISABLE_WASM')
|| content.includes('DISABLE_WASM_PROXY')
|| content.includes('DISABLE_WASM_THREAD')) {
throw new Error(`target file '${filename}' contains data fields from "BUILD_DEFS".`);
}
}
}
}
});
}
});
} else {
config.plugins.push(new webpack.BannerPlugin({ banner: COPYRIGHT_BANNER, raw: true }));
}
@ -97,11 +163,12 @@ function buildConfig({ filename, format, target, mode, devtool }) {
// "ort{.min}.js" config
function buildOrtConfig({
suffix = '',
target = 'es5',
target = 'es2017',
mode = 'production',
devtool = 'source-map'
devtool = 'source-map',
build_defs = DEFAULT_BUILD_DEFS
}) {
const config = buildConfig({ filename: `ort${suffix}.js`, format: 'umd', target, mode, devtool });
const config = buildConfig({ filename: `ort${suffix}.js`, format: 'umd', target, mode, devtool, build_defs });
// set global name 'ort'
config.output.library.name = 'ort';
return config;
@ -111,11 +178,12 @@ function buildOrtConfig({
function buildOrtWebConfig({
suffix = '',
format = 'umd',
target = 'es5',
target = 'es2017',
mode = 'production',
devtool = 'source-map'
devtool = 'source-map',
build_defs = DEFAULT_BUILD_DEFS
}) {
const config = buildConfig({ filename: `ort-web${suffix}.js`, format, target, mode, devtool });
const config = buildConfig({ filename: `ort-web${suffix}.js`, format, target, mode, devtool, build_defs });
// exclude onnxruntime-common from bundle
config.externals = {
'onnxruntime-common': {
@ -139,7 +207,7 @@ function buildOrtWebConfig({
function buildTestRunnerConfig({
suffix = '',
format = 'umd',
target = 'es5',
target = 'es2017',
mode = 'production',
devtool = 'source-map'
}) {
@ -174,6 +242,7 @@ function buildTestRunnerConfig({
}
},
plugins: [
new webpack.DefinePlugin({ BUILD_DEFS: DEFAULT_BUILD_DEFS }),
new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }),
new NodePolyfillPlugin({
excludeAliases: ["console"]
@ -186,7 +255,7 @@ function buildTestRunnerConfig({
{
loader: 'ts-loader',
options: {
compilerOptions: { target: target }
compilerOptions: { target }
}
}
]
@ -200,15 +269,13 @@ function buildTestRunnerConfig({
};
if (mode === 'production') {
config.plugins.push(new TerserPlugin(defaultTerserPluginOptions()));
config.plugins.push(new TerserPlugin(defaultTerserPluginOptions(target)));
}
return config;
}
module.exports = () => {
const args = minimist(process.argv);
const bundleMode = args['bundle-mode'] || 'prod'; // 'prod'|'dev'|'perf'|'node'|undefined;
const builds = [];
switch (bundleMode) {
@ -220,8 +287,32 @@ module.exports = () => {
buildOrtConfig({ mode: 'development', devtool: 'inline-source-map' }),
// ort.es6.min.js
buildOrtConfig({ suffix: '.es6.min', target: 'es6' }),
// ort.es6.js
buildOrtConfig({ suffix: '.es6', mode: 'development', devtool: 'inline-source-map', target: 'es6' }),
// ort.es5.min.js
buildOrtConfig({ suffix: '.es5.min', target: 'es5' }),
// ort.wasm.min.js
buildOrtConfig({
suffix: '.wasm.min', build_defs: {
...DEFAULT_BUILD_DEFS,
DISABLE_WEBGL: true,
}
}),
// ort.webgl.min.js
buildOrtConfig({
suffix: '.webgl.min', build_defs: {
...DEFAULT_BUILD_DEFS,
DISABLE_WASM: true,
}
}),
// ort.wasm-core.min.js
buildOrtConfig({
suffix: '.wasm-core.min', build_defs: {
...DEFAULT_BUILD_DEFS,
DISABLE_WEBGL: true,
DISABLE_WASM_PROXY: true,
DISABLE_WASM_THREAD: true,
}
}),
// ort-web.min.js
buildOrtWebConfig({ suffix: '.min' }),
@ -229,8 +320,8 @@ module.exports = () => {
buildOrtWebConfig({ mode: 'development', devtool: 'inline-source-map' }),
// ort-web.es6.min.js
buildOrtWebConfig({ suffix: '.es6.min', target: 'es6' }),
// ort-web.es6.js
buildOrtWebConfig({ suffix: '.es6', mode: 'development', devtool: 'inline-source-map', target: 'es6' }),
// ort-web.es5.min.js
buildOrtWebConfig({ suffix: '.es5.min', target: 'es5' }),
);
case 'node':
@ -249,5 +340,10 @@ module.exports = () => {
throw new Error(`unsupported bundle mode: ${bundleMode}`);
}
if (filter) {
const filterRegex = new RegExp(filter);
return builds.filter(b => filterRegex.test(b.output.filename));
}
return builds;
};