From e6111be9986be8af0e08ac928db79832af8afdbc Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Wed, 19 Aug 2020 23:41:35 -0400 Subject: [PATCH] Remove decorators and bundler workarounds --- package-lock.json | 6 +- package.json | 6 +- src/codecs/generic/quality-option.tsx | 4 +- src/codecs/imagequant/options.tsx | 4 +- src/codecs/mozjpeg/options.tsx | 4 +- src/codecs/oxipng/options.tsx | 3 +- src/codecs/processor.ts | 99 +++++++++++++++------------ src/codecs/resize/options.tsx | 14 ++-- src/codecs/webp/options.tsx | 4 +- src/components/App/index.tsx | 16 +++-- src/components/Options/index.tsx | 13 ++-- src/components/Output/index.tsx | 29 +++----- src/components/compress/index.tsx | 11 ++- src/components/intro/index.tsx | 23 +++---- src/components/range/index.tsx | 5 +- src/components/results/index.tsx | 8 +-- src/custom-els/RangeInput/index.ts | 6 +- src/index.ts | 13 +--- src/init-app.tsx | 11 +-- src/lib/initial-util.ts | 27 -------- 20 files changed, 118 insertions(+), 188 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7369efdb..b655f30d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17762,9 +17762,9 @@ "dev": true }, "pointer-tracker": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.0.3.tgz", - "integrity": "sha512-PURBF4oc45JPECuguX6oPL3pJU5AlF0Nb/4sZdmqzPNAkV4LGL9MJMqb0smWDtmQ0F0KpbxEJn4/Lf5ugN1keQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.4.0.tgz", + "integrity": "sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==", "dev": true }, "portfinder": { diff --git a/package.json b/package.json index 72a81f10..145463a9 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,6 @@ } } }, - "//": "See https://github.com/GoogleChromeLabs/pointer-tracker/pull/10", - "alias": { - "pointer-tracker": "./node_modules/pointer-tracker/dist/PointerTracker.mjs" - }, "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-decorators": "^7.10.5", @@ -68,7 +64,7 @@ "normalize-path": "^3.0.0", "optimize-css-assets-webpack-plugin": "5.0.1", "parcel": "^2.0.0-beta.1", - "pointer-tracker": "2.0.3", + "pointer-tracker": "^2.4.0", "postcss-modules": "^3.2.0", "preact": "8.4.2", "prerender-loader": "1.3.0", diff --git a/src/codecs/generic/quality-option.tsx b/src/codecs/generic/quality-option.tsx index 413ef331..0c744210 100644 --- a/src/codecs/generic/quality-option.tsx +++ b/src/codecs/generic/quality-option.tsx @@ -1,5 +1,4 @@ import { h, Component } from 'preact'; -import { bind } from '../../lib/initial-util'; import * as style from '../../components/Options/style.module.scss'; import Range from '../../components/range'; @@ -26,8 +25,7 @@ export default function qualityOption(opts: QualityOptionArg = {}) { } = opts; class QualityOptions extends Component { - @bind - onChange(event: Event) { + onChange = (event: Event) => { const el = event.currentTarget as HTMLInputElement; this.props.onChange({ quality: Number(el.value) }); } diff --git a/src/codecs/imagequant/options.tsx b/src/codecs/imagequant/options.tsx index c82f5648..9218bdeb 100644 --- a/src/codecs/imagequant/options.tsx +++ b/src/codecs/imagequant/options.tsx @@ -1,5 +1,4 @@ import { h, Component } from 'preact'; -import { bind } from '../../lib/initial-util'; import { inputFieldValueAsNumber, konami, preventDefault } from '../../lib/util'; import { QuantizeOptions } from './processor-meta'; import * as style from '../../components/Options/style.module.scss'; @@ -27,8 +26,7 @@ export default class QuantizerOptions extends Component { }); } - @bind - onChange(event: Event) { + onChange = (event: Event) => { const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement; const { options } = this.props; diff --git a/src/codecs/mozjpeg/options.tsx b/src/codecs/mozjpeg/options.tsx index 2dad773b..e172e517 100644 --- a/src/codecs/mozjpeg/options.tsx +++ b/src/codecs/mozjpeg/options.tsx @@ -1,5 +1,4 @@ import { h, Component } from 'preact'; -import { bind } from '../../lib/initial-util'; import { inputFieldChecked, inputFieldValueAsNumber, preventDefault } from '../../lib/util'; import { EncodeOptions, MozJpegColorSpace } from './encoder-meta'; import * as style from '../../components/Options/style.module.scss'; @@ -23,8 +22,7 @@ export default class MozJPEGEncoderOptions extends Component { showAdvanced: false, }; - @bind - onChange(event: Event) { + onChange = (event: Event) => { const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement; const { options } = this.props; diff --git a/src/codecs/oxipng/options.tsx b/src/codecs/oxipng/options.tsx index 68bc12cc..db972003 100644 --- a/src/codecs/oxipng/options.tsx +++ b/src/codecs/oxipng/options.tsx @@ -11,8 +11,7 @@ type Props = { }; export default class OxiPNGEncoderOptions extends Component { - @bind - onChange(event: Event) { + onChange = (event: Event) => { const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement; const options: EncodeOptions = { diff --git a/src/codecs/processor.ts b/src/codecs/processor.ts index 264505e4..bee3bafb 100644 --- a/src/codecs/processor.ts +++ b/src/codecs/processor.ts @@ -16,7 +16,6 @@ import * as browserGIF from './browser-gif/encoder'; import * as browserTIFF from './browser-tiff/encoder'; import * as browserJP2 from './browser-jp2/encoder'; import * as browserPDF from './browser-pdf/encoder'; -import { bind } from '../lib/initial-util'; type ProcessorWorkerApi = import('./processor-worker').ProcessorWorkerApi; @@ -55,7 +54,7 @@ export default class Processor { private _workerTimeoutId: number = 0; /** @private */ - async runProcessingJob(options: ProcessingJobOptions, processingFunc: any, ...args: any[]) { + async _runProcessingJob(options: ProcessingJobOptions, job: Function) { const { needsWorker = false } = options; this._latestJobId += 1; @@ -69,10 +68,12 @@ export default class Processor { // @ts-ignore - Typescript doesn't know about the 2nd param to new Worker, and the // definition can't be overwritten. this._worker = new Worker( - './processor-worker', - { name: 'processor-worker' }, + // './processor-worker', + new URL('./processor-worker/index.ts', import.meta.url), + { name: 'processor-worker', type: 'module' }, // { name: 'processor-worker', type: 'module' }, ) as Worker; + console.log(this._worker); // Need to do some TypeScript trickery to make the type match. this._workerApi = proxy(this._worker) as any as ProcessorWorkerApi; } @@ -80,7 +81,7 @@ export default class Processor { this._busy = true; const returnVal = Promise.race([ - processingFunc.call(this, ...args), + job(), new Promise((_, reject) => { this._abortRejector = reject; }), ]); @@ -99,7 +100,7 @@ export default class Processor { if (!this._worker) return; // If the worker is unused for 10 seconds, remove it to save memory. - this._workerTimeoutId = self.setTimeout(this.terminateWorker, workerTimeout); + this._workerTimeoutId = self.setTimeout(this.terminateWorker.bind(this), workerTimeout); } /** Abort the current job, if any */ @@ -112,7 +113,6 @@ export default class Processor { this.terminateWorker(); } - @bind terminateWorker() { if (!this._worker) return; this._worker.terminate(); @@ -120,93 +120,108 @@ export default class Processor { } // Off main thread jobs: - @processingJob({ needsWorker: true }) imageQuant(data: ImageData, opts: QuantizeOptions): Promise { - return this._workerApi!.quantize(data, opts); + return this._runProcessingJob({ needsWorker: true }, () => { + return this._workerApi!.quantize(data, opts); + }); } - @processingJob({ needsWorker: true }) rotate( data: ImageData, opts: import('./rotate/processor-meta').RotateOptions, - ): Promise { - return this._workerApi!.rotate(data, opts); + ): Promise { + return this._runProcessingJob({ needsWorker: true }, () => { + return this._workerApi!.rotate(data, opts); + }); } - @processingJob({ needsWorker: true }) workerResize( data: ImageData, opts: import('./resize/processor-meta').WorkerResizeOptions, ): Promise { - return this._workerApi!.resize(data, opts); + return this._runProcessingJob({ needsWorker: true }, () => { + return this._workerApi!.resize(data, opts); + }); } - @processingJob({ needsWorker: true }) mozjpegEncode( data: ImageData, opts: MozJPEGEncoderOptions, ): Promise { - return this._workerApi!.mozjpegEncode(data, opts); + return this._runProcessingJob({ needsWorker: true }, () => { + return this._workerApi!.mozjpegEncode(data, opts); + }); } - @processingJob({ needsWorker: true }) async oxiPngEncode( data: ImageData, opts: OxiPNGEncoderOptions, ): Promise { - // OxiPNG expects PNG input. - const pngBlob = await canvasEncode(data, 'image/png'); - const pngBuffer = await blobToArrayBuffer(pngBlob); - return this._workerApi!.oxiPngEncode(pngBuffer, opts); + return this._runProcessingJob({ needsWorker: true }, async () => { + // OxiPNG expects PNG input. + const pngBlob = await canvasEncode(data, 'image/png'); + const pngBuffer = await blobToArrayBuffer(pngBlob); + return this._workerApi!.oxiPngEncode(pngBuffer, opts); + }); } - @processingJob({ needsWorker: true }) webpEncode(data: ImageData, opts: WebPEncoderOptions): Promise { - return this._workerApi!.webpEncode(data, opts); + return this._runProcessingJob({ needsWorker: true }, () => { + return this._workerApi!.webpEncode(data, opts); + }); } - @processingJob({ needsWorker: true }) async webpDecode(blob: Blob): Promise { - const data = await blobToArrayBuffer(blob); - return this._workerApi!.webpDecode(data); + return this._runProcessingJob({ needsWorker: true }, async () => { + const data = await blobToArrayBuffer(blob); + return this._workerApi!.webpDecode(data); + }); } // Not-worker jobs: - @processingJob() browserBmpEncode(data: ImageData): Promise { - return browserBMP.encode(data); + return this._runProcessingJob({}, () => { + return browserBMP.encode(data); + }); } - @processingJob() browserPngEncode(data: ImageData): Promise { - return browserPNG.encode(data); + return this._runProcessingJob({}, () => { + return browserPNG.encode(data); + }); } - @processingJob() browserJpegEncode(data: ImageData, opts: BrowserJPEGOptions): Promise { - return browserJPEG.encode(data, opts); + return this._runProcessingJob({}, () => { + return browserJPEG.encode(data, opts); + }); } - @processingJob() browserWebpEncode(data: ImageData, opts: BrowserWebpEncodeOptions): Promise { - return browserWebP.encode(data, opts); + return this._runProcessingJob({}, () => { + return browserWebP.encode(data, opts); + }); } - @processingJob() browserGifEncode(data: ImageData): Promise { - return browserGIF.encode(data); + return this._runProcessingJob({}, () => { + return browserGIF.encode(data); + }); } - @processingJob() browserTiffEncode(data: ImageData): Promise { - return browserTIFF.encode(data); + return this._runProcessingJob({}, () => { + return browserTIFF.encode(data); + }); } - @processingJob() browserJp2Encode(data: ImageData): Promise { - return browserJP2.encode(data); + return this._runProcessingJob({}, () => { + return browserJP2.encode(data); + }); } - @processingJob() browserPdfEncode(data: ImageData): Promise { - return browserPDF.encode(data); + return this._runProcessingJob({}, () => { + return browserPDF.encode(data); + }); } // Synchronous jobs diff --git a/src/codecs/resize/options.tsx b/src/codecs/resize/options.tsx index 9b45fd25..31195ee5 100644 --- a/src/codecs/resize/options.tsx +++ b/src/codecs/resize/options.tsx @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; import linkState from 'linkstate'; -import { bind, linkRef } from '../../lib/initial-util'; +import { linkRef } from '../../lib/initial-util'; import { inputFieldValueAsNumber, inputFieldValue, preventDefault, inputFieldChecked, } from '../../lib/util'; @@ -58,8 +58,7 @@ export default class ResizerOptions extends Component { this.props.onChange(newOptions); } - @bind - private onChange() { + private onChange = () => { this.reportOptions(); } @@ -83,8 +82,7 @@ export default class ResizerOptions extends Component { } } - @bind - private onWidthInput() { + private onWidthInput = () => { if (this.state.maintainAspect) { const width = inputFieldValueAsNumber(this.form!.width); this.form!.height.value = Math.round(width / this.getAspect()); @@ -93,8 +91,7 @@ export default class ResizerOptions extends Component { this.reportOptions(); } - @bind - private onHeightInput() { + private onHeightInput = () => { if (this.state.maintainAspect) { const height = inputFieldValueAsNumber(this.form!.height); this.form!.width.value = Math.round(height * this.getAspect()); @@ -123,8 +120,7 @@ export default class ResizerOptions extends Component { return 'custom'; } - @bind - private onPresetChange(event: Event) { + private onPresetChange = (event: Event) => { const select = event.target as HTMLSelectElement; if (select.value === 'custom') return; const multiplier = Number(select.value); diff --git a/src/codecs/webp/options.tsx b/src/codecs/webp/options.tsx index cb05597e..8fb191f4 100644 --- a/src/codecs/webp/options.tsx +++ b/src/codecs/webp/options.tsx @@ -1,5 +1,4 @@ import { h, Component } from 'preact'; -import { bind } from '../../lib/initial-util'; import { inputFieldCheckedAsNumber, inputFieldValueAsNumber, preventDefault } from '../../lib/util'; import { EncodeOptions, WebPImageHint } from './encoder-meta'; import * as style from '../../components/Options/style.module.scss'; @@ -41,8 +40,7 @@ export default class WebPEncoderOptions extends Component { showAdvanced: false, }; - @bind - onChange(event: Event) { + onChange = (event: Event) => { const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement; const lossless = inputFieldCheckedAsNumber(form.lossless); const { options } = this.props; diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 0e0b2fce..33f9d6ff 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; -import { bind, linkRef, Fileish } from '../../lib/initial-util'; +import { linkRef, Fileish } from '../../lib/initial-util'; import * as style from './style.module.scss'; import { FileDropEvent } from 'file-drop-element'; import 'file-drop-element'; @@ -43,6 +43,12 @@ export default class App extends Component { constructor() { super(); + this.onFileDrop = this.onFileDrop.bind(this); + this.onIntroPickFile = this.onIntroPickFile.bind(this); + this.showSnack = this.showSnack.bind(this); + this.onPopState = this.onPopState.bind(this); + this.openEditor = this.openEditor.bind(this); + compressPromise.then((module) => { this.setState({ Compress: module.default }); }).catch((e) => { @@ -61,7 +67,8 @@ export default class App extends Component { }); // In development, persist application state across hot reloads: - if (process.env.NODE_ENV === 'development') { + // if (process.env.NODE_ENV === 'development') { + if (module.hot) { this.setState(window.STATE); const oldCDU = this.componentDidUpdate; this.componentDidUpdate = (props, state, prev) => { @@ -81,7 +88,6 @@ export default class App extends Component { window.addEventListener('popstate', this.onPopState); } - @bind private onFileDrop({ files }: FileDropEvent) { if (!files || files.length === 0) return; const file = files[0]; @@ -89,24 +95,20 @@ export default class App extends Component { this.setState({ file }); } - @bind private onIntroPickFile(file: File | Fileish) { this.openEditor(); this.setState({ file }); } - @bind private showSnack(message: string, options: SnackOptions = {}): Promise { if (!this.snackbar) throw Error('Snackbar missing'); return this.snackbar.showSnackbar(message, options); } - @bind private onPopState() { this.setState({ isEditorOpen: location.pathname === ROUTE_EDITOR }); } - @bind private openEditor() { if (this.state.isEditorOpen) return; // Change path, but preserve query string. diff --git a/src/components/Options/index.tsx b/src/components/Options/index.tsx index c8ec1c7f..c516d0c3 100644 --- a/src/components/Options/index.tsx +++ b/src/components/Options/index.tsx @@ -1,7 +1,6 @@ import { h, Component } from 'preact'; import * as style from './style.module.scss'; -import { bind } from '../../lib/initial-util'; import { cleanSet, cleanMerge } from '../../lib/clean-modify'; import OxiPNGEncoderOptions from '../../codecs/oxipng/options'; import MozJpegEncoderOptions from '../../codecs/mozjpeg/options'; @@ -82,8 +81,7 @@ export default class Options extends Component { encodersSupported.then(encoderSupportMap => this.setState({ encoderSupportMap })); } - @bind - private onEncoderTypeChange(event: Event) { + private onEncoderTypeChange = (event: Event) => { const el = event.currentTarget as HTMLSelectElement; // The select element only has values matching encoder types, @@ -92,8 +90,7 @@ export default class Options extends Component { this.props.onEncoderTypeChange(type); } - @bind - private onPreprocessorEnabledChange(event: Event) { + private onPreprocessorEnabledChange = (event: Event) => { const el = event.currentTarget as HTMLInputElement; const preprocessor = el.name.split('.')[0] as keyof PreprocessorState; @@ -102,15 +99,13 @@ export default class Options extends Component { ); } - @bind - private onQuantizerOptionsChange(opts: QuantizeOptions) { + private onQuantizerOptionsChange = (opts: QuantizeOptions) => { this.props.onPreprocessorOptionsChange( cleanMerge(this.props.preprocessorState, 'quantizer', opts), ); } - @bind - private onResizeOptionsChange(opts: ResizeOptions) { + private onResizeOptionsChange = (opts: ResizeOptions) => { this.props.onPreprocessorOptionsChange( cleanMerge(this.props.preprocessorState, 'resize', opts), ); diff --git a/src/components/Output/index.tsx b/src/components/Output/index.tsx index 753373c7..a3f3da86 100644 --- a/src/components/Output/index.tsx +++ b/src/components/Output/index.tsx @@ -3,7 +3,7 @@ import PinchZoom, { ScaleToOpts } from './custom-els/PinchZoom'; import './custom-els/PinchZoom'; import './custom-els/TwoUp'; import * as style from './style.module.scss'; -import { bind, linkRef } from '../../lib/initial-util'; +import { linkRef } from '../../lib/initial-util'; import { shallowEqual, drawDataToCanvas } from '../../lib/util'; import { ToggleBackgroundIcon, @@ -135,29 +135,25 @@ export default class Output extends Component { return props.rightCompressed || (props.source && props.source.processed); } - @bind - private toggleBackground() { + private toggleBackground = () => { this.setState({ altBackground: !this.state.altBackground, }); } - @bind - private zoomIn() { + private zoomIn = () => { if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element'); this.pinchZoomLeft.scaleTo(this.state.scale * 1.25, scaleToOpts); } - @bind - private zoomOut() { + private zoomOut = () => { if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element'); this.pinchZoomLeft.scaleTo(this.state.scale / 1.25, scaleToOpts); } - @bind - private onRotateClick() { + private onRotateClick = () => { const { inputProcessorState } = this.props; if (!inputProcessorState) return; @@ -170,8 +166,7 @@ export default class Output extends Component { this.props.onInputProcessorChange(newState); } - @bind - private onScaleValueFocus() { + private onScaleValueFocus = () => { this.setState({ editingScale: true }, () => { if (this.scaleInput) { // Firefox unfocuses the input straight away unless I force a style calculation here. I have @@ -182,13 +177,11 @@ export default class Output extends Component { }); } - @bind - private onScaleInputBlur() { + private onScaleInputBlur = () => { this.setState({ editingScale: false }); } - @bind - private onScaleInputChanged(event: Event) { + private onScaleInputChanged = (event: Event) => { const target = event.target as HTMLInputElement; const percent = parseFloat(target.value); if (isNaN(percent)) return; @@ -197,8 +190,7 @@ export default class Output extends Component { this.pinchZoomLeft.scaleTo(percent / 100, scaleToOpts); } - @bind - private onPinchZoomLeftChange(event: Event) { + private onPinchZoomLeftChange = (event: Event) => { if (!this.pinchZoomRight || !this.pinchZoomLeft) throw Error('Missing pinch-zoom element'); this.setState({ scale: this.pinchZoomLeft.scale, @@ -218,8 +210,7 @@ export default class Output extends Component { * * @param event Event to redirect */ - @bind - private onRetargetableEvent(event: Event) { + private onRetargetableEvent = (event: Event) => { const targetEl = event.target as HTMLElement; if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element'); // If the event is on the handle of the two-up, let it through, diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index cfcdb1df..c1146e79 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; -import { bind, Fileish } from '../../lib/initial-util'; +import { Fileish } from '../../lib/initial-util'; import { blobToImg, drawableToImageData, blobToText } from '../../lib/util'; import * as style from './style.module.scss'; import Output from '../Output'; @@ -254,8 +254,7 @@ export default class Compress extends Component { import('../../lib/sw-bridge').then(({ mainAppLoaded }) => mainAppLoaded()); } - @bind - private onMobileWidthChange() { + private onMobileWidthChange = () => { this.setState({ mobileView: this.widthQuery.matches }); } @@ -344,8 +343,7 @@ export default class Compress extends Component { }); } - @bind - private async onInputProcessorChange(options: InputProcessorState): Promise { + private onInputProcessorChange = async (options: InputProcessorState): Promise => { const source = this.state.source; if (!source) return; @@ -396,8 +394,7 @@ export default class Compress extends Component { } } - @bind - private async updateFile(file: File | Fileish) { + private updateFile = async (file: File | Fileish) => { const loadingCounter = this.state.loadingCounter + 1; // Either processor is good enough here. const processor = this.leftProcessor; diff --git a/src/components/intro/index.tsx b/src/components/intro/index.tsx index f0562deb..5149c0e5 100644 --- a/src/components/intro/index.tsx +++ b/src/components/intro/index.tsx @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; -import { bind, linkRef, Fileish } from '../../lib/initial-util'; +import { linkRef, Fileish } from '../../lib/initial-util'; import '../custom-els/LoadingSpinner'; import logo from 'url:./imgs/logo.svg'; @@ -67,13 +67,11 @@ export default class Intro extends Component { window.addEventListener('appinstalled', this.onAppInstalled); } - @bind - private resetFileInput() { + private resetFileInput = () => { this.fileInput!.value = ''; } - @bind - private onFileChange(event: Event): void { + private onFileChange = (event: Event) => { const fileInput = event.target as HTMLInputElement; const file = fileInput.files && fileInput.files[0]; if (!file) return; @@ -81,13 +79,11 @@ export default class Intro extends Component { this.props.onFile(file); } - @bind - private onButtonClick() { + private onButtonClick = () => { this.fileInput!.click(); } - @bind - private async onDemoClick(index: number, event: Event) { + private onDemoClick = async (index: number, event: Event) => { try { this.setState({ fetchingDemoIndex: index }); const demo = demos[index]; @@ -104,8 +100,7 @@ export default class Intro extends Component { } } - @bind - private onBeforeInstallPromptEvent(event: BeforeInstallPromptEvent) { + private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => { // Don't show the mini-infobar on mobile event.preventDefault(); @@ -121,8 +116,7 @@ export default class Intro extends Component { ga('send', 'event', gaEventInfo); } - @bind - private async onInstallClick(event: Event) { + private onInstallClick = async (event: Event) => { // Get the deferred beforeinstallprompt event const beforeInstallEvent = this.state.beforeInstallEvent; // If there's no deferred prompt, bail. @@ -150,8 +144,7 @@ export default class Intro extends Component { } } - @bind - private onAppInstalled() { + private onAppInstalled = () => { // We don't need the install button, if it's shown this.setState({ beforeInstallEvent: undefined }); diff --git a/src/components/range/index.tsx b/src/components/range/index.tsx index 20508af4..22c18d10 100644 --- a/src/components/range/index.tsx +++ b/src/components/range/index.tsx @@ -2,7 +2,7 @@ import { h, Component } from 'preact'; import * as style from './style.module.scss'; import RangeInputElement from '../../custom-els/RangeInput'; import '../../custom-els/RangeInput'; -import { linkRef, bind } from '../../lib/initial-util'; +import { linkRef } from '../../lib/initial-util'; interface Props extends JSX.HTMLAttributes {} interface State {} @@ -10,8 +10,7 @@ interface State {} export default class Range extends Component { rangeWc?: RangeInputElement; - @bind - private onTextInput(event: Event) { + private onTextInput = (event: Event) => { const input = event.target as HTMLInputElement; const value = input.value.trim(); if (!value) return; diff --git a/src/components/results/index.tsx b/src/components/results/index.tsx index dcc67d46..a3a0b236 100644 --- a/src/components/results/index.tsx +++ b/src/components/results/index.tsx @@ -5,7 +5,7 @@ import FileSize from './FileSize'; import { DownloadIcon, CopyAcrossIcon, CopyAcrossIconProps } from '../../lib/icons'; import '../custom-els/LoadingSpinner'; import { SourceImage } from '../compress'; -import { Fileish, bind } from '../../lib/initial-util'; +import { Fileish } from '../../lib/initial-util'; interface Props { loading: boolean; @@ -52,14 +52,12 @@ export default class Results extends Component { } } - @bind - private onCopyToOtherClick(event: Event) { + private onCopyToOtherClick = (event: Event) => { event.preventDefault(); this.props.onCopyToOtherClick(); } - @bind - onDownload() { + onDownload = () => { // GA can’t do floats. So we round to ints. We're deliberately rounding to nearest kilobyte to // avoid cases where exact image sizes leak something interesting about the user. const before = Math.round(this.props.source!.file.size / 1024); diff --git a/src/custom-els/RangeInput/index.ts b/src/custom-els/RangeInput/index.ts index 4b40c231..6b0e3ac1 100644 --- a/src/custom-els/RangeInput/index.ts +++ b/src/custom-els/RangeInput/index.ts @@ -1,5 +1,4 @@ import PointerTracker from 'pointer-tracker'; -import { bind } from '../../lib/initial-util'; import * as style from './styles.module.css'; const RETARGETED_EVENTS = ['focus', 'blur']; @@ -23,6 +22,7 @@ class RangeInputElement extends HTMLElement { constructor() { super(); + this._input = document.createElement('input'); this._input.type = 'range'; this._input.className = style.input; @@ -38,10 +38,12 @@ class RangeInputElement extends HTMLElement { }, }); + this._retargetEvent = this._retargetEvent.bind(this); for (const event of RETARGETED_EVENTS) { this._input.addEventListener(event, this._retargetEvent, true); } + this._update = this._update.bind(this); for (const event of UPDATE_EVENTS) { this._input.addEventListener(event, this._update, true); } @@ -80,14 +82,12 @@ class RangeInputElement extends HTMLElement { this._update(); } - @bind private _retargetEvent(event: Event) { event.stopImmediatePropagation(); const retargetted = new Event(event.type, event); this.dispatchEvent(retargetted); } - @bind private _update() { const value = Number(this.value) || 0; const min = Number(this.min) || 0; diff --git a/src/index.ts b/src/index.ts index 235bdad5..eb5bee8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,7 @@ declare module '@webcomponents/custom-elements'; -// Patch Worker to ignore `importScripts("x.css")` generated by Parcel: -const W = self.Worker; -self.Worker = function (url: string | URL, options?: WorkerOptions) { - const code = ` - importScripts=(function(){ - return this.apply(self,[].slice.call(arguments).map(function(x){return !/\\.css$/i.test(x) && new URL(x,self.url).href}).filter(Boolean)) - }).bind(importScripts);importScripts(self.url=${JSON.stringify(url)}) - `.trim(); - return new W(URL.createObjectURL(new Blob([code], { type: 'text/javascript' })), options); -} as any as (typeof Worker); - function init() { - require('./init-app.tsx'); + import('./init-app.tsx'); } if (!('customElements' in self)) { diff --git a/src/init-app.tsx b/src/init-app.tsx index 6b553706..ff41339c 100644 --- a/src/init-app.tsx +++ b/src/init-app.tsx @@ -1,7 +1,7 @@ import { h, render } from 'preact'; import './lib/fix-pmc.mjs'; import './style/index.scss'; -import App from './components/App'; +import App from './components/App/index.tsx'; // Find the outermost Element in our server-rendered HTML structure. let root = document.getElementById('app_root') as Element; @@ -10,12 +10,7 @@ let root = document.getElementById('app_root') as Element; root = render(, document.body, root); root.setAttribute('id', 'app_root'); -if (process.env.NODE_ENV !== 'production') { +if (module.hot) { // Enable support for React DevTools and some helpful console warnings: - require('preact/debug'); - - // Full HMR may not be working due to https://github.com/parcel-bundler/parcel/issues/5016 - if (module.hot) { - module.hot.accept(); - } + import('preact/debug'); } diff --git a/src/lib/initial-util.ts b/src/lib/initial-util.ts index 51d6b3f4..4beb2ba7 100644 --- a/src/lib/initial-util.ts +++ b/src/lib/initial-util.ts @@ -1,33 +1,6 @@ // This file contains the utils that are needed for the very first rendering of the page. They're // here because WebPack isn't quite smart enough to split things in the same file. -/** - * A decorator that binds values to their class instance. - * @example - * class C { - * @bind - * foo () { - * return this; - * } - * } - * let f = new C().foo; - * f() instanceof C; // true - */ -export function bind(target: any, propertyKey: string, descriptor: PropertyDescriptor) { - return { - // the first time the prototype property is accessed for an instance, - // define an instance property pointing to the bound function. - // This effectively "caches" the bound prototype method as an instance property. - get() { - const bound = descriptor.value.bind(this); - Object.defineProperty(this, propertyKey, { - value: bound, - }); - return bound; - }, - }; -} - /** Creates a function ref that assigns its value to a given property of an object. * @example * // element is stored as `this.foo` when rendered.