diff --git a/libsquoosh/lib/simple-ts.js b/libsquoosh/lib/simple-ts.js
new file mode 100644
index 00000000..98e0d4c9
--- /dev/null
+++ b/libsquoosh/lib/simple-ts.js
@@ -0,0 +1,133 @@
+/**
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { spawn } from 'child_process';
+import { relative, join } from 'path';
+import { promises as fsp } from 'fs';
+import { promisify } from 'util';
+
+import * as ts from 'typescript';
+import glob from 'glob';
+import { sync as whichSync } from 'which';
+
+const globP = promisify(glob);
+
+const tscPath = whichSync('tsc');
+
+const extRe = /\.tsx?$/;
+
+function loadConfig(mainPath) {
+ const fileName = ts.findConfigFile(mainPath, ts.sys.fileExists);
+ if (!fileName) throw Error('tsconfig not found');
+ const text = ts.sys.readFile(fileName);
+ const loadedConfig = ts.parseConfigFileTextToJson(fileName, text).config;
+ const parsedTsConfig = ts.parseJsonConfigFileContent(
+ loadedConfig,
+ ts.sys,
+ process.cwd(),
+ undefined,
+ fileName,
+ );
+ return parsedTsConfig;
+}
+
+export default function simpleTS(mainPath, { noBuild, watch } = {}) {
+ const config = loadConfig(mainPath);
+ const args = ['-b', mainPath];
+
+ let tsBuildDone;
+
+ async function watchBuiltFiles(rollupContext) {
+ const matches = await globP(config.options.outDir + '/**/*.js');
+ for (const match of matches) rollupContext.addWatchFile(match);
+ }
+
+ async function tsBuild(rollupContext) {
+ if (tsBuildDone) {
+ // Watch lists are cleared on each build, so we need to rewatch all the JS files.
+ await watchBuiltFiles(rollupContext);
+ return tsBuildDone;
+ }
+ if (noBuild) {
+ return (tsBuildDone = Promise.resolve());
+ }
+ tsBuildDone = Promise.resolve().then(async () => {
+ await new Promise((resolve) => {
+ const proc = spawn(tscPath, args, {
+ stdio: 'inherit',
+ });
+
+ proc.on('exit', (code) => {
+ if (code !== 0) {
+ throw Error('TypeScript build failed');
+ }
+ resolve();
+ });
+ });
+
+ await watchBuiltFiles(rollupContext);
+
+ if (watch) {
+ tsBuildDone.then(() => {
+ spawn(tscPath, [...args, '--watch', '--preserveWatchOutput'], {
+ stdio: 'inherit',
+ });
+ });
+ }
+ });
+
+ return tsBuildDone;
+ }
+
+ return {
+ name: 'simple-ts',
+ resolveId(id, importer) {
+ // If there isn't an importer, it's an entry point, so we don't need to resolve it relative
+ // to something.
+ if (!importer) return null;
+
+ const tsResolve = ts.resolveModuleName(
+ id,
+ importer,
+ config.options,
+ ts.sys,
+ );
+
+ if (
+ // It didn't find anything
+ !tsResolve.resolvedModule ||
+ // Or if it's linking to a definition file, it's something in node_modules,
+ // or something local like css.d.ts
+ tsResolve.resolvedModule.extension === '.d.ts'
+ ) {
+ return null;
+ }
+ return tsResolve.resolvedModule.resolvedFileName;
+ },
+ async load(id) {
+ if (!extRe.test(id)) return null;
+
+ // TypeScript building is deferred until the first TS file load.
+ // This allows prerequisites to happen first,
+ // such as css.d.ts generation in css-plugin.
+ await tsBuild(this);
+
+ // Look for the JS equivalent in the tmp folder
+ const newId = join(
+ config.options.outDir,
+ relative(config.options.rootDir, id),
+ ).replace(extRe, '.js');
+
+ return fsp.readFile(newId, { encoding: 'utf8' });
+ },
+ };
+}
diff --git a/libsquoosh/package-lock.json b/libsquoosh/package-lock.json
index 3eb66bd2..bd899243 100644
--- a/libsquoosh/package-lock.json
+++ b/libsquoosh/package-lock.json
@@ -1125,9 +1125,9 @@
"dev": true
},
"@types/node": {
- "version": "15.0.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz",
- "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==",
+ "version": "15.6.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
+ "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==",
"dev": true
},
"@types/resolve": {
@@ -1492,6 +1492,12 @@
"@types/estree": "*"
}
},
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
"jest-worker": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
@@ -1826,6 +1832,12 @@
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
+ "typescript": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz",
+ "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==",
+ "dev": true
+ },
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
@@ -1859,6 +1871,15 @@
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.0.3.tgz",
"integrity": "sha512-d2H/t0eqRNM4w2WvmTdoeIvzAUSpK7JmATB8Nr2lb7nQ9BTIJVjbQ/TRFVEh2gUH1HwclPdoPtfMoFfetXaZnA=="
},
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/libsquoosh/package.json b/libsquoosh/package.json
index bb038b5e..6a333f40 100644
--- a/libsquoosh/package.json
+++ b/libsquoosh/package.json
@@ -22,7 +22,10 @@
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
+ "@types/node": "^15.6.1",
"rollup": "^2.46.0",
- "rollup-plugin-terser": "^7.0.2"
+ "rollup-plugin-terser": "^7.0.2",
+ "typescript": "^4.1.3",
+ "which": "^2.0.2"
}
}
diff --git a/libsquoosh/rollup.config.js b/libsquoosh/rollup.config.js
index 9c619411..d6d6537d 100644
--- a/libsquoosh/rollup.config.js
+++ b/libsquoosh/rollup.config.js
@@ -1,5 +1,6 @@
import resolve from '@rollup/plugin-node-resolve';
import cjs from '@rollup/plugin-commonjs';
+import simpleTS from './lib/simple-ts';
import asset from './lib/asset-plugin.js';
import json from './lib/json-plugin.js';
import autojson from './lib/autojson-plugin.js';
@@ -20,6 +21,7 @@ export default {
asset(),
autojson(),
json(),
+ simpleTS('.'),
getBabelOutputPlugin({
babelrc: false,
configFile: false,
diff --git a/libsquoosh/src/image_data.js b/libsquoosh/src/image_data.js
deleted file mode 100644
index 7d7736be..00000000
--- a/libsquoosh/src/image_data.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default class ImageData {
- constructor(data, width, height) {
- this.data = data;
- this.width = width;
- this.height = height;
- }
-}
diff --git a/libsquoosh/src/image_data.ts b/libsquoosh/src/image_data.ts
new file mode 100644
index 00000000..9125199f
--- /dev/null
+++ b/libsquoosh/src/image_data.ts
@@ -0,0 +1,11 @@
+export default class ImageData {
+ readonly data: Uint8ClampedArray;
+ readonly width: number;
+ readonly height: number;
+
+ constructor(data: Uint8ClampedArray, width: number, height: number) {
+ this.data = data;
+ this.width = width;
+ this.height = height;
+ }
+}
diff --git a/libsquoosh/src/missing-types.d.ts b/libsquoosh/src/missing-types.d.ts
new file mode 100644
index 00000000..09d35510
--- /dev/null
+++ b/libsquoosh/src/missing-types.d.ts
@@ -0,0 +1,178 @@
+/**
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///
+
+// These don't exist in NodeJS types so we're not able to use them but they are referenced in some emscripten and codec types
+// Thus, we need to explicitly assign them to be `never`
+// We're also not able to use the APIs that use these types
+// So, if we want to use those APIs we need to supply its dependencies ourselves
+// However, probably those APIs are more suited to be used in web (i.e. there can be other
+// dependencies to web APIs that might not work in Node)
+type RequestInfo = never;
+type Response = never;
+type WebGLRenderingContext = never;
+type MessageEvent = never;
+
+type BufferSource = ArrayBufferView | ArrayBuffer;
+type URL = import('url').URL;
+
+/*
+ * Matches the implementation in `image_data.ts` which is exposed to globalThis
+ */
+interface ImageData {
+ /**
+ * Returns the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255.
+ */
+ readonly data: Uint8ClampedArray;
+ /**
+ * Returns the actual dimensions of the data in the ImageData object, in pixels.
+ */
+ readonly height: number;
+ /**
+ * Returns the actual dimensions of the data in the ImageData object, in pixels.
+ */
+ readonly width: number;
+}
+
+/**
+ * WebAssembly definitions are not available in `@types/node` yet,
+ * so these are copied from `lib.dom.d.ts`
+ */
+declare namespace WebAssembly {
+ interface CompileError {}
+
+ var CompileError: {
+ prototype: CompileError;
+ new (): CompileError;
+ };
+
+ interface Global {
+ value: any;
+ valueOf(): any;
+ }
+
+ var Global: {
+ prototype: Global;
+ new (descriptor: GlobalDescriptor, v?: any): Global;
+ };
+
+ interface Instance {
+ readonly exports: Exports;
+ }
+
+ var Instance: {
+ prototype: Instance;
+ new (module: Module, importObject?: Imports): Instance;
+ };
+
+ interface LinkError {}
+
+ var LinkError: {
+ prototype: LinkError;
+ new (): LinkError;
+ };
+
+ interface Memory {
+ readonly buffer: ArrayBuffer;
+ grow(delta: number): number;
+ }
+
+ var Memory: {
+ prototype: Memory;
+ new (descriptor: MemoryDescriptor): Memory;
+ };
+
+ interface Module {}
+
+ var Module: {
+ prototype: Module;
+ new (bytes: BufferSource): Module;
+ customSections(moduleObject: Module, sectionName: string): ArrayBuffer[];
+ exports(moduleObject: Module): ModuleExportDescriptor[];
+ imports(moduleObject: Module): ModuleImportDescriptor[];
+ };
+
+ interface RuntimeError {}
+
+ var RuntimeError: {
+ prototype: RuntimeError;
+ new (): RuntimeError;
+ };
+
+ interface Table {
+ readonly length: number;
+ get(index: number): Function | null;
+ grow(delta: number): number;
+ set(index: number, value: Function | null): void;
+ }
+
+ var Table: {
+ prototype: Table;
+ new (descriptor: TableDescriptor): Table;
+ };
+
+ interface GlobalDescriptor {
+ mutable?: boolean;
+ value: ValueType;
+ }
+
+ interface MemoryDescriptor {
+ initial: number;
+ maximum?: number;
+ }
+
+ interface ModuleExportDescriptor {
+ kind: ImportExportKind;
+ name: string;
+ }
+
+ interface ModuleImportDescriptor {
+ kind: ImportExportKind;
+ module: string;
+ name: string;
+ }
+
+ interface TableDescriptor {
+ element: TableKind;
+ initial: number;
+ maximum?: number;
+ }
+
+ interface WebAssemblyInstantiatedSource {
+ instance: Instance;
+ module: Module;
+ }
+
+ type ImportExportKind = 'function' | 'global' | 'memory' | 'table';
+ type TableKind = 'anyfunc';
+ type ValueType = 'f32' | 'f64' | 'i32' | 'i64';
+ type ExportValue = Function | Global | Memory | Table;
+ type Exports = Record;
+ type ImportValue = ExportValue | number;
+ type ModuleImports = Record;
+ type Imports = Record;
+ function compile(bytes: BufferSource): Promise;
+ // `compileStreaming` does not exist in NodeJS
+ // function compileStreaming(source: Response | Promise): Promise;
+ function instantiate(
+ bytes: BufferSource,
+ importObject?: Imports,
+ ): Promise;
+ function instantiate(
+ moduleObject: Module,
+ importObject?: Imports,
+ ): Promise;
+ // `instantiateStreaming` does not exist in NodeJS
+ // function instantiateStreaming(response: Response | PromiseLike, importObject?: Imports): Promise;
+ function validate(bytes: BufferSource): boolean;
+}
diff --git a/libsquoosh/tsconfig.json b/libsquoosh/tsconfig.json
new file mode 100644
index 00000000..b99cde25
--- /dev/null
+++ b/libsquoosh/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../generic-tsconfig.json",
+ "compilerOptions": {
+ "lib": ["esnext"],
+ "types": ["node"]
+ },
+ "include": ["src/**/*"]
+}