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 42c18f9f..53d9579d 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/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/**/*"] +}