Compare commits

...

23 Commits

Author SHA1 Message Date
ffe8cd3562 Trick to avoid duplicating the SVG in the source 2021-09-22 15:04:44 +01:00
853a26ba26 Inlining logo SVG 2021-09-22 14:46:29 +01:00
cad09160b6 Reducing browser spinner time (#1157) 2021-09-20 13:49:53 +01:00
9663c23489 Merge pull request #1151 from ergunsh/improve-encode-types
Improve `encode` API and its types
2021-09-17 13:50:29 +01:00
95a1b35c91 Update encodedWith to contain resolved values
We already await the promises that we set on the `encodedWith` instead of setting already
resolved promises to `encodedWith` we can set the resolved values

So, the users can use like
`const { mozjpeg: { binary } } = await image.encode({ mozjpeg })` or
they can first run
`await image.encode({ mozjpeg })` and then
`image.encodedWith.mozjpeg.binary`
2021-09-10 15:18:14 +02:00
914cdea41d Return encode result from Image.encode calls
Before this, there were one way to use the API:
call `await image.encode({ mozjpeg: {} })`
then use `encodedWith` by asserting that `mozjpeg` property exists on it

After adding return value to encode, people
will be able to use it like
`const { mozjpeg } = await image.encode({ mozjpeg: {} });`
which provides better type safety
2021-09-10 14:45:20 +02:00
6bfce29af6 Improve typing of Image.encode
Instead of returning `any` we're now returning the whole object.

Still from typing perspective, the API is not that
great since we don't have any type relation
between `encode` calls and `encodedWith` property. Maybe we can think about
returning directly from `encode` call
with the returned object having properties
that is supplied in `encode` calls
2021-09-10 14:08:21 +02:00
64cad1cc23 Merge pull request #1123 from styfle/load-file-async
Reduce `@squoosh/lib` Node.js API usage (make it run on the web)
2021-09-08 22:30:53 +01:00
87f25d909b Merge branch 'dev' into load-file-async 2021-09-08 17:04:50 -04:00
bdfdaf53af Remove unused readme link per atjn
Co-authored-by: Anton <dev@atjn.dk>
2021-09-08 17:04:46 -04:00
1ab9e8b3ab Merge pull request #1141 from ergunsh/improve-exposed-types 2021-09-08 17:57:46 +01:00
2718077d3d Merge branch 'dev' into improve-exposed-types 2021-09-08 19:48:01 +03:00
c4bc369c6b Fix typo in triangle 2021-09-03 15:03:28 +03:00
68ce8f420d Improve encode parameter typing 2021-09-03 14:54:08 +03:00
27f103fee5 Improve preprocess parameter type 2021-09-03 11:53:52 +03:00
c21d3714a8 Fix link in readme 2021-09-02 16:38:22 -04:00
3ea0d88c4f Apply suggestions from atjn
Co-authored-by: Anton <dev@atjn.dk>
2021-09-02 16:36:37 -04:00
202d0bc088 Generalize file to ArrayLike<number> 2021-08-20 18:48:30 -04:00
bdb5b16372 Remove Buffer from decodeFile() 2021-08-20 18:36:23 -04:00
30140c2b7a Fix CLI bug 2021-08-20 18:35:29 -04:00
10996cdbca Add missing import 2021-08-20 16:08:01 -04:00
e5806507d4 Remove Node.js API surface in libsquoosh 2021-08-20 15:11:14 -04:00
1c5b44f9a1 Add loadFile parameter to ImagePool 2021-08-16 16:51:23 -04:00
14 changed files with 242 additions and 122 deletions

View File

@ -4,6 +4,7 @@ import { program } from 'commander/esm.mjs';
import JSON5 from 'json5';
import path from 'path';
import { promises as fsp } from 'fs';
import { cpus } from 'os';
import ora from 'ora';
import kleur from 'kleur';
@ -75,7 +76,9 @@ async function getInputFiles(paths) {
for (const inputPath of paths) {
const files = (await fsp.lstat(inputPath)).isDirectory()
? (await fsp.readdir(inputPath, {withFileTypes: true})).filter(dirent => dirent.isFile()).map(dirent => path.join(inputPath, dirent.name))
? (await fsp.readdir(inputPath, { withFileTypes: true }))
.filter((dirent) => dirent.isFile())
.map((dirent) => path.join(inputPath, dirent.name))
: [inputPath];
for (const file of files) {
try {
@ -101,7 +104,7 @@ async function getInputFiles(paths) {
async function processFiles(files) {
files = await getInputFiles(files);
const imagePool = new ImagePool();
const imagePool = new ImagePool(cpus().length);
const results = new Map();
const progress = progressTracker(results);
@ -116,7 +119,8 @@ async function processFiles(files) {
let decoded = 0;
let decodedFiles = await Promise.all(
files.map(async (file) => {
const image = imagePool.ingestImage(file);
const buffer = await fsp.readFile(file);
const image = imagePool.ingestImage(buffer);
await image.decoded;
results.set(image, {
file,
@ -178,7 +182,7 @@ async function processFiles(files) {
const outputPath = path.join(
program.opts().outputDir,
path.basename(originalFile, path.extname(originalFile)) +
program.opts().suffix
program.opts().suffix,
);
for (const output of Object.values(image.encodedWith)) {
const outputFile = `${outputPath}.${(await output).extension}`;

38
lib/as-text-plugin.js Normal file
View File

@ -0,0 +1,38 @@
/**
* 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 { promises as fs } from 'fs';
const prefix = 'as-text:';
export default function dataURLPlugin() {
return {
name: 'as-text-plugin',
async resolveId(id, importer) {
if (!id.startsWith(prefix)) return;
const realId = id.slice(prefix.length);
const resolveResult = await this.resolve(realId, importer);
if (!resolveResult) throw Error(`Cannot find ${realId} from ${importer}`);
return prefix + resolveResult.id;
},
async load(id) {
if (!id.startsWith(prefix)) return;
const realId = id.slice(prefix.length);
this.addWatchFile(realId);
const source = await fs.readFile(realId, { encoding: 'utf-8' });
return `export default ${JSON.stringify(source)}`;
},
};
}

View File

@ -14,28 +14,40 @@ import { promises as fs } from 'fs';
import { lookup as lookupMime } from 'mime-types';
const prefix = 'data-url:';
const prefix = /^data-url(-text)?:/;
export default function dataURLPlugin() {
return {
name: 'data-url-plugin',
async resolveId(id, importer) {
if (!id.startsWith(prefix)) return;
const match = prefix.exec(id);
if (!match) return;
return (
prefix + (await this.resolve(id.slice(prefix.length), importer)).id
match[0] + (await this.resolve(id.slice(match[0].length), importer)).id
);
},
async load(id) {
if (!id.startsWith(prefix)) return;
const realId = id.slice(prefix.length);
const match = prefix.exec(id);
if (!match) return;
const isText = !!match[1];
const realId = id.slice(match[0].length);
this.addWatchFile(realId);
const source = await fs.readFile(realId);
const type = lookupMime(realId) || 'text/plain';
return `export default 'data:${type};base64,${source.toString(
'base64',
)}';`;
if (isText) {
const encodedBody = encodeURIComponent(source.toString('utf8'));
return `export default ${JSON.stringify(
`data:${type};charset=utf-8,${encodedBody}`,
)};`;
}
return `export default ${JSON.stringify(
`data:${type};base64,${source.toString('base64')}`,
)};`;
},
};
}

View File

@ -28,10 +28,17 @@ if (!self.<%- amdFunctionName %>) {
<% } else { %>
new Promise(resolve => {
if ("document" in self) {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
const link = document.createElement("link");
link.rel = "preload";
link.as = "script";
link.href = uri;
link.onload = () => {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
};
document.head.appendChild(link);
} else {
self.nextDefineUri = uri;
importScripts(uri);

View File

@ -16,21 +16,23 @@ You can start using the libSquoosh by adding these lines to the top of your JS p
```js
import { ImagePool } from '@squoosh/lib';
const imagePool = new ImagePool();
import { cpus } from 'os';
const imagePool = new ImagePool(cpus().length);
```
This will create an image pool with an underlying processing pipeline that you can use to ingest and encode images. The ImagePool constructor takes one argument that defines how many parallel operations it is allowed to run at any given time. By default, this number is set to the amount of CPU cores available in the system it is running on.
This will create an image pool with an underlying processing pipeline that you can use to ingest and encode images. The ImagePool constructor takes one argument that defines how many parallel operations it is allowed to run at any given time.
## Ingesting images
You can ingest a new image like so:
```js
const imagePath = 'path/to/image.png';
const image = imagePool.ingestImage(imagePath);
import fs from 'fs/promises';
const file = await fs.readFile('./path/to/image.png');
const image = imagePool.ingestImage(file);
```
The `ingestImage` function can take anything the node [`readFile`][readfile] function can take, including a buffer and `FileHandle`.
The `ingestImage` function can accept any [`ArrayBuffer`][arraybuffer] whether that is from `readFile()` or `fetch()`.
The returned `image` object is a representation of the original image, that you can now preprocess, encode, and extract information about.
@ -39,7 +41,7 @@ The returned `image` object is a representation of the original image, that you
When an image has been ingested, you can start preprocessing it and encoding it to other formats. This example will resize the image and then encode it to a `.jpg` and `.jxl` image:
```js
await image.decoded; //Wait until the image is decoded before running preprocessors.
await image.decoded; //Wait until the image is decoded before running preprocessors.
const preprocessOptions = {
//When both width and height are specified, the image resized to specified size.
@ -47,7 +49,7 @@ const preprocessOptions = {
enabled: true,
width: 100,
height: 50,
}
},
/*
//When either width or height is specified, the image resized to specified size keeping aspect ratio.
resize: {
@ -55,7 +57,7 @@ const preprocessOptions = {
width: 100,
}
*/
}
};
await image.preprocess(preprocessOptions);
const encodeOptions = {
@ -63,9 +65,8 @@ const encodeOptions = {
jxl: {
quality: 90,
},
}
};
await image.encode(encodeOptions);
```
The default values for each option can be found in the [`codecs.ts`][codecs.ts] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified there. _Better documentation is needed here._
@ -168,4 +169,4 @@ const encodeOptions: {
[squoosh]: https://squoosh.app
[codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts
[butteraugli]: https://github.com/google/butteraugli
[readfile]: https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options
[arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

View File

@ -29,14 +29,23 @@ interface ResizeWithAspectParams {
target_height: number;
}
interface ResizeInstantiateOptions {
export interface ResizeOptions {
width: number;
height: number;
method: string;
method: 'triangle' | 'catrom' | 'mitchell' | 'lanczos3';
premultiply: boolean;
linearRGB: boolean;
}
export interface QuantOptions {
numColors: number;
dither: number;
}
export interface RotateOptions {
numRotations: number;
}
declare global {
// Needed for being able to use ImageData as type in codec types
type ImageData = import('./image_data.js').default;
@ -52,6 +61,7 @@ import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js';
import mozEncWasm from 'asset-url:../../codecs/mozjpeg/enc/mozjpeg_node_enc.wasm';
import mozDec from '../../codecs/mozjpeg/dec/mozjpeg_node_dec.js';
import mozDecWasm from 'asset-url:../../codecs/mozjpeg/dec/mozjpeg_node_dec.wasm';
import type { EncodeOptions as MozJPEGEncodeOptions } from '../../codecs/mozjpeg/enc/mozjpeg_enc';
// WebP
import type { WebPModule as WebPEncodeModule } from '../../codecs/webp/enc/webp_enc';
@ -59,6 +69,7 @@ import webpEnc from '../../codecs/webp/enc/webp_node_enc.js';
import webpEncWasm from 'asset-url:../../codecs/webp/enc/webp_node_enc.wasm';
import webpDec from '../../codecs/webp/dec/webp_node_dec.js';
import webpDecWasm from 'asset-url:../../codecs/webp/dec/webp_node_dec.wasm';
import type { EncodeOptions as WebPEncodeOptions } from '../../codecs/webp/enc/webp_enc.js';
// AVIF
import type { AVIFModule as AVIFEncodeModule } from '../../codecs/avif/enc/avif_enc';
@ -69,6 +80,7 @@ import avifEncMtWorker from 'chunk-url:../../codecs/avif/enc/avif_node_enc_mt.wo
import avifEncMtWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc_mt.wasm';
import avifDec from '../../codecs/avif/dec/avif_node_dec.js';
import avifDecWasm from 'asset-url:../../codecs/avif/dec/avif_node_dec.wasm';
import type { EncodeOptions as AvifEncodeOptions } from '../../codecs/avif/enc/avif_enc.js';
// JXL
import type { JXLModule as JXLEncodeModule } from '../../codecs/jxl/enc/jxl_enc';
@ -76,6 +88,7 @@ import jxlEnc from '../../codecs/jxl/enc/jxl_node_enc.js';
import jxlEncWasm from 'asset-url:../../codecs/jxl/enc/jxl_node_enc.wasm';
import jxlDec from '../../codecs/jxl/dec/jxl_node_dec.js';
import jxlDecWasm from 'asset-url:../../codecs/jxl/dec/jxl_node_dec.wasm';
import type { EncodeOptions as JxlEncodeOptions } from '../../codecs/jxl/enc/jxl_enc.js';
// WP2
import type { WP2Module as WP2EncodeModule } from '../../codecs/wp2/enc/wp2_enc';
@ -83,6 +96,7 @@ import wp2Enc from '../../codecs/wp2/enc/wp2_node_enc.js';
import wp2EncWasm from 'asset-url:../../codecs/wp2/enc/wp2_node_enc.wasm';
import wp2Dec from '../../codecs/wp2/dec/wp2_node_dec.js';
import wp2DecWasm from 'asset-url:../../codecs/wp2/dec/wp2_node_dec.wasm';
import type { EncodeOptions as WP2EncodeOptions } from '../../codecs/wp2/enc/wp2_enc.js';
// PNG
import * as pngEncDec from '../../codecs/png/pkg/squoosh_png.js';
@ -95,6 +109,9 @@ const pngEncDecPromise = pngEncDec.default(
import * as oxipng from '../../codecs/oxipng/pkg/squoosh_oxipng.js';
import oxipngWasm from 'asset-url:../../codecs/oxipng/pkg/squoosh_oxipng_bg.wasm';
const oxipngPromise = oxipng.default(fsp.readFile(pathify(oxipngWasm)));
interface OxiPngEncodeOptions {
level: number;
}
// Resize
import * as resize from '../../codecs/resize/pkg/squoosh_resize.js';
@ -171,13 +188,7 @@ export const preprocessors = {
buffer: Uint8Array,
input_width: number,
input_height: number,
{
width,
height,
method,
premultiply,
linearRGB,
}: ResizeInstantiateOptions,
{ width, height, method, premultiply, linearRGB }: ResizeOptions,
) => {
({ width, height } = resizeWithAspect({
input_width,
@ -218,7 +229,7 @@ export const preprocessors = {
buffer: Uint8Array,
width: number,
height: number,
{ numColors, dither }: { numColors: number; dither: number },
{ numColors, dither }: QuantOptions,
) =>
new ImageData(
imageQuant.quantize(buffer, width, height, numColors, dither),
@ -239,7 +250,7 @@ export const preprocessors = {
buffer: Uint8Array,
width: number,
height: number,
{ numRotations }: { numRotations: number },
{ numRotations }: RotateOptions,
) => {
const degrees = (numRotations * 90) % 360;
const sameDimensions = degrees == 0 || degrees == 180;
@ -401,13 +412,13 @@ export const codecs = {
jxlEncWasm,
),
defaultEncoderOptions: {
speed: 4,
effort: 1,
quality: 75,
progressive: false,
epf: -1,
nearLossless: 0,
lossyPalette: false,
decodingSpeedTier: 0,
photonNoiseIso: 0,
},
autoOptimize: {
option: 'quality',
@ -480,3 +491,12 @@ export const codecs = {
},
},
} as const;
export {
MozJPEGEncodeOptions,
WebPEncodeOptions,
AvifEncodeOptions,
JxlEncodeOptions,
WP2EncodeOptions,
OxiPngEncodeOptions,
};

View File

@ -1,8 +1,18 @@
import { isMainThread } from 'worker_threads';
import { cpus } from 'os';
import { promises as fsp } from 'fs';
import { codecs as encoders, preprocessors } from './codecs.js';
import {
AvifEncodeOptions,
codecs as encoders,
JxlEncodeOptions,
MozJPEGEncodeOptions,
OxiPngEncodeOptions,
preprocessors,
QuantOptions,
ResizeOptions,
RotateOptions,
WebPEncodeOptions,
WP2EncodeOptions,
} from './codecs.js';
import WorkerPool from './worker_pool.js';
import { autoOptimize } from './auto-optimizer.js';
import type ImageData from './image_data';
@ -10,30 +20,34 @@ import type ImageData from './image_data';
export { ImagePool, encoders, preprocessors };
type EncoderKey = keyof typeof encoders;
type PreprocessorKey = keyof typeof preprocessors;
type FileLike = Buffer | ArrayBuffer | string | ArrayBufferView;
type PreprocessOptions = {
resize?: ResizeOptions;
quant?: QuantOptions;
rotate?: RotateOptions;
};
type EncodeResult = {
optionsUsed: object;
binary: Uint8Array;
extension: string;
size: number;
};
type EncoderOptions = {
mozjpeg?: Partial<MozJPEGEncodeOptions>;
webp?: Partial<WebPEncodeOptions>;
avif?: Partial<AvifEncodeOptions>;
jxl?: Partial<JxlEncodeOptions>;
wp2?: Partial<WP2EncodeOptions>;
oxipng?: Partial<OxiPngEncodeOptions>;
};
async function decodeFile({
file,
}: {
file: FileLike;
file: ArrayBuffer | ArrayLike<number>;
}): Promise<{ bitmap: ImageData; size: number }> {
let buffer;
if (ArrayBuffer.isView(file)) {
buffer = Buffer.from(file.buffer);
file = 'Binary blob';
} else if (file instanceof ArrayBuffer) {
buffer = Buffer.from(file);
file = 'Binary blob';
} else if ((file as unknown) instanceof Buffer) {
// TODO: Check why we need type assertions here.
buffer = (file as unknown) as Buffer;
file = 'Binary blob';
} else if (typeof file === 'string') {
buffer = await fsp.readFile(file);
} else {
throw Error('Unexpected input type');
}
const firstChunk = buffer.slice(0, 16);
const array = new Uint8Array(file);
const firstChunk = array.slice(0, 16);
const firstChunkString = Array.from(firstChunk)
.map((v) => String.fromCodePoint(v))
.join('');
@ -41,14 +55,14 @@ async function decodeFile({
detectors.some((detector) => detector.exec(firstChunkString)),
)?.[0] as EncoderKey | undefined;
if (!key) {
throw Error(`${file} has an unsupported format`);
throw Error(`File has an unsupported format`);
}
const encoder = encoders[key];
const mod = await encoder.dec();
const rgba = mod.decode(new Uint8Array(buffer));
const rgba = mod.decode(array);
return {
bitmap: rgba,
size: buffer.length,
size: array.length,
};
}
@ -83,7 +97,7 @@ async function encodeImage({
encConfig: any;
optimizerButteraugliTarget: number;
maxOptimizerRounds: number;
}) {
}): Promise<EncodeResult> {
let binary: Uint8Array;
let optionsUsed = encConfig;
const encoder = await encoders[encName].enc();
@ -169,12 +183,15 @@ function handleJob(params: JobMessage) {
* Represents an ingested image.
*/
class Image {
public file: FileLike;
public file: ArrayBuffer | ArrayLike<number>;
public workerPool: WorkerPool<JobMessage, any>;
public decoded: Promise<{ bitmap: ImageData }>;
public encodedWith: { [key: string]: any };
public encodedWith: { [key in EncoderKey]?: EncodeResult };
constructor(workerPool: WorkerPool<JobMessage, any>, file: FileLike) {
constructor(
workerPool: WorkerPool<JobMessage, any>,
file: ArrayBuffer | ArrayLike<number>,
) {
this.file = file;
this.workerPool = workerPool;
this.decoded = workerPool.dispatchJob({ operation: 'decode', file });
@ -183,10 +200,10 @@ class Image {
/**
* Define one or several preprocessors to use on the image.
* @param {object} preprocessOptions - An object with preprocessors to use, and their settings.
* @param {PreprocessOptions} preprocessOptions - An object with preprocessors to use, and their settings.
* @returns {Promise<undefined>} - A promise that resolves when all preprocessors have completed their work.
*/
async preprocess(preprocessOptions = {}) {
async preprocess(preprocessOptions: PreprocessOptions = {}) {
for (const [name, options] of Object.entries(preprocessOptions)) {
if (!Object.keys(preprocessors).includes(name)) {
throw Error(`Invalid preprocessor "${name}"`);
@ -210,17 +227,16 @@ class Image {
/**
* Define one or several encoders to use on the image.
* @param {object} encodeOptions - An object with encoders to use, and their settings.
* @returns {Promise<void>} - A promise that resolves when the image has been encoded with all the specified encoders.
* @returns {Promise<{ [key in keyof T]: EncodeResult }>} - A promise that resolves when the image has been encoded with all the specified encoders.
*/
async encode(
async encode<T extends EncoderOptions>(
encodeOptions: {
optimizerButteraugliTarget?: number;
maxOptimizerRounds?: number;
} & {
[key in EncoderKey]?: any; // any is okay for now
} = {},
): Promise<void> {
} & T,
): Promise<{ [key in keyof T]: EncodeResult }> {
const { bitmap } = await this.decoded;
const setEncodedWithPromises = [];
for (const [name, options] of Object.entries(encodeOptions)) {
if (!Object.keys(encoders).includes(name)) {
continue;
@ -231,18 +247,26 @@ class Image {
typeof options === 'string'
? options
: Object.assign({}, encRef.defaultEncoderOptions, options);
this.encodedWith[encName] = this.workerPool.dispatchJob({
operation: 'encode',
bitmap,
encName,
encConfig,
optimizerButteraugliTarget: Number(
encodeOptions.optimizerButteraugliTarget ?? 1.4,
),
maxOptimizerRounds: Number(encodeOptions.maxOptimizerRounds ?? 6),
});
setEncodedWithPromises.push(
this.workerPool
.dispatchJob({
operation: 'encode',
bitmap,
encName,
encConfig,
optimizerButteraugliTarget: Number(
encodeOptions.optimizerButteraugliTarget ?? 1.4,
),
maxOptimizerRounds: Number(encodeOptions.maxOptimizerRounds ?? 6),
})
.then((encodeResult) => {
this.encodedWith[encName] = encodeResult;
}),
);
}
await Promise.all(Object.values(this.encodedWith));
await Promise.all(setEncodedWithPromises);
return this.encodedWith as { [key in keyof T]: EncodeResult };
}
}
@ -254,19 +278,19 @@ class ImagePool {
/**
* Create a new pool.
* @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system.
* @param {number} [threads] - Number of concurrent image processes to run in the pool.
*/
constructor(threads: number) {
this.workerPool = new WorkerPool(threads || cpus().length, __filename);
this.workerPool = new WorkerPool(threads, __filename);
}
/**
* Ingest an image into the image pool.
* @param {FileLike} image - The image or path to the image that should be ingested and decoded.
* @param {ArrayBuffer | ArrayLike<number>} file - The image that should be ingested and decoded.
* @returns {Image} - A custom class reference to the decoded image.
*/
ingestImage(image: FileLike): Image {
return new Image(this.workerPool, image);
ingestImage(file: ArrayBuffer | ArrayLike<number>): Image {
return new Image(this.workerPool, file);
}
/**

10
missing-types.d.ts vendored
View File

@ -44,6 +44,16 @@ declare module 'data-url:*' {
export default url;
}
declare module 'data-url-text:*' {
const url: string;
export default url;
}
declare module 'as-text:*' {
const text: string;
export default text;
}
declare module 'service-worker:*' {
const url: string;
export default url;

View File

@ -32,6 +32,7 @@ import featurePlugin from './lib/feature-plugin';
import initialCssPlugin from './lib/initial-css-plugin';
import serviceWorkerPlugin from './lib/sw-plugin';
import dataURLPlugin from './lib/data-url-plugin';
import asTextPlugin from './lib/as-text-plugin';
import entryDataPlugin, { fileNameToURL } from './lib/entry-data-plugin';
import dedent from 'dedent';
@ -89,6 +90,7 @@ export default async function ({ watch }) {
'codecs',
]),
urlPlugin(),
asTextPlugin(),
dataURLPlugin(),
cssPlugin(),
];

View File

@ -37,8 +37,10 @@ main();
ga('set', 'transport', 'beacon');
ga('set', 'dimension1', displayMode);
ga('send', 'pageview', '/index.html', { title: 'Squoosh' });
// Load the GA script
const script = document.createElement('script');
script.src = 'https://www.google-analytics.com/analytics.js';
document.head.appendChild(script);
// Load the GA script without keeping the browser spinner going.
addEventListener('load', () => {
const script = document.createElement('script');
script.src = 'https://www.google-analytics.com/analytics.js';
document.head.appendChild(script);
});
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -14,7 +14,7 @@ import smallSectionAsset from 'url:./imgs/info-content/small.svg';
import simpleSectionAsset from 'url:./imgs/info-content/simple.svg';
import secureSectionAsset from 'url:./imgs/info-content/secure.svg';
import logoIcon from 'url:./imgs/demos/icon-demo-logo.png';
import logoWithText from 'url:./imgs/logo-with-text.svg';
import logoSVGSourceImport from 'as-text:./logo-svg-include.txt';
import * as style from './style.css';
import type SnackBarElement from 'shared/custom-els/snack-bar';
import 'shared/custom-els/snack-bar';
@ -69,6 +69,12 @@ async function getImageClipboardItem(
}
}
// The logo SVG source is pretty big, so to avoid it existing in both the HTML
// and the JS bundle, we pick it up from the HTML.
const logoSVGSource = __PRERENDER__
? logoSVGSourceImport
: document.querySelector('.' + style.logoContainer)!.innerHTML;
interface Props {
onFile?: (file: File) => void;
showSnack?: SnackBarElement['showSnackbar'];
@ -240,15 +246,10 @@ export default class Intro extends Component<Props, State> {
class={style.blobCanvas}
/>
)}
<h1 class={style.logoContainer}>
<img
class={style.logo}
src={logoWithText}
alt="Squoosh"
width="539"
height="162"
/>
</h1>
<h1
class={style.logoContainer}
dangerouslySetInnerHTML={{ __html: logoSVGSource }}
/>
<div class={style.loadImg}>
{showBlobSVG && (
<svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,3 +1,11 @@
/* Just the glyphs needed for the Squoosh logo */
@font-face {
font-family: logofont;
font-weight: 700;
src: url(data:font/woff;base64,d09GMgABAAAAAASoAA4AAAAACOQAAARUAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgnYcgXAGYABkEQwKhHiEFgsUAAE2AiQDJAQgBYJaByAbeQfIxId/N/1zb0JJk7qwVQyoSyhM6qJf2zN3wnP5MyX/c6mabKKFAVwk4QDd+3f7Z41FoYvttryoVK6NsWls0wJjfeO38YjNiCJjtHr/1Q8CwAqDCAUBr2QweT2PEoyA1elzSuDRPDnUg8D2odZuRPU0jvQhDaxz5rMh0dmYGFMJs4nu1qE+eGjM/weALmNBwUEBOxTgRXxFMYpg//e8Ci0oLAD5Z/l7jALyz0eIk0p5wwIKMGmoUVDk+I2Voc2UQOpf+V8SHykzsc2YglhhpAxij4qYCCDLLiZGpIxaxGLgdEfA8VXagNHBPOEGN4kdxIwSYCOPFjwtuDxUnqS+IGdGuWtrAgUsQYZRHpDu7Qiw6ukb7cWFrWzAQoFsxEIZPaFS7IAQDQ4cvEx4DjEL+W8XY9w93bi6CVjaTB8XOUO3aIVhv7jXI3UoIVGDVOAYRkFZmv6lSiyebcjPN+BqWJ91Zp43fwUwPxAfFchViYiY6etaTAWH6kKIhE7SgEaAL0DMDnZmf6FCMTrQj0EMY1SW726Rv5Tvki/Jp/KJfCwf5XNoQXWWGi/LsNXQcgvxSnnJ8wFi43KUbaZAdno0JKEudnYudkHOvy6ZysqvkYTtt8jiu2VxJhNZfP99YVuKLLnyTX7rdSy8cT5WkrBgNPL1AkqEuyzOB+WZxXf5LSMoLzmnXHS9/9b7ZNHk7wAjFqQ3hMZy/nbFtQXbNAIRWP4jQ8kUVX7FG8L2Y4VH4/QvOjyltORefuthLJSWBnyUZCKMQBfeEbbfP3h5MvqPEuA12/1Or4a5yB8MYpzmv+DgZ3O9ntK5NjZdTEis7owRG/urcsYir+4JU2+cUdUY36zxbeoo1RsaBsJTkP7a8gPKCq64OCyytqmvKXVZE5mtjskqTmWW8Y3ttF43lZujm57W5+dN63XTObm6qWl9XsDErwGiVhMg/hoY+GugqNEE6n4NxH280sBFFITFZA/oMqby8zKmB3TZMfnqDC5tyrRUm/F7gKhWB4i/Bfj/FiCq1QHi7/643nboYs9I1K/XB/n/4eH+8COPf1aYl6yOya4KiL8ypq8/Qso6O+HoL9pfsL/okJObGJlwoSkkBZds6wfb4pYDYzxCEjIL09ll83QG11IUGgUAAJrLVRQvuOTOlNTbpvzOCcwPZllp+cGK/bUffKT+wRxg/trKyDwPAmVzJTtUmnnR/Ca2bGU0B5hvtjI2u3ZLmoci/IQP0Fz40ipk0Hb402H4k7/hScuRQrJj7rXEg+BR85QaWsDzADqVUNUxBLbRYp0JnCMELkwBnsiOEWgYqDGjYaHCzRoFPPDyyy3gg68goh8DmMQQOtGODoxAhShEIBKx9DgD+tGPdvSgFSrkIAcitEolEz3oIVeRDxy+4lvVSiuGMEbLtUDr97BiNKIPw8hCvzLeYjOefMW4mq5hUSguFWNZnL3D6EQ/+tLPa3ktEYgO08G8O9bbQgkWAA==)
format('woff2');
}
.intro {
composes: abs-fill from global;
-webkit-overflow-scrolling: touch;
@ -35,12 +43,12 @@
.logo-container {
margin: 5rem 0 1rem;
}
.logo {
transform: translate(-1%, 0);
width: 189px;
height: auto;
& svg {
transform: translate(-1%, 0);
width: 189px;
height: auto;
}
}
.load-img {