wip
# Conflicts: # codecs/cpp.Dockerfile # codecs/imagequant/example.html # codecs/webp/dec/webp_dec.d.ts # codecs/webp/dec/webp_dec.js # codecs/webp/dec/webp_dec.wasm # codecs/webp/enc/webp_enc.d.ts # codecs/webp/enc/webp_enc.js # codecs/webp/enc/webp_enc.wasm # package-lock.json # package.json # src/codecs/tiny.webp # src_old/codecs/encoders.ts # src_old/codecs/processor-worker/tiny.avif # src_old/codecs/processor-worker/tiny.webp # src_old/codecs/tiny.webp # src_old/components/compress/index.tsx # src_old/lib/util.ts # src_old/sw/util.ts
7
.gitignore
vendored
@@ -1,6 +1,9 @@
|
|||||||
|
.tmp
|
||||||
node_modules
|
node_modules
|
||||||
/build
|
|
||||||
/*.log
|
|
||||||
*.scss.d.ts
|
*.scss.d.ts
|
||||||
*.css.d.ts
|
*.css.d.ts
|
||||||
|
build
|
||||||
*.o
|
*.o
|
||||||
|
|
||||||
|
# Auto-generated by lib/image-worker-plugin.js
|
||||||
|
src/image-worker/index.ts
|
||||||
|
|||||||
4
.prettierrc.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
18
_headers.ejs
@@ -1,18 +0,0 @@
|
|||||||
# Long-term cache by default.
|
|
||||||
/*
|
|
||||||
Cache-Control: max-age=31536000
|
|
||||||
|
|
||||||
# And here are the exceptions:
|
|
||||||
/
|
|
||||||
Cache-Control: no-cache
|
|
||||||
|
|
||||||
/serviceworker.js
|
|
||||||
Cache-Control: no-cache
|
|
||||||
|
|
||||||
/manifest.json
|
|
||||||
Cache-Control: must-revalidate, max-age=3600
|
|
||||||
|
|
||||||
# URLs in /assets do not include a hash and are mutable.
|
|
||||||
# But it isn't a big deal if the user gets an old version.
|
|
||||||
/assets/*
|
|
||||||
Cache-Control: must-revalidate, max-age=3600
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM emscripten/emsdk:1.40.0
|
FROM emscripten/emsdk:2.0.3
|
||||||
RUN apt-get update && apt-get install -qqy autoconf libtool pkg-config
|
RUN apt-get update && apt-get install -qqy autoconf libtool pkg-config
|
||||||
ENV CFLAGS "-Os -flto"
|
ENV CFLAGS "-Os -flto"
|
||||||
ENV CXXFLAGS "${CFLAGS} -std=c++17"
|
ENV CXXFLAGS "${CFLAGS} -std=c++17"
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ all: $(OUT_JS)
|
|||||||
--closure 1 \
|
--closure 1 \
|
||||||
-s ALLOW_MEMORY_GROWTH=1 \
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
-s MODULARIZE=1 \
|
-s MODULARIZE=1 \
|
||||||
-s 'EXPORT_NAME="$(basename $(@F))"' \
|
-s TEXTDECODER=2 \
|
||||||
|
-s ENVIRONMENT='worker' \
|
||||||
|
-s EXPORT_ES6=1 \
|
||||||
-o $@ \
|
-o $@ \
|
||||||
$+
|
$+
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<style>
|
<style>
|
||||||
canvas {
|
canvas {
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src='imagequant.js'></script>
|
<script type="module">
|
||||||
<script>
|
import imagequant from './imagequant.js';
|
||||||
const Module = imagequant();
|
|
||||||
|
|
||||||
async function loadImage(src) {
|
async function loadImage(src) {
|
||||||
// Load image
|
// Load image
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = src;
|
img.src = src;
|
||||||
await new Promise(resolve => img.onload = resolve);
|
await new Promise((resolve) => (img.onload = resolve));
|
||||||
// Make canvas same size as image
|
// Make canvas same size as image
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
[canvas.width, canvas.height] = [img.width, img.height];
|
[canvas.width, canvas.height] = [img.width, img.height];
|
||||||
@@ -22,19 +21,32 @@
|
|||||||
return ctx.getImageData(0, 0, img.width, img.height);
|
return ctx.getImageData(0, 0, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.onRuntimeInitialized = async _ => {
|
async function main() {
|
||||||
console.log('Version:', Module.version().toString(16));
|
const module = await imagequant();
|
||||||
|
|
||||||
|
console.log('Version:', module.version().toString(16));
|
||||||
const image = await loadImage('../example.png');
|
const image = await loadImage('../example.png');
|
||||||
// const rawImage = Module.quantize(image.data, image.width, image.height, 256, 1.0);
|
const rawImage = module.quantize(
|
||||||
const rawImage = Module.zx_quantize(image.data, image.width, image.height, 1.0);
|
image.data,
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
256,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
console.log('done');
|
console.log('done');
|
||||||
|
|
||||||
const imageData = new ImageData(new Uint8ClampedArray(rawImage.buffer), image.width, image.height);
|
const imageData = new ImageData(
|
||||||
|
new Uint8ClampedArray(rawImage.buffer),
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
);
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = image.width;
|
canvas.width = image.width;
|
||||||
canvas.height = image.height;
|
canvas.height = image.height;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
document.body.appendChild(canvas);
|
document.body.appendChild(canvas);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
21
codecs/imagequant/imagequant.d.ts
vendored
@@ -1,6 +1,19 @@
|
|||||||
interface QuantizerModule extends EmscriptenWasm.Module {
|
export interface QuantizerModule extends EmscriptenWasm.Module {
|
||||||
quantize(data: BufferSource, width: number, height: number, numColors: number, dither: number): Uint8ClampedArray;
|
quantize(
|
||||||
zx_quantize(data: BufferSource, width: number, height: number, dither: number): Uint8ClampedArray;
|
data: BufferSource,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
numColors: number,
|
||||||
|
dither: number,
|
||||||
|
): Uint8ClampedArray;
|
||||||
|
zx_quantize(
|
||||||
|
data: BufferSource,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
dither: number,
|
||||||
|
): Uint8ClampedArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(opts: EmscriptenWasm.ModuleOpts): QuantizerModule;
|
declare var moduleFactory: EmscriptenWasm.ModuleFactory<QuantizerModule>;
|
||||||
|
|
||||||
|
export default moduleFactory;
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ all: $(OUT_JS)
|
|||||||
-I $(CODEC_DIR) \
|
-I $(CODEC_DIR) \
|
||||||
${CXXFLAGS} \
|
${CXXFLAGS} \
|
||||||
${LDFLAGS} \
|
${LDFLAGS} \
|
||||||
--bind \
|
|
||||||
--closure 1 \
|
--closure 1 \
|
||||||
|
--bind \
|
||||||
-s ALLOW_MEMORY_GROWTH=1 \
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
-s MODULARIZE=1 \
|
-s MODULARIZE=1 \
|
||||||
-s 'EXPORT_NAME="$(basename $(@F))"' \
|
-s TEXTDECODER=2 \
|
||||||
|
-s ENVIRONMENT='worker' \
|
||||||
|
-s EXPORT_ES6=1 \
|
||||||
-o $@ \
|
-o $@ \
|
||||||
$+
|
$+
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<script src='mozjpeg_enc.js'></script>
|
<script type="module">
|
||||||
<script>
|
import mozjpeg_enc from './mozjpeg_enc.js';
|
||||||
const module = mozjpeg_enc();
|
|
||||||
|
|
||||||
async function loadImage(src) {
|
async function loadImage(src) {
|
||||||
// Load image
|
// Load image
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = src;
|
img.src = src;
|
||||||
await new Promise(resolve => img.onload = resolve);
|
await new Promise((resolve) => (img.onload = resolve));
|
||||||
// Make canvas same size as image
|
// Make canvas same size as image
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
[canvas.width, canvas.height] = [img.width, img.height];
|
[canvas.width, canvas.height] = [img.width, img.height];
|
||||||
@@ -17,7 +16,9 @@
|
|||||||
return ctx.getImageData(0, 0, img.width, img.height);
|
return ctx.getImageData(0, 0, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.onRuntimeInitialized = async _ => {
|
async function main() {
|
||||||
|
const module = await mozjpeg_enc();
|
||||||
|
|
||||||
console.log('Version:', module.version().toString(16));
|
console.log('Version:', module.version().toString(16));
|
||||||
const image = await loadImage('../example.png');
|
const image = await loadImage('../example.png');
|
||||||
const result = module.encode(image.data, image.width, image.height, {
|
const result = module.encode(image.data, image.width, image.height, {
|
||||||
@@ -39,10 +40,12 @@
|
|||||||
chroma_quality: 75,
|
chroma_quality: 75,
|
||||||
});
|
});
|
||||||
|
|
||||||
const blob = new Blob([result], {type: 'image/jpeg'});
|
const blob = new Blob([result], { type: 'image/jpeg' });
|
||||||
const blobURL = URL.createObjectURL(blob);
|
const blobURL = URL.createObjectURL(blob);
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = blobURL;
|
img.src = blobURL;
|
||||||
document.body.appendChild(img);
|
document.body.appendChild(img);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
15
codecs/mozjpeg_enc/mozjpeg_enc.d.ts
vendored
@@ -1,7 +1,14 @@
|
|||||||
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder-meta';
|
import { EncodeOptions } from 'image-worker/mozjpegEncode';
|
||||||
|
|
||||||
interface MozJPEGModule extends EmscriptenWasm.Module {
|
export interface MozJPEGModule extends EmscriptenWasm.Module {
|
||||||
encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array;
|
encode(
|
||||||
|
data: BufferSource,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
options: EncodeOptions,
|
||||||
|
): Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(opts: EmscriptenWasm.ModuleOpts): MozJPEGModule;
|
declare var moduleFactory: EmscriptenWasm.ModuleFactory<MozJPEGModule>;
|
||||||
|
|
||||||
|
export default moduleFactory;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
CODEC_URL := https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz
|
CODEC_URL := https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.1.0.tar.gz
|
||||||
CODEC_DIR = node_modules/libwebp
|
CODEC_DIR = node_modules/libwebp
|
||||||
CODEC_OUT_RELATIVE = src/.libs/libwebp.a
|
CODEC_OUT_RELATIVE = src/.libs/libwebp.a
|
||||||
CODEC_OUT := $(addprefix $(CODEC_DIR)/, $(CODEC_OUT_RELATIVE))
|
CODEC_OUT := $(addprefix $(CODEC_DIR)/, $(CODEC_OUT_RELATIVE))
|
||||||
@@ -18,7 +18,9 @@ all: $(OUT_JS)
|
|||||||
--closure 1 \
|
--closure 1 \
|
||||||
-s ALLOW_MEMORY_GROWTH=1 \
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
-s MODULARIZE=1 \
|
-s MODULARIZE=1 \
|
||||||
-s 'EXPORT_NAME="$(basename $(@F))"' \
|
-s TEXTDECODER=2 \
|
||||||
|
-s ENVIRONMENT='worker' \
|
||||||
|
-s EXPORT_ES6=1 \
|
||||||
-o $@ \
|
-o $@ \
|
||||||
$+
|
$+
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<script src='webp_dec.js'></script>
|
<script type="module">
|
||||||
<script>
|
import webp_dec from './webp_dec.js';
|
||||||
const Module = webp_dec();
|
|
||||||
|
|
||||||
async function loadFile(src) {
|
async function loadFile(src) {
|
||||||
const resp = await fetch(src);
|
const resp = await fetch(src);
|
||||||
return await resp.arrayBuffer();
|
return await resp.arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.onRuntimeInitialized = async _ => {
|
async function main() {
|
||||||
console.log('Version:', Module.version().toString(16));
|
const module = await webp_dec();
|
||||||
|
console.log('Version:', module.version().toString(16));
|
||||||
const image = await loadFile('../../example.webp');
|
const image = await loadFile('../../example.webp');
|
||||||
const imageData = Module.decode(image);
|
const imageData = module.decode(image);
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = result.width;
|
canvas.width = imageData.width;
|
||||||
canvas.height = result.height;
|
canvas.height = imageData.height;
|
||||||
document.body.appendChild(canvas);
|
document.body.appendChild(canvas);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
6
codecs/webp/dec/webp_dec.d.ts
vendored
@@ -1,5 +1,7 @@
|
|||||||
interface WebPModule extends EmscriptenWasm.Module {
|
export interface WebPModule extends EmscriptenWasm.Module {
|
||||||
decode(data: BufferSource): ImageData | null;
|
decode(data: BufferSource): ImageData | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(opts: EmscriptenWasm.ModuleOpts): WebPModule;
|
declare var moduleFactory: EmscriptenWasm.ModuleFactory<WebPModule>;
|
||||||
|
|
||||||
|
export default moduleFactory;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<script src='webp_enc.js'></script>
|
<script type="module">
|
||||||
<script>
|
import webp_enc from './webp_enc.js';
|
||||||
const module = webp_enc();
|
|
||||||
|
|
||||||
async function loadImage(src) {
|
async function loadImage(src) {
|
||||||
// Load image
|
// Load image
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = src;
|
img.src = src;
|
||||||
await new Promise(resolve => img.onload = resolve);
|
await new Promise((resolve) => (img.onload = resolve));
|
||||||
// Make canvas same size as image
|
// Make canvas same size as image
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
[canvas.width, canvas.height] = [img.width, img.height];
|
[canvas.width, canvas.height] = [img.width, img.height];
|
||||||
@@ -17,7 +16,9 @@
|
|||||||
return ctx.getImageData(0, 0, img.width, img.height);
|
return ctx.getImageData(0, 0, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.onRuntimeInitialized = async _ => {
|
async function main() {
|
||||||
|
const module = await webp_enc();
|
||||||
|
|
||||||
console.log('Version:', module.version().toString(16));
|
console.log('Version:', module.version().toString(16));
|
||||||
const image = await loadImage('../../example.png');
|
const image = await loadImage('../../example.png');
|
||||||
const result = module.encode(image.data, image.width, image.height, {
|
const result = module.encode(image.data, image.width, image.height, {
|
||||||
@@ -50,11 +51,13 @@
|
|||||||
use_sharp_yuv: 0,
|
use_sharp_yuv: 0,
|
||||||
});
|
});
|
||||||
console.log('size', result.length);
|
console.log('size', result.length);
|
||||||
const blob = new Blob([result], {type: 'image/webp'});
|
const blob = new Blob([result], { type: 'image/webp' });
|
||||||
|
|
||||||
const blobURL = URL.createObjectURL(blob);
|
const blobURL = URL.createObjectURL(blob);
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = blobURL;
|
img.src = blobURL;
|
||||||
document.body.appendChild(img);
|
document.body.appendChild(img);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
15
codecs/webp/enc/webp_enc.d.ts
vendored
@@ -1,7 +1,14 @@
|
|||||||
import { EncodeOptions } from '../../../src/codecs/webp/encoder-meta';
|
import { EncodeOptions } from 'image-worker/webpEncode';
|
||||||
|
|
||||||
interface WebPModule extends EmscriptenWasm.Module {
|
export interface WebPModule extends EmscriptenWasm.Module {
|
||||||
encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array | null;
|
encode(
|
||||||
|
data: BufferSource,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
options: EncodeOptions,
|
||||||
|
): Uint8Array | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(opts: EmscriptenWasm.ModuleOpts): WebPModule;
|
declare var moduleFactory: EmscriptenWasm.ModuleFactory<WebPModule>;
|
||||||
|
|
||||||
|
export default moduleFactory;
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
const DtsCreator = require('typed-css-modules');
|
|
||||||
const chokidar = require('chokidar');
|
|
||||||
const util = require('util');
|
|
||||||
const sass = require('node-sass');
|
|
||||||
const normalizePath = require('normalize-path');
|
|
||||||
|
|
||||||
const sassRender = util.promisify(sass.render);
|
|
||||||
|
|
||||||
async function sassToCss(path) {
|
|
||||||
const result = await sassRender({ file: path });
|
|
||||||
return result.css;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} Opts
|
|
||||||
* @property {boolean} watch Watch for changes
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Create typing files for CSS & SCSS.
|
|
||||||
*
|
|
||||||
* @param {string[]} rootPaths Paths to search within
|
|
||||||
* @param {Opts} [opts={}] Options.
|
|
||||||
*/
|
|
||||||
function addCssTypes(rootPaths, opts = {}) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const { watch = false } = opts;
|
|
||||||
|
|
||||||
const paths = [];
|
|
||||||
const preReadyPromises = [];
|
|
||||||
let ready = false;
|
|
||||||
|
|
||||||
for (const rootPath of rootPaths) {
|
|
||||||
const rootPathUnix = normalizePath(rootPath);
|
|
||||||
// Look for scss & css in each path.
|
|
||||||
paths.push(rootPathUnix + '/**/*.scss');
|
|
||||||
paths.push(rootPathUnix + '/**/*.css');
|
|
||||||
}
|
|
||||||
|
|
||||||
// For simplicity, the watcher is used even if we're not watching.
|
|
||||||
// If we're not watching, we stop the watcher after the initial files are found.
|
|
||||||
const watcher = chokidar.watch(paths, {
|
|
||||||
// Avoid processing already-processed files.
|
|
||||||
ignored: '*.d.*',
|
|
||||||
// Without this, travis and netlify builds never complete. I'm not sure why, but it might be
|
|
||||||
// related to https://github.com/paulmillr/chokidar/pull/758
|
|
||||||
persistent: watch,
|
|
||||||
});
|
|
||||||
|
|
||||||
function change(path) {
|
|
||||||
const promise = (async function() {
|
|
||||||
const creator = new DtsCreator({ camelCase: true });
|
|
||||||
const result = path.endsWith('.scss') ?
|
|
||||||
await creator.create(path, await sassToCss(path)) :
|
|
||||||
await creator.create(path);
|
|
||||||
|
|
||||||
await result.writeFile();
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!ready) preReadyPromises.push(promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
watcher.on('change', change);
|
|
||||||
watcher.on('add', change);
|
|
||||||
|
|
||||||
// 'ready' is when events have been fired for file discovery.
|
|
||||||
watcher.on('ready', () => {
|
|
||||||
ready = true;
|
|
||||||
// Wait for the current set of processing to finish.
|
|
||||||
Promise.all(preReadyPromises).then(resolve);
|
|
||||||
// And if we're not watching, close the watcher.
|
|
||||||
if (!watch) watcher.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = addCssTypes;
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const ejs = require('ejs');
|
|
||||||
const AssetsPlugin = require('assets-webpack-plugin');
|
|
||||||
|
|
||||||
module.exports = class AssetTemplatePlugin extends AssetsPlugin {
|
|
||||||
constructor(options) {
|
|
||||||
options = options || {};
|
|
||||||
if (!options.template) throw Error('AssetTemplatePlugin: template option is required.');
|
|
||||||
super({
|
|
||||||
useCompilerPath: true,
|
|
||||||
filename: options.filename,
|
|
||||||
processOutput: files => this._processOutput(files)
|
|
||||||
});
|
|
||||||
this._template = path.resolve(process.cwd(), options.template);
|
|
||||||
const ignore = options.ignore || /(manifest\.json|\.DS_Store)$/;
|
|
||||||
this._ignore = typeof ignore === 'function' ? ({ test: ignore }) : ignore;
|
|
||||||
}
|
|
||||||
|
|
||||||
_processOutput(files) {
|
|
||||||
const mapping = {
|
|
||||||
all: [],
|
|
||||||
byType: {},
|
|
||||||
entries: {}
|
|
||||||
};
|
|
||||||
for (const entryName in files) {
|
|
||||||
// non-entry-point-derived assets are collected under an empty string key
|
|
||||||
// since that's a bit awkward, we'll call them "assets"
|
|
||||||
const name = entryName === '' ? 'assets' : entryName;
|
|
||||||
const listing = files[entryName];
|
|
||||||
const entry = mapping.entries[name] = {
|
|
||||||
all: [],
|
|
||||||
byType: {}
|
|
||||||
};
|
|
||||||
for (let type in listing) {
|
|
||||||
const list = [].concat(listing[type]).filter(file => !this._ignore.test(file));
|
|
||||||
if (!list.length) continue;
|
|
||||||
mapping.all = mapping.all.concat(list);
|
|
||||||
mapping.byType[type] = (mapping.byType[type] || []).concat(list);
|
|
||||||
entry.all = entry.all.concat(list);
|
|
||||||
entry.byType[type] = (entry.byType[type] || []).concat(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapping.files = mapping.all;
|
|
||||||
return ejs.render(fs.readFileSync(this._template, 'utf8'), mapping);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
let loaderUtils = require('loader-utils');
|
|
||||||
let componentPath = require.resolve('./async-component');
|
|
||||||
|
|
||||||
module.exports = function () { };
|
|
||||||
module.exports.pitch = function (remainingRequest) {
|
|
||||||
this.cacheable && this.cacheable();
|
|
||||||
let query = loaderUtils.getOptions(this) || {};
|
|
||||||
let routeName = typeof query.name === 'function' ? query.name(this.resourcePath) : null;
|
|
||||||
let name;
|
|
||||||
if (routeName !== null) {
|
|
||||||
name = routeName;
|
|
||||||
}
|
|
||||||
else if ('name' in query) {
|
|
||||||
name = query.name;
|
|
||||||
}
|
|
||||||
else if ('formatName' in query) {
|
|
||||||
name = query.formatName(this.resourcePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
import async from ${JSON.stringify(componentPath)};
|
|
||||||
function load(cb) {
|
|
||||||
require.ensure([], function (require) {
|
|
||||||
cb( require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)}) );
|
|
||||||
}${name ? (', ' + JSON.stringify(name)) : ''});
|
|
||||||
}
|
|
||||||
export default async(load);
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { h, Component } from 'preact';
|
|
||||||
|
|
||||||
export default function (req) {
|
|
||||||
function Async() {
|
|
||||||
Component.call(this);
|
|
||||||
|
|
||||||
let b, old;
|
|
||||||
this.componentWillMount = () => {
|
|
||||||
b = this.base = this.nextBase || this.__b; // short circuits 1st render
|
|
||||||
req(m => {
|
|
||||||
this.setState({ child: m.default || m });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.shouldComponentUpdate = (_, nxt) => {
|
|
||||||
nxt = nxt.child === void 0;
|
|
||||||
if (nxt && old === void 0 && !!b) {
|
|
||||||
old = h(b.nodeName, { dangerouslySetInnerHTML: { __html: b.innerHTML } });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
old = ''; // dump it
|
|
||||||
}
|
|
||||||
return !nxt;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.render = (p, s) => s.child ? h(s.child, p) : old;
|
|
||||||
}
|
|
||||||
(Async.prototype = new Component()).constructor = Async;
|
|
||||||
return Async;
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
const util = require('util');
|
|
||||||
const minimatch = require('minimatch');
|
|
||||||
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
|
|
||||||
const WebWorkerTemplatePlugin = require('webpack/lib/webworker/WebWorkerTemplatePlugin');
|
|
||||||
const ParserHelpers = require('webpack/lib/ParserHelpers');
|
|
||||||
|
|
||||||
const NAME = 'auto-sw-plugin';
|
|
||||||
const JS_TYPES = ['auto', 'esm', 'dynamic'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically finds and bundles Service Workers by looking for navigator.serviceWorker.register(..).
|
|
||||||
* An Array of webpack assets is injected into the Service Worker bundle as a `BUILD_ASSETS` global.
|
|
||||||
* Hidden and `.map` files are excluded by default, and this can be customized using the include & exclude options.
|
|
||||||
* @example
|
|
||||||
* // webpack config
|
|
||||||
* plugins: [
|
|
||||||
* new AutoSWPlugin({
|
|
||||||
* exclude: [
|
|
||||||
* '**\/.*', // don't expose hidden files (default)
|
|
||||||
* '**\/*.map', // don't precache sourcemaps (default)
|
|
||||||
* 'index.html' // don't cache the page itself
|
|
||||||
* ]
|
|
||||||
* })
|
|
||||||
* ]
|
|
||||||
* @param {Object} [options={}]
|
|
||||||
* @param {string[]} [options.exclude] Minimatch pattern(s) of which assets to omit from BUILD_ASSETS.
|
|
||||||
* @param {string[]} [options.include] Minimatch pattern(s) of assets to allow in BUILD_ASSETS.
|
|
||||||
*/
|
|
||||||
module.exports = class AutoSWPlugin {
|
|
||||||
constructor(options) {
|
|
||||||
this.options = Object.assign({
|
|
||||||
exclude: [
|
|
||||||
'**/*.map',
|
|
||||||
'**/.*'
|
|
||||||
]
|
|
||||||
}, options || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(compiler) {
|
|
||||||
const serviceWorkers = [];
|
|
||||||
|
|
||||||
compiler.hooks.emit.tapPromise(NAME, compilation => this.emit(compiler, compilation, serviceWorkers));
|
|
||||||
|
|
||||||
compiler.hooks.normalModuleFactory.tap(NAME, (factory) => {
|
|
||||||
for (const type of JS_TYPES) {
|
|
||||||
factory.hooks.parser.for(`javascript/${type}`).tap(NAME, parser => {
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
const processRegisterCall = expr => {
|
|
||||||
const dep = parser.evaluateExpression(expr.arguments[0]);
|
|
||||||
|
|
||||||
if (!dep.isString()) {
|
|
||||||
parser.state.module.warnings.push({
|
|
||||||
message: 'navigator.serviceWorker.register() will only be bundled if passed a String literal.'
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = dep.string;
|
|
||||||
const outputFilename = this.options.filename || 'serviceworker.js'
|
|
||||||
const context = parser.state.current.context;
|
|
||||||
serviceWorkers.push({
|
|
||||||
outputFilename,
|
|
||||||
filename,
|
|
||||||
context
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = `__webpack__serviceworker__${++counter}`;
|
|
||||||
ParserHelpers.toConstantDependency(parser, id)(expr.arguments[0]);
|
|
||||||
return ParserHelpers.addParsedVariableToModule(parser, id, '__webpack_public_path__ + ' + JSON.stringify(outputFilename));
|
|
||||||
};
|
|
||||||
|
|
||||||
parser.hooks.call.for('navigator.serviceWorker.register').tap(NAME, processRegisterCall);
|
|
||||||
parser.hooks.call.for('self.navigator.serviceWorker.register').tap(NAME, processRegisterCall);
|
|
||||||
parser.hooks.call.for('window.navigator.serviceWorker.register').tap(NAME, processRegisterCall);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createFilter(list) {
|
|
||||||
const filters = [].concat(list);
|
|
||||||
for (let i=0; i<filters.length; i++) {
|
|
||||||
if (typeof filters[i] === 'string') {
|
|
||||||
filters[i] = minimatch.filter(filters[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
async emit(compiler, compilation, serviceWorkers) {
|
|
||||||
let assetMapping = Object.keys(compilation.assets);
|
|
||||||
if (this.options.include) {
|
|
||||||
const filters = this.createFilter(this.options.include);
|
|
||||||
assetMapping = assetMapping.filter(filename => {
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (filter(filename)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.options.exclude) {
|
|
||||||
const filters = this.createFilter(this.options.exclude);
|
|
||||||
assetMapping = assetMapping.filter(filename => {
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (filter(filename)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await Promise.all(serviceWorkers.map(
|
|
||||||
(serviceWorker, index) => this.compileServiceWorker(compiler, compilation, serviceWorker, index, assetMapping)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
async compileServiceWorker(compiler, compilation, options, index, assetMapping) {
|
|
||||||
const entryFilename = options.filename;
|
|
||||||
|
|
||||||
const chunkFilename = compiler.options.output.chunkFilename.replace(/\.([a-z]+)$/i, '.serviceworker.$1');
|
|
||||||
const workerOptions = {
|
|
||||||
filename: options.outputFilename, // chunkFilename.replace(/\.?\[(?:chunkhash|contenthash|hash)(:\d+(?::\d+)?)?\]/g, ''),
|
|
||||||
chunkFilename: this.options.chunkFilename || chunkFilename,
|
|
||||||
globalObject: 'self'
|
|
||||||
};
|
|
||||||
|
|
||||||
const childCompiler = compilation.createChildCompiler(NAME, { filename: workerOptions.filename });
|
|
||||||
(new WebWorkerTemplatePlugin(workerOptions)).apply(childCompiler);
|
|
||||||
|
|
||||||
/* The duplication DefinePlugin ends up causing is problematic (it doesn't hoist injections), so we'll do it manually. */
|
|
||||||
// (new DefinePlugin({
|
|
||||||
// BUILD_ASSETS: JSON.stringify(assetMapping)
|
|
||||||
// })).apply(childCompiler);
|
|
||||||
(new SingleEntryPlugin(options.context, entryFilename, workerOptions.filename)).apply(childCompiler);
|
|
||||||
|
|
||||||
const subCache = `subcache ${__dirname} ${entryFilename} ${index}`;
|
|
||||||
let childCompilation;
|
|
||||||
childCompiler.hooks.compilation.tap(NAME, c => {
|
|
||||||
childCompilation = c;
|
|
||||||
if (childCompilation.cache) {
|
|
||||||
if (!childCompilation.cache[subCache]) childCompilation.cache[subCache] = {};
|
|
||||||
childCompilation.cache = childCompilation.cache[subCache];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await (util.promisify(childCompiler.runAsChild.bind(childCompiler)))();
|
|
||||||
|
|
||||||
const versionVar = this.options.version ?
|
|
||||||
`var VERSION = ${JSON.stringify(this.options.version)};` : '';
|
|
||||||
const original = childCompilation.assets[workerOptions.filename].source();
|
|
||||||
const source = `${versionVar}var BUILD_ASSETS=${JSON.stringify(assetMapping)};${original}`;
|
|
||||||
childCompilation.assets[workerOptions.filename] = {
|
|
||||||
source: () => source,
|
|
||||||
size: () => Buffer.byteLength(source, 'utf8')
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(compilation.assets, childCompilation.assets);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
/** A Webpack plugin to refresh file mtime values from disk before compiling.
|
|
||||||
* This is used in order to account for SCSS-generated .d.ts files written
|
|
||||||
* as part of compilation so they trigger only a single recompile per write.
|
|
||||||
*
|
|
||||||
* All credit for the technique and implementation goes to @reiv. See:
|
|
||||||
* https://github.com/Jimdo/typings-for-css-modules-loader/issues/48#issuecomment-347036461
|
|
||||||
*/
|
|
||||||
module.exports = class WatchTimestampsPlugin {
|
|
||||||
constructor(patterns) {
|
|
||||||
this.patterns = patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(compiler) {
|
|
||||||
compiler.hooks.watchRun.tapAsync('watch-timestamps-plugin', (watch, callback) => {
|
|
||||||
const patterns = this.patterns;
|
|
||||||
const timestamps = watch.fileTimestamps;
|
|
||||||
|
|
||||||
for (const filepath of timestamps) {
|
|
||||||
if (patterns.some(pat => pat instanceof RegExp ? pat.test(filepath) : filepath.indexOf(pat) === 0)) {
|
|
||||||
let time = fs.statSync(filepath).mtime;
|
|
||||||
if (timestamps instanceof Map) timestamps.set(filepath, time);
|
|
||||||
else timestamps[filepath] = time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
51
emscripten-wasm.d.ts → emscripten-types.d.ts
vendored
@@ -1,7 +1,11 @@
|
|||||||
// These types roughly model the object that the JS files generated by Emscripten define. Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten/index.d.ts and turned into a type definition rather than a global to support our way of using Emscripten.
|
// These types roughly model the object that the JS files generated by Emscripten define. Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten/index.d.ts and turned into a type definition rather than a global to support our way of using Emscripten.
|
||||||
// TODO(@surma): Upstream this?
|
// TODO(@surma): Upstream this?
|
||||||
declare namespace EmscriptenWasm {
|
declare namespace EmscriptenWasm {
|
||||||
type EnvironmentType = "WEB" | "NODE" | "SHELL" | "WORKER";
|
type ModuleFactory<T extends Module = Module> = (
|
||||||
|
moduleOverrides?: ModuleOpts,
|
||||||
|
) => Promise<T>;
|
||||||
|
|
||||||
|
type EnvironmentType = 'WEB' | 'NODE' | 'SHELL' | 'WORKER';
|
||||||
|
|
||||||
// Options object for modularized Emscripten files. Shoe-horned by @surma.
|
// Options object for modularized Emscripten files. Shoe-horned by @surma.
|
||||||
// FIXME: This an incomplete definition!
|
// FIXME: This an incomplete definition!
|
||||||
@@ -16,9 +20,9 @@ declare namespace EmscriptenWasm {
|
|||||||
printErr(str: string): void;
|
printErr(str: string): void;
|
||||||
arguments: string[];
|
arguments: string[];
|
||||||
environment: EnvironmentType;
|
environment: EnvironmentType;
|
||||||
preInit: { (): void }[];
|
preInit: { (): void }[];
|
||||||
preRun: { (): void }[];
|
preRun: { (): void }[];
|
||||||
postRun: { (): void }[];
|
postRun: { (): void }[];
|
||||||
preinitializedWebGLContext: WebGLRenderingContext;
|
preinitializedWebGLContext: WebGLRenderingContext;
|
||||||
noInitialRun: boolean;
|
noInitialRun: boolean;
|
||||||
noExitRuntime: boolean;
|
noExitRuntime: boolean;
|
||||||
@@ -27,17 +31,25 @@ declare namespace EmscriptenWasm {
|
|||||||
wasmBinary: ArrayBuffer;
|
wasmBinary: ArrayBuffer;
|
||||||
|
|
||||||
destroy(object: object): void;
|
destroy(object: object): void;
|
||||||
getPreloadedPackage(remotePackageName: string, remotePackageSize: number): ArrayBuffer;
|
getPreloadedPackage(
|
||||||
|
remotePackageName: string,
|
||||||
|
remotePackageSize: number,
|
||||||
|
): ArrayBuffer;
|
||||||
instantiateWasm(
|
instantiateWasm(
|
||||||
imports: WebAssembly.Imports,
|
imports: WebAssembly.Imports,
|
||||||
successCallback: (module: WebAssembly.Module) => void
|
successCallback: (module: WebAssembly.Module) => void,
|
||||||
): WebAssembly.Exports;
|
): WebAssembly.Exports;
|
||||||
locateFile(url: string): string;
|
locateFile(url: string): string;
|
||||||
onCustomMessage(event: MessageEvent): void;
|
onCustomMessage(event: MessageEvent): void;
|
||||||
|
|
||||||
Runtime: any;
|
Runtime: any;
|
||||||
|
|
||||||
ccall(ident: string, returnType: string | null, argTypes: string[], args: any[]): any;
|
ccall(
|
||||||
|
ident: string,
|
||||||
|
returnType: string | null,
|
||||||
|
argTypes: string[],
|
||||||
|
args: any[],
|
||||||
|
): any;
|
||||||
cwrap(ident: string, returnType: string | null, argTypes: string[]): any;
|
cwrap(ident: string, returnType: string | null, argTypes: string[]): any;
|
||||||
|
|
||||||
setValue(ptr: number, value: any, type: string, noSafe?: boolean): void;
|
setValue(ptr: number, value: any, type: string, noSafe?: boolean): void;
|
||||||
@@ -50,7 +62,12 @@ declare namespace EmscriptenWasm {
|
|||||||
ALLOC_NONE: number;
|
ALLOC_NONE: number;
|
||||||
|
|
||||||
allocate(slab: any, types: string, allocator: number, ptr: number): number;
|
allocate(slab: any, types: string, allocator: number, ptr: number): number;
|
||||||
allocate(slab: any, types: string[], allocator: number, ptr: number): number;
|
allocate(
|
||||||
|
slab: any,
|
||||||
|
types: string[],
|
||||||
|
allocator: number,
|
||||||
|
ptr: number,
|
||||||
|
): number;
|
||||||
|
|
||||||
Pointer_stringify(ptr: number, length?: number): string;
|
Pointer_stringify(ptr: number, length?: number): string;
|
||||||
UTF16ToString(ptr: number): string;
|
UTF16ToString(ptr: number): string;
|
||||||
@@ -67,7 +84,7 @@ declare namespace EmscriptenWasm {
|
|||||||
HEAP8: Int8Array;
|
HEAP8: Int8Array;
|
||||||
HEAP16: Int16Array;
|
HEAP16: Int16Array;
|
||||||
HEAP32: Int32Array;
|
HEAP32: Int32Array;
|
||||||
HEAPU8: Uint8Array;
|
HEAPU8: Uint8Array;
|
||||||
HEAPU16: Uint16Array;
|
HEAPU16: Uint16Array;
|
||||||
HEAPU32: Uint32Array;
|
HEAPU32: Uint32Array;
|
||||||
HEAPF32: Float32Array;
|
HEAPF32: Float32Array;
|
||||||
@@ -84,16 +101,23 @@ declare namespace EmscriptenWasm {
|
|||||||
addOnPostRun(cb: () => any): void;
|
addOnPostRun(cb: () => any): void;
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
intArrayFromString(stringy: string, dontAddNull?: boolean, length?: number): number[];
|
intArrayFromString(
|
||||||
|
stringy: string,
|
||||||
|
dontAddNull?: boolean,
|
||||||
|
length?: number,
|
||||||
|
): number[];
|
||||||
intArrayToString(array: number[]): string;
|
intArrayToString(array: number[]): string;
|
||||||
writeStringToMemory(str: string, buffer: number, dontAddNull: boolean): void;
|
writeStringToMemory(
|
||||||
|
str: string,
|
||||||
|
buffer: number,
|
||||||
|
dontAddNull: boolean,
|
||||||
|
): void;
|
||||||
writeArrayToMemory(array: number[], buffer: number): void;
|
writeArrayToMemory(array: number[], buffer: number): void;
|
||||||
writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void;
|
writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void;
|
||||||
|
|
||||||
addRunDependency(id: any): void;
|
addRunDependency(id: any): void;
|
||||||
removeRunDependency(id: any): void;
|
removeRunDependency(id: any): void;
|
||||||
|
|
||||||
|
|
||||||
preloadedImages: any;
|
preloadedImages: any;
|
||||||
preloadedAudios: any;
|
preloadedAudios: any;
|
||||||
|
|
||||||
@@ -104,4 +128,3 @@ declare namespace EmscriptenWasm {
|
|||||||
onRuntimeInitialized: () => void | null;
|
onRuntimeInitialized: () => void | null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
22
generic-tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2019",
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"jsx": "react",
|
||||||
|
"jsxFactory": "h",
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"composite": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"rootDir": "./",
|
||||||
|
"outDir": ".tmp/ts",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"paths": {
|
||||||
|
"static-build/*": ["src/static-build/*"],
|
||||||
|
"image-worker/*": ["src/image-worker/*"],
|
||||||
|
"worker-main-shared/*": ["src/worker-main-shared/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
global.d.ts
vendored
@@ -1,23 +0,0 @@
|
|||||||
declare const __webpack_public_path__: string;
|
|
||||||
declare const PRERENDER: boolean;
|
|
||||||
|
|
||||||
declare interface NodeModule {
|
|
||||||
hot: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface Window {
|
|
||||||
STATE: any;
|
|
||||||
ga: typeof ga;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare namespace JSX {
|
|
||||||
interface Element { }
|
|
||||||
interface IntrinsicElements { }
|
|
||||||
interface HTMLAttributes {
|
|
||||||
decoding?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'classnames' {
|
|
||||||
export default function classnames(...args: any[]): string;
|
|
||||||
}
|
|
||||||
75
lib/asset-plugin.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
import { basename } from 'path';
|
||||||
|
|
||||||
|
const defaultOpts = {
|
||||||
|
prefix: 'url',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function urlPlugin(opts) {
|
||||||
|
opts = Object.assign({}, defaultOpts, opts);
|
||||||
|
|
||||||
|
/** @type {Map<string, Buffer>} */
|
||||||
|
let assetIdToSourceBuffer;
|
||||||
|
|
||||||
|
const prefix = opts.prefix + ':';
|
||||||
|
return {
|
||||||
|
name: 'url-plugin',
|
||||||
|
buildStart() {
|
||||||
|
assetIdToSourceBuffer = new Map();
|
||||||
|
},
|
||||||
|
augmentChunkHash(info) {
|
||||||
|
// Get the sources for all assets imported by this chunk.
|
||||||
|
const buffers = Object.keys(info.modules)
|
||||||
|
.map((moduleId) => assetIdToSourceBuffer.get(moduleId))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (buffers.length === 0) return;
|
||||||
|
|
||||||
|
for (const moduleId of Object.keys(info.modules)) {
|
||||||
|
const buffer = assetIdToSourceBuffer.get(moduleId);
|
||||||
|
if (buffer) buffers.push(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedBuffer =
|
||||||
|
buffers.length === 1 ? buffers[0] : Buffer.concat(buffers);
|
||||||
|
|
||||||
|
return combinedBuffer;
|
||||||
|
},
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
// Add an additional .js to the end so it ends up with .js at the end in the _virtual folder.
|
||||||
|
return prefix + resolveResult.id + '.js';
|
||||||
|
},
|
||||||
|
async load(id) {
|
||||||
|
if (!id.startsWith(prefix)) return;
|
||||||
|
const realId = id.slice(prefix.length, -'.js'.length);
|
||||||
|
const source = await fs.readFile(realId);
|
||||||
|
assetIdToSourceBuffer.set(id, source);
|
||||||
|
this.addWatchFile(realId);
|
||||||
|
|
||||||
|
return `export default import.meta.ROLLUP_FILE_URL_${this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
source,
|
||||||
|
name: basename(realId),
|
||||||
|
})}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
163
lib/client-bundle-plugin.js
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/**
|
||||||
|
* 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 rollup from 'rollup';
|
||||||
|
|
||||||
|
const prefix = 'client-bundle:';
|
||||||
|
const entryPathPlaceholder = 'CLIENT_BUNDLE_PLUGIN_ENTRY_PATH';
|
||||||
|
const importsPlaceholder = 'CLIENT_BUNDLE_PLUGIN_IMPORTS';
|
||||||
|
|
||||||
|
export function getDependencies(clientOutput, item) {
|
||||||
|
const crawlDependencies = new Set([item.fileName]);
|
||||||
|
|
||||||
|
for (const fileName of crawlDependencies) {
|
||||||
|
const chunk = clientOutput.find((v) => v.fileName === fileName);
|
||||||
|
|
||||||
|
for (const dep of chunk.imports) {
|
||||||
|
crawlDependencies.add(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't add self as dependency
|
||||||
|
crawlDependencies.delete(item.fileName);
|
||||||
|
|
||||||
|
return [...crawlDependencies];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (inputOptions, outputOptions, resolveFileUrl) {
|
||||||
|
let cache;
|
||||||
|
let entryPointPlaceholderMap;
|
||||||
|
let exportCounter;
|
||||||
|
let clientBundle;
|
||||||
|
let clientOutput;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'client-bundle',
|
||||||
|
buildStart() {
|
||||||
|
entryPointPlaceholderMap = new Map();
|
||||||
|
exportCounter = 0;
|
||||||
|
},
|
||||||
|
async resolveId(id, importer) {
|
||||||
|
if (!id.startsWith(prefix)) return null;
|
||||||
|
|
||||||
|
const realId = id.slice(prefix.length);
|
||||||
|
const resolveResult = await this.resolve(realId, importer);
|
||||||
|
// Add an additional .js to the end so it ends up with .js at the end in the _virtual folder.
|
||||||
|
if (resolveResult) return prefix + resolveResult.id + '.js';
|
||||||
|
// This Rollup couldn't resolve it, but maybe the inner one can.
|
||||||
|
return id + '.js';
|
||||||
|
},
|
||||||
|
load(id) {
|
||||||
|
if (!id.startsWith(prefix)) return;
|
||||||
|
|
||||||
|
const realId = id.slice(prefix.length, -'.js'.length);
|
||||||
|
|
||||||
|
exportCounter++;
|
||||||
|
|
||||||
|
entryPointPlaceholderMap.set(exportCounter, realId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
`export default import.meta.${entryPathPlaceholder + exportCounter};`,
|
||||||
|
`export const imports = import.meta.${
|
||||||
|
importsPlaceholder + exportCounter
|
||||||
|
};`,
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
async buildEnd(error) {
|
||||||
|
const entryPoints = [...entryPointPlaceholderMap.values()];
|
||||||
|
// The static-build is done, so now we can perform our client build.
|
||||||
|
// Exit early if there's nothing to build.
|
||||||
|
if (error || entryPoints.length === 0) return;
|
||||||
|
|
||||||
|
clientBundle = await rollup.rollup({
|
||||||
|
...inputOptions,
|
||||||
|
cache,
|
||||||
|
input: entryPoints,
|
||||||
|
});
|
||||||
|
|
||||||
|
cache = clientBundle.cache;
|
||||||
|
},
|
||||||
|
async renderStart(staticBuildOutputOpts) {
|
||||||
|
// The static-build has started generating output, so we can do the same for our client build.
|
||||||
|
// Exit early if there's nothing to build.
|
||||||
|
if (!clientBundle) return;
|
||||||
|
const copiedOutputOptions = {
|
||||||
|
assetFileNames: staticBuildOutputOpts.assetFileNames,
|
||||||
|
};
|
||||||
|
clientOutput = (
|
||||||
|
await clientBundle.generate({
|
||||||
|
...copiedOutputOptions,
|
||||||
|
...outputOptions,
|
||||||
|
})
|
||||||
|
).output;
|
||||||
|
},
|
||||||
|
resolveImportMeta(property, { moduleId, format }) {
|
||||||
|
// Pick up the placeholder exports we created earlier, and fill in the correct details.
|
||||||
|
let num = undefined;
|
||||||
|
|
||||||
|
if (property.startsWith(entryPathPlaceholder)) {
|
||||||
|
num = Number(property.slice(entryPathPlaceholder.length));
|
||||||
|
} else if (property.startsWith(importsPlaceholder)) {
|
||||||
|
num = Number(property.slice(importsPlaceholder.length));
|
||||||
|
} else {
|
||||||
|
// This isn't one of our placeholders.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = entryPointPlaceholderMap.get(num);
|
||||||
|
const clientEntry = clientOutput.find(
|
||||||
|
(item) => item.facadeModuleId === id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (property.startsWith(entryPathPlaceholder)) {
|
||||||
|
return resolveFileUrl({
|
||||||
|
fileName: clientEntry.fileName,
|
||||||
|
moduleId,
|
||||||
|
format,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = getDependencies(clientOutput, clientEntry);
|
||||||
|
|
||||||
|
return (
|
||||||
|
'[' +
|
||||||
|
dependencies
|
||||||
|
.map((item) => {
|
||||||
|
const entry = clientOutput.find((v) => v.fileName === item);
|
||||||
|
|
||||||
|
return resolveFileUrl({
|
||||||
|
fileName: entry.fileName,
|
||||||
|
moduleId,
|
||||||
|
format: outputOptions.format,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.join(',') +
|
||||||
|
']'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async generateBundle(options, bundle) {
|
||||||
|
// Exit early if there's nothing to build.
|
||||||
|
if (!clientOutput) return;
|
||||||
|
// Copy everything from the client bundle into the main bundle.
|
||||||
|
for (const clientEntry of clientOutput) {
|
||||||
|
// Skip if the file already exists
|
||||||
|
if (clientEntry.fileName in bundle) continue;
|
||||||
|
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
source: clientEntry.code || clientEntry.source,
|
||||||
|
fileName: clientEntry.fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
195
lib/css-plugin.js
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* 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 fsp, readFileSync } from 'fs';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { parse as parsePath, resolve as resolvePath, dirname } from 'path';
|
||||||
|
|
||||||
|
import postcss from 'postcss';
|
||||||
|
import postCSSNested from 'postcss-nested';
|
||||||
|
import postCSSUrl from 'postcss-url';
|
||||||
|
import postCSSModules from 'postcss-modules';
|
||||||
|
import postCSSImport from 'postcss-import';
|
||||||
|
import postCSSSimpleVars from 'postcss-simple-vars';
|
||||||
|
import cssNano from 'cssnano';
|
||||||
|
import camelCase from 'lodash.camelcase';
|
||||||
|
import glob from 'glob';
|
||||||
|
|
||||||
|
const globP = promisify(glob);
|
||||||
|
|
||||||
|
const moduleSuffix = '.css';
|
||||||
|
const prefix = 'css-bundle:';
|
||||||
|
const assetRe = new RegExp('/fake/path/to/asset/([^/]+)/', 'g');
|
||||||
|
|
||||||
|
export default function (resolveFileUrl) {
|
||||||
|
/** @type {string[]} */
|
||||||
|
let emittedCSSIds;
|
||||||
|
/** @type {Map<string, string>} */
|
||||||
|
let hashToId;
|
||||||
|
/** @type {Map<string, { module: string, css: string }>} */
|
||||||
|
let pathToResult;
|
||||||
|
|
||||||
|
async function loadBundledCSS(path, rollupContext) {
|
||||||
|
const parsedPath = parsePath(path);
|
||||||
|
|
||||||
|
if (!pathToResult.has(path)) {
|
||||||
|
throw Error(`Cannot find ${path} in pathToResult`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = pathToResult.get(path).css;
|
||||||
|
|
||||||
|
const cssResult = await postcss([
|
||||||
|
postCSSImport({
|
||||||
|
path: ['./', 'static-build/'],
|
||||||
|
load(path) {
|
||||||
|
if (!pathToResult.has(path)) {
|
||||||
|
throw Error(`Cannot find ${path} in pathToResult`);
|
||||||
|
}
|
||||||
|
return pathToResult.get(path).css;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
postCSSUrl({
|
||||||
|
url: ({ relativePath, url }) => {
|
||||||
|
if (/^https?:\/\//.test(url)) return url;
|
||||||
|
const parsedPath = parsePath(relativePath);
|
||||||
|
const source = readFileSync(resolvePath(dirname(path), relativePath));
|
||||||
|
const fileId = rollupContext.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
name: parsedPath.base,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
const hash = createHash('md5');
|
||||||
|
hash.update(source);
|
||||||
|
const md5 = hash.digest('hex');
|
||||||
|
hashToId.set(md5, fileId);
|
||||||
|
return `/fake/path/to/asset/${md5}/`;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
cssNano,
|
||||||
|
]).process(file, {
|
||||||
|
from: path,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileId = rollupContext.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
source: cssResult.css,
|
||||||
|
name: parsedPath.base,
|
||||||
|
});
|
||||||
|
|
||||||
|
emittedCSSIds.push(fileId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
`export default import.meta.ROLLUP_FILE_URL_${fileId}`,
|
||||||
|
`export const inline = ${JSON.stringify(cssResult.css)}`,
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'css',
|
||||||
|
async buildStart() {
|
||||||
|
emittedCSSIds = [];
|
||||||
|
hashToId = new Map();
|
||||||
|
pathToResult = new Map();
|
||||||
|
|
||||||
|
const cssPaths = await globP('src/static-build/**/*.css', {
|
||||||
|
nodir: true,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
cssPaths.map(async (path) => {
|
||||||
|
this.addWatchFile(path);
|
||||||
|
const file = await fsp.readFile(path);
|
||||||
|
let moduleJSON;
|
||||||
|
|
||||||
|
const cssResult = await postcss([
|
||||||
|
postCSSNested,
|
||||||
|
postCSSSimpleVars(),
|
||||||
|
postCSSModules({
|
||||||
|
getJSON(_, json) {
|
||||||
|
moduleJSON = json;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]).process(file, {
|
||||||
|
from: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssClassExports = Object.entries(moduleJSON).map(
|
||||||
|
([key, val]) =>
|
||||||
|
`export const $${camelCase(key)} = ${JSON.stringify(val)};`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const defs = Object.keys(moduleJSON)
|
||||||
|
.map((key) => `export const $${camelCase(key)}: string;`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const defPath = path + '.d.ts';
|
||||||
|
const currentDefFileContent = await fsp
|
||||||
|
.readFile(defPath, { encoding: 'utf8' })
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
// Only write the file if contents have changed, otherwise it causes a loop with
|
||||||
|
// TypeScript's file watcher.
|
||||||
|
if (defs !== currentDefFileContent) {
|
||||||
|
await fsp.writeFile(defPath, defs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathToResult.set(path, {
|
||||||
|
module: cssClassExports.join('\n'),
|
||||||
|
css: cssResult.css,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async resolveId(id, importer) {
|
||||||
|
if (!id.startsWith(prefix)) return;
|
||||||
|
const resolved = await this.resolve(id.slice(prefix.length), importer);
|
||||||
|
if (!resolved) throw Error(`Couldn't resolve ${id} from ${importer}`);
|
||||||
|
return prefix + resolved.id;
|
||||||
|
},
|
||||||
|
async load(id) {
|
||||||
|
if (id.startsWith(prefix)) {
|
||||||
|
return loadBundledCSS(id.slice(prefix.length), this);
|
||||||
|
}
|
||||||
|
if (id.endsWith(moduleSuffix)) {
|
||||||
|
if (!pathToResult.has(id)) {
|
||||||
|
throw Error(`Cannot find ${id} in pathToResult`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathToResult.get(id).module;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async generateBundle(options, bundle) {
|
||||||
|
const cssAssets = emittedCSSIds.map((id) => this.getFileName(id));
|
||||||
|
|
||||||
|
for (const cssAsset of cssAssets) {
|
||||||
|
bundle[cssAsset].source = bundle[cssAsset].source.replace(
|
||||||
|
assetRe,
|
||||||
|
(_, p1) =>
|
||||||
|
resolveFileUrl({
|
||||||
|
fileName: this.getFileName(hashToId.get(p1)),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of Object.values(bundle)) {
|
||||||
|
if (item.type === 'asset') continue;
|
||||||
|
item.code = item.code.replace(assetRe, (match, p1) =>
|
||||||
|
resolveFileUrl({
|
||||||
|
fileName: this.getFileName(hashToId.get(p1)),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
37
lib/emit-files-plugin.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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 * as path from 'path';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import glob from 'glob';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
|
const globP = promisify(glob);
|
||||||
|
|
||||||
|
export default function emitFiles({ root, include }) {
|
||||||
|
return {
|
||||||
|
name: 'emit-files-plugin',
|
||||||
|
async buildStart() {
|
||||||
|
const paths = await globP(include, { nodir: true, cwd: root });
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
paths.map(async (filePath) => {
|
||||||
|
return this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
source: await fs.readFile(path.join(root, filePath)),
|
||||||
|
fileName: 'static/' + filePath,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
55
lib/image-worker-plugin.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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 { promisify } from 'util';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { promises as fsp } from 'fs';
|
||||||
|
|
||||||
|
import glob from 'glob';
|
||||||
|
|
||||||
|
const globP = promisify(glob);
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
name: 'image-worker-plugin',
|
||||||
|
async buildStart() {
|
||||||
|
const base = path.join(process.cwd(), 'src', 'image-worker');
|
||||||
|
const dirs = (
|
||||||
|
await globP('*/', {
|
||||||
|
cwd: base,
|
||||||
|
})
|
||||||
|
).map((dir) => dir.slice(0, -1));
|
||||||
|
|
||||||
|
const file = [
|
||||||
|
`// This file is autogenerated by lib/image-worker-plugin.js`,
|
||||||
|
`import { expose } from 'comlink';`,
|
||||||
|
`import { timed } from './util';`,
|
||||||
|
dirs.map((dir) => `import ${dir} from './${dir}';`),
|
||||||
|
`const exports = {`,
|
||||||
|
dirs.map((dir) => [
|
||||||
|
` ${dir}(`,
|
||||||
|
` ...args: Parameters<typeof ${dir}>`,
|
||||||
|
` ): ReturnType<typeof ${dir}> {`,
|
||||||
|
` return timed('${dir}', () => ${dir}(...args));`,
|
||||||
|
` },`,
|
||||||
|
]),
|
||||||
|
`};`,
|
||||||
|
`export type ProcessorWorkerApi = typeof exports;`,
|
||||||
|
`expose(exports, self);`,
|
||||||
|
]
|
||||||
|
.flat(Infinity)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
await fsp.writeFile(path.join(base, 'index.ts'), file);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
19
lib/move-output.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// Move .tmp/build/static to docs/
|
||||||
|
const fs = require('fs');
|
||||||
|
const del = require('del');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
del.sync('build');
|
||||||
|
fs.renameSync(path.join('.tmp', 'build', 'static'), 'build');
|
||||||
24
lib/node-external-plugin.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// Check that a node module exists, but treat it as external.
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
name: 'node-external',
|
||||||
|
resolveId(id) {
|
||||||
|
try {
|
||||||
|
require.resolve(id);
|
||||||
|
return { id, external: true };
|
||||||
|
} catch (err) {}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
84
lib/omt.ejs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If the loader is already loaded, just stop.
|
||||||
|
if (!self.<%- amdFunctionName %>) {
|
||||||
|
const singleRequire = async name => {
|
||||||
|
if (name === 'require') return require;
|
||||||
|
const url = '/c/' + name.slice(2) + '.js';
|
||||||
|
name = './static' + url;
|
||||||
|
if (registry[name]) return registry[name];
|
||||||
|
|
||||||
|
if (!registry[name]) {
|
||||||
|
<% if (useEval) { %>
|
||||||
|
const text = await fetch(url).then(resp => resp.text());
|
||||||
|
eval(text);
|
||||||
|
<% } else { %>
|
||||||
|
if ("document" in self) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = url;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
script.onload = resolve;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
importScripts(url);
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
|
}
|
||||||
|
if (!registry[name]) {
|
||||||
|
throw new Error(`Module ${name} didn’t register its module`);
|
||||||
|
}
|
||||||
|
return registry[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
const require = (names, resolve) => {
|
||||||
|
Promise.all(names.map(singleRequire))
|
||||||
|
.then(modules => resolve(modules.length === 1 ? modules[0] : modules));
|
||||||
|
};
|
||||||
|
|
||||||
|
const registry = {
|
||||||
|
require: Promise.resolve(require)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.<%- amdFunctionName %> = (moduleName, depsNames, factory) => {
|
||||||
|
if (registry[moduleName]) {
|
||||||
|
// Module is already loading or loaded.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registry[moduleName] = Promise.resolve().then(() => {
|
||||||
|
let exports = {};
|
||||||
|
const module = {
|
||||||
|
uri: location.origin + moduleName.slice(1)
|
||||||
|
};
|
||||||
|
return Promise.all(
|
||||||
|
depsNames.map(depName => {
|
||||||
|
switch(depName) {
|
||||||
|
case "exports":
|
||||||
|
return exports;
|
||||||
|
case "module":
|
||||||
|
return module;
|
||||||
|
default:
|
||||||
|
return singleRequire(depName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).then(deps => {
|
||||||
|
const facValue = factory(...deps);
|
||||||
|
if(!exports.default) {
|
||||||
|
exports.default = facValue;
|
||||||
|
}
|
||||||
|
return exports;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
36
lib/resolve-dirs-plugin.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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 { posix as pathUtils } from 'path';
|
||||||
|
|
||||||
|
export default function resolveDirs(paths) {
|
||||||
|
const pathBaseDir = paths.map((path) => [
|
||||||
|
pathUtils.basename(path),
|
||||||
|
pathUtils.dirname(path),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'resolve-dirs',
|
||||||
|
async resolveId(id) {
|
||||||
|
const match = pathBaseDir.find(
|
||||||
|
([pathId]) => id === pathId || id.startsWith(pathId + '/'),
|
||||||
|
);
|
||||||
|
if (!match) return;
|
||||||
|
const pathDir = match[1];
|
||||||
|
const resolveResult = await this.resolve(`./${pathDir}/${id}`, './');
|
||||||
|
if (!resolveResult) {
|
||||||
|
throw new Error(`Couldn't find ${'./' + id}`);
|
||||||
|
}
|
||||||
|
return pathUtils.resolve(resolveResult.id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
34
lib/run-script.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* 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 { fork } from 'child_process';
|
||||||
|
|
||||||
|
export default function runScript(path) {
|
||||||
|
return {
|
||||||
|
name: 'run-script',
|
||||||
|
writeBundle() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const proc = fork(path, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(Error('Static build failed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
130
lib/simple-ts.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
const globP = promisify(glob);
|
||||||
|
|
||||||
|
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('tsc', args, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
throw Error('TypeScript build failed');
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await watchBuiltFiles(rollupContext);
|
||||||
|
|
||||||
|
if (watch) {
|
||||||
|
tsBuildDone.then(() => {
|
||||||
|
spawn('tsc', [...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(process.cwd(), id),
|
||||||
|
).replace(extRe, '.js');
|
||||||
|
|
||||||
|
return fsp.readFile(newId, { encoding: 'utf8' });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
23
missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/// <reference path="./emscripten-types.d.ts" />
|
||||||
|
|
||||||
|
declare module 'url:*' {
|
||||||
|
const value: string;
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'omt:*' {
|
||||||
|
const value: string;
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
17399
package-lock.json
generated
99
package.json
@@ -1,77 +1,46 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "1.12.0",
|
"version": "2.0.0",
|
||||||
"license": "apache-2.0",
|
"license": "apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --host 0.0.0.0 --hot",
|
"build": "rollup -c && node lib/move-output.js",
|
||||||
"build": "webpack -p",
|
"dev": "rollup -cw & npm run serve",
|
||||||
"lint": "tslint -c tslint.json -p tsconfig.json -t verbose",
|
"serve": "serve --config server.json .tmp/build/static"
|
||||||
"lintfix": "tslint -c tslint.json -p tsconfig.json -t verbose --fix 'src/**/*.{ts,tsx,js,jsx}'",
|
},
|
||||||
"sizereport": "sizereport --config"
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^15.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||||
|
"@surma/rollup-plugin-off-main-thread": "^1.4.1",
|
||||||
|
"@types/node": "^14.10.1",
|
||||||
|
"comlink": "^4.3.0",
|
||||||
|
"cssnano": "^4.1.10",
|
||||||
|
"del": "^5.1.0",
|
||||||
|
"husky": "^4.3.0",
|
||||||
|
"lint-staged": "^10.3.0",
|
||||||
|
"lodash.camelcase": "^4.3.0",
|
||||||
|
"postcss": "^7.0.32",
|
||||||
|
"postcss-import": "^12.0.1",
|
||||||
|
"postcss-modules": "^3.2.2",
|
||||||
|
"postcss-nested": "^4.2.3",
|
||||||
|
"postcss-simple-vars": "^5.0.2",
|
||||||
|
"postcss-url": "^8.0.0",
|
||||||
|
"preact": "^10.4.8",
|
||||||
|
"preact-render-to-string": "^5.1.10",
|
||||||
|
"prettier": "^2.1.1",
|
||||||
|
"rollup": "^2.26.11",
|
||||||
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"serve": "^11.3.2",
|
||||||
|
"typescript": "^4.0.2"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "npm run lint"
|
"pre-commit": "lint-staged"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"lint-staged": {
|
||||||
"@types/node": "10.14.15",
|
"*.{js,css,json,md,ts,tsx}": [
|
||||||
"@types/pretty-bytes": "5.1.0",
|
"prettier --write"
|
||||||
"@types/webassembly-js-api": "0.0.3",
|
]
|
||||||
"@webcomponents/custom-elements": "1.2.4",
|
|
||||||
"@webpack-cli/serve": "0.1.8",
|
|
||||||
"assets-webpack-plugin": "3.9.10",
|
|
||||||
"chalk": "2.4.2",
|
|
||||||
"chokidar": "3.0.2",
|
|
||||||
"classnames": "2.2.6",
|
|
||||||
"clean-webpack-plugin": "1.0.1",
|
|
||||||
"comlink": "3.1.1",
|
|
||||||
"copy-webpack-plugin": "5.0.4",
|
|
||||||
"critters-webpack-plugin": "2.4.0",
|
|
||||||
"css-loader": "1.0.1",
|
|
||||||
"ejs": "2.6.2",
|
|
||||||
"escape-string-regexp": "2.0.0",
|
|
||||||
"exports-loader": "0.7.0",
|
|
||||||
"file-drop-element": "0.2.0",
|
|
||||||
"file-loader": "4.2.0",
|
|
||||||
"gzip-size": "5.1.1",
|
|
||||||
"html-webpack-plugin": "3.2.0",
|
|
||||||
"husky": "3.0.4",
|
|
||||||
"idb-keyval": "3.2.0",
|
|
||||||
"linkstate": "1.1.1",
|
|
||||||
"loader-utils": "1.2.3",
|
|
||||||
"mini-css-extract-plugin": "0.8.0",
|
|
||||||
"minimatch": "3.0.4",
|
|
||||||
"node-fetch": "2.6.0",
|
|
||||||
"node-sass": "4.13.0",
|
|
||||||
"normalize-path": "^3.0.0",
|
|
||||||
"optimize-css-assets-webpack-plugin": "5.0.1",
|
|
||||||
"pointer-tracker": "2.0.3",
|
|
||||||
"preact": "8.4.2",
|
|
||||||
"prerender-loader": "1.3.0",
|
|
||||||
"pretty-bytes": "5.3.0",
|
|
||||||
"progress-bar-webpack-plugin": "1.12.1",
|
|
||||||
"raw-loader": "3.1.0",
|
|
||||||
"readdirp": "3.1.2",
|
|
||||||
"sass-loader": "7.3.1",
|
|
||||||
"script-ext-html-webpack-plugin": "2.1.4",
|
|
||||||
"source-map-loader": "0.2.4",
|
|
||||||
"style-loader": "1.0.0",
|
|
||||||
"terser-webpack-plugin": "1.4.1",
|
|
||||||
"travis-size-report": "1.1.0",
|
|
||||||
"ts-loader": "6.0.3",
|
|
||||||
"tslint": "5.19.0",
|
|
||||||
"tslint-config-airbnb": "5.11.1",
|
|
||||||
"tslint-config-semistandard": "8.0.1",
|
|
||||||
"tslint-react": "4.0.0",
|
|
||||||
"typed-css-modules": "0.4.2",
|
|
||||||
"typescript": "3.5.3",
|
|
||||||
"url-loader": "2.1.0",
|
|
||||||
"webpack": "4.39.3",
|
|
||||||
"webpack-bundle-analyzer": "3.4.1",
|
|
||||||
"webpack-cli": "3.3.4",
|
|
||||||
"webpack-dev-server": "3.8.0",
|
|
||||||
"worker-plugin": "3.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
106
rollup.config.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* 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 * as path from 'path';
|
||||||
|
import { promises as fsp } from 'fs';
|
||||||
|
import del from 'del';
|
||||||
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import OMT from '@surma/rollup-plugin-off-main-thread';
|
||||||
|
|
||||||
|
import simpleTS from './lib/simple-ts';
|
||||||
|
import clientBundlePlugin from './lib/client-bundle-plugin';
|
||||||
|
import nodeExternalPlugin from './lib/node-external-plugin';
|
||||||
|
import cssPlugin from './lib/css-plugin';
|
||||||
|
import assetPlugin from './lib/asset-plugin';
|
||||||
|
import resolveDirsPlugin from './lib/resolve-dirs-plugin';
|
||||||
|
import runScript from './lib/run-script';
|
||||||
|
import emitFiles from './lib/emit-files-plugin';
|
||||||
|
import imageWorkerPlugin from './lib/image-worker-plugin';
|
||||||
|
|
||||||
|
function resolveFileUrl({ fileName }) {
|
||||||
|
return JSON.stringify(fileName.replace(/^static\//, '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// With AMD output, Rollup always uses document.baseURI, which breaks in workers.
|
||||||
|
// This fixes it:
|
||||||
|
function resolveImportMeta(property, { chunkId }) {
|
||||||
|
if (property !== 'url') return;
|
||||||
|
return `new URL(${resolveFileUrl({ fileName: chunkId })}, location).href`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ({ watch }) {
|
||||||
|
const omtLoaderPromise = fsp.readFile(
|
||||||
|
path.join(__dirname, 'lib', 'omt.ejs'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
await del('.tmp/build');
|
||||||
|
|
||||||
|
const tsPluginInstance = simpleTS('.', {
|
||||||
|
watch,
|
||||||
|
});
|
||||||
|
const commonPlugins = () => [
|
||||||
|
tsPluginInstance,
|
||||||
|
resolveDirsPlugin([
|
||||||
|
'src/static-build',
|
||||||
|
'src/client',
|
||||||
|
'src/image-worker',
|
||||||
|
'src/worker-main-shared',
|
||||||
|
'codecs',
|
||||||
|
]),
|
||||||
|
assetPlugin(),
|
||||||
|
cssPlugin(resolveFileUrl),
|
||||||
|
];
|
||||||
|
const dir = '.tmp/build';
|
||||||
|
const staticPath = 'static/c/[name]-[hash][extname]';
|
||||||
|
|
||||||
|
return {
|
||||||
|
input: 'src/static-build/index.tsx',
|
||||||
|
output: {
|
||||||
|
dir,
|
||||||
|
format: 'cjs',
|
||||||
|
assetFileNames: staticPath,
|
||||||
|
exports: 'named',
|
||||||
|
},
|
||||||
|
// Don't watch the ts files. Instead we watch the output from the ts compiler.
|
||||||
|
watch: { clearScreen: false, exclude: ['**/*.ts', '**/*.tsx'] },
|
||||||
|
preserveModules: true,
|
||||||
|
plugins: [
|
||||||
|
{ resolveFileUrl, resolveImportMeta },
|
||||||
|
clientBundlePlugin(
|
||||||
|
{
|
||||||
|
plugins: [
|
||||||
|
{ resolveFileUrl, resolveImportMeta },
|
||||||
|
OMT({ loader: await omtLoaderPromise }),
|
||||||
|
...commonPlugins(),
|
||||||
|
commonjs(),
|
||||||
|
resolve(),
|
||||||
|
terser({ module: true }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir,
|
||||||
|
format: 'amd',
|
||||||
|
chunkFileNames: staticPath.replace('[extname]', '.js'),
|
||||||
|
entryFileNames: staticPath.replace('[extname]', '.js'),
|
||||||
|
},
|
||||||
|
resolveFileUrl,
|
||||||
|
),
|
||||||
|
...commonPlugins(),
|
||||||
|
emitFiles({ include: '**/*', root: path.join(__dirname, 'src', 'copy') }),
|
||||||
|
nodeExternalPlugin(),
|
||||||
|
imageWorkerPlugin(),
|
||||||
|
runScript(dir + '/index.js'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
13
serve.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source": "**/*",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Cache-Control",
|
||||||
|
"value": "no-cache"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
const escapeRE = require("escape-string-regexp");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
repo: "GoogleChromeLabs/squoosh",
|
|
||||||
path: "build/**/!(*.map)",
|
|
||||||
branch: "dev",
|
|
||||||
findRenamed(path, newPaths) {
|
|
||||||
const nameParts = /^(.+\.)[a-f0-9]+(\..+)$/.exec(path);
|
|
||||||
if (!nameParts) return;
|
|
||||||
|
|
||||||
const matchRe = new RegExp(`^${escapeRE(nameParts[1])}[a-f0-9]+${escapeRE(nameParts[2])}$`);
|
|
||||||
return newPaths.find(newPath => matchRe.test(newPath));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
59
src/client/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 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 { wrap } from 'comlink';
|
||||||
|
import workerURL from 'omt:image-worker';
|
||||||
|
import imgURL from 'url:./tmp.png';
|
||||||
|
|
||||||
|
import type { ProcessorWorkerApi } from 'image-worker/index';
|
||||||
|
const worker = new Worker(workerURL);
|
||||||
|
const api = wrap<ProcessorWorkerApi>(worker);
|
||||||
|
|
||||||
|
async function demo() {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = imgURL;
|
||||||
|
await img.decode();
|
||||||
|
// Make canvas same size as image
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const data = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const result = await api.mozjpegEncode(data, {
|
||||||
|
quality: 75,
|
||||||
|
baseline: false,
|
||||||
|
arithmetic: false,
|
||||||
|
progressive: true,
|
||||||
|
optimize_coding: true,
|
||||||
|
smoothing: 0,
|
||||||
|
color_space: 3,
|
||||||
|
quant_table: 3,
|
||||||
|
trellis_multipass: false,
|
||||||
|
trellis_opt_zero: false,
|
||||||
|
trellis_opt_table: false,
|
||||||
|
trellis_loops: 1,
|
||||||
|
auto_subsample: true,
|
||||||
|
chroma_subsample: 2,
|
||||||
|
separate_chroma_quality: false,
|
||||||
|
chroma_quality: 75,
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const resultUrl = URL.createObjectURL(new Blob([result]));
|
||||||
|
const img = new Image();
|
||||||
|
img.src = resultUrl;
|
||||||
|
document.body.append(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demo();
|
||||||
13
src/client/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
BIN
src/client/tmp.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
8
src/client/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../generic-tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["esnext", "dom", "dom.iterable"],
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"references": [{ "path": "../image-worker" }]
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import imagequant, { QuantizerModule } from '../../../codecs/imagequant/imagequant';
|
|
||||||
import wasmUrl from '../../../codecs/imagequant/imagequant.wasm';
|
|
||||||
import { QuantizeOptions } from './processor-meta';
|
|
||||||
import { initEmscriptenModule } from '../util';
|
|
||||||
|
|
||||||
let emscriptenModule: Promise<QuantizerModule>;
|
|
||||||
|
|
||||||
export async function process(data: ImageData, opts: QuantizeOptions): Promise<ImageData> {
|
|
||||||
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(imagequant, wasmUrl);
|
|
||||||
|
|
||||||
const module = await emscriptenModule;
|
|
||||||
|
|
||||||
const result = opts.zx ?
|
|
||||||
module.zx_quantize(data.data, data.width, data.height, opts.dither)
|
|
||||||
:
|
|
||||||
module.quantize(data.data, data.width, data.height, opts.maxNumColors, opts.dither);
|
|
||||||
|
|
||||||
return new ImageData(result, data.width, data.height);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { WorkerResizeOptions } from './processor-meta';
|
|
||||||
import { getContainOffsets } from './util';
|
|
||||||
import { resize as codecResize } from '../../../codecs/resize/pkg';
|
|
||||||
|
|
||||||
function crop(data: ImageData, sx: number, sy: number, sw: number, sh: number): ImageData {
|
|
||||||
const inputPixels = new Uint32Array(data.data.buffer);
|
|
||||||
|
|
||||||
// Copy within the same buffer for speed and memory efficiency.
|
|
||||||
for (let y = 0; y < sh; y += 1) {
|
|
||||||
const start = ((y + sy) * data.width) + sx;
|
|
||||||
inputPixels.copyWithin(y * sw, start, start + sw);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ImageData(
|
|
||||||
new Uint8ClampedArray(inputPixels.buffer.slice(0, sw * sh * 4)),
|
|
||||||
sw, sh,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Resize methods by index */
|
|
||||||
const resizeMethods: WorkerResizeOptions['method'][] = [
|
|
||||||
'triangle', 'catrom', 'mitchell', 'lanczos3',
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function resize(data: ImageData, opts: WorkerResizeOptions): Promise<ImageData> {
|
|
||||||
let input = data;
|
|
||||||
|
|
||||||
if (opts.fitMethod === 'contain') {
|
|
||||||
const { sx, sy, sw, sh } = getContainOffsets(data.width, data.height, opts.width, opts.height);
|
|
||||||
input = crop(input, Math.round(sx), Math.round(sy), Math.round(sw), Math.round(sh));
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = codecResize(
|
|
||||||
new Uint8Array(input.data.buffer), input.width, input.height, opts.width, opts.height,
|
|
||||||
resizeMethods.indexOf(opts.method), opts.premultiply, opts.linearRGB,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new ImageData(new Uint8ClampedArray(result.buffer), opts.width, opts.height);
|
|
||||||
}
|
|
||||||
5
src/copy/_headers
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/*
|
||||||
|
Cache-Control: no-cache
|
||||||
|
|
||||||
|
/c/*
|
||||||
|
Cache-Control: max-age=31536000
|
||||||
13
src/image-worker/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
56
src/image-worker/mozjpegEncode/index.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* 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 mozjpeg_enc, { MozJPEGModule } from 'codecs/mozjpeg_enc/mozjpeg_enc';
|
||||||
|
import wasmUrl from 'url:codecs/mozjpeg_enc/mozjpeg_enc.wasm';
|
||||||
|
import { initEmscriptenModule } from '../util';
|
||||||
|
|
||||||
|
export const enum MozJpegColorSpace {
|
||||||
|
GRAYSCALE = 1,
|
||||||
|
RGB,
|
||||||
|
YCbCr,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EncodeOptions {
|
||||||
|
quality: number;
|
||||||
|
baseline: boolean;
|
||||||
|
arithmetic: boolean;
|
||||||
|
progressive: boolean;
|
||||||
|
optimize_coding: boolean;
|
||||||
|
smoothing: number;
|
||||||
|
color_space: MozJpegColorSpace;
|
||||||
|
quant_table: number;
|
||||||
|
trellis_multipass: boolean;
|
||||||
|
trellis_opt_zero: boolean;
|
||||||
|
trellis_opt_table: boolean;
|
||||||
|
trellis_loops: number;
|
||||||
|
auto_subsample: boolean;
|
||||||
|
chroma_subsample: number;
|
||||||
|
separate_chroma_quality: boolean;
|
||||||
|
chroma_quality: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let emscriptenModule: Promise<MozJPEGModule>;
|
||||||
|
|
||||||
|
export default async function encode(
|
||||||
|
data: ImageData,
|
||||||
|
options: EncodeOptions,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
if (!emscriptenModule) {
|
||||||
|
emscriptenModule = initEmscriptenModule(mozjpeg_enc, wasmUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = await emscriptenModule;
|
||||||
|
const resultView = module.encode(data.data, data.width, data.height, options);
|
||||||
|
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||||
|
return resultView.buffer as ArrayBuffer;
|
||||||
|
}
|
||||||
52
src/image-worker/quantize/index.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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 imagequant, { QuantizerModule } from 'codecs/imagequant/imagequant';
|
||||||
|
import wasmUrl from 'url:codecs/imagequant/imagequant.wasm';
|
||||||
|
import { initEmscriptenModule } from '../util';
|
||||||
|
|
||||||
|
export interface QuantizeOptions {
|
||||||
|
zx: number;
|
||||||
|
maxNumColors: number;
|
||||||
|
dither: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOptions: QuantizeOptions = {
|
||||||
|
zx: 0,
|
||||||
|
maxNumColors: 256,
|
||||||
|
dither: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let emscriptenModule: Promise<QuantizerModule>;
|
||||||
|
|
||||||
|
export default async function process(
|
||||||
|
data: ImageData,
|
||||||
|
opts: QuantizeOptions,
|
||||||
|
): Promise<ImageData> {
|
||||||
|
if (!emscriptenModule) {
|
||||||
|
emscriptenModule = initEmscriptenModule(imagequant, wasmUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = await emscriptenModule;
|
||||||
|
|
||||||
|
const result = opts.zx
|
||||||
|
? module.zx_quantize(data.data, data.width, data.height, opts.dither)
|
||||||
|
: module.quantize(
|
||||||
|
data.data,
|
||||||
|
data.width,
|
||||||
|
data.height,
|
||||||
|
opts.maxNumColors,
|
||||||
|
opts.dither,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ImageData(result, data.width, data.height);
|
||||||
|
}
|
||||||
7
src/image-worker/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../generic-tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["webworker", "esnext"]
|
||||||
|
},
|
||||||
|
"references": []
|
||||||
|
}
|
||||||
39
src/image-worker/util.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export function initEmscriptenModule<T extends EmscriptenWasm.Module>(
|
||||||
|
moduleFactory: EmscriptenWasm.ModuleFactory<T>,
|
||||||
|
wasmUrl: string,
|
||||||
|
): Promise<T> {
|
||||||
|
return moduleFactory({
|
||||||
|
// Just to be safe, don't automatically invoke any wasm functions
|
||||||
|
noInitialRun: true,
|
||||||
|
locateFile: () => wasmUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClampOpts {
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clamp(
|
||||||
|
num: number,
|
||||||
|
{ min = Number.MIN_VALUE, max = Number.MAX_VALUE }: ClampOpts,
|
||||||
|
): number {
|
||||||
|
return Math.min(Math.max(num, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function timed<T>(name: string, func: () => Promise<T>) {
|
||||||
|
console.time(name);
|
||||||
|
return func().finally(() => console.timeEnd(name));
|
||||||
|
}
|
||||||
26
src/image-worker/webpDecode/index.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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 webpDecoder, { WebPModule } from 'codecs/webp/dec/webp_dec';
|
||||||
|
import wasmUrl from 'url:codecs/webp/dec/webp_dec.wasm';
|
||||||
|
import { initEmscriptenModule } from '../util';
|
||||||
|
|
||||||
|
let emscriptenModule: Promise<WebPModule>;
|
||||||
|
|
||||||
|
export default async function decode(data: ArrayBuffer): Promise<ImageData> {
|
||||||
|
if (!emscriptenModule) {
|
||||||
|
emscriptenModule = initEmscriptenModule(webpDecoder, wasmUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = await emscriptenModule;
|
||||||
|
return module.decode(data);
|
||||||
|
}
|
||||||
61
src/image-worker/webpEncode/index.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* 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 webpEncoder, { WebPModule } from 'codecs/webp/enc/webp_enc';
|
||||||
|
import wasmUrl from 'url:codecs/webp/enc/webp_enc.wasm';
|
||||||
|
import { initEmscriptenModule } from '../util';
|
||||||
|
|
||||||
|
export interface EncodeOptions {
|
||||||
|
quality: number;
|
||||||
|
target_size: number;
|
||||||
|
target_PSNR: number;
|
||||||
|
method: number;
|
||||||
|
sns_strength: number;
|
||||||
|
filter_strength: number;
|
||||||
|
filter_sharpness: number;
|
||||||
|
filter_type: number;
|
||||||
|
partitions: number;
|
||||||
|
segments: number;
|
||||||
|
pass: number;
|
||||||
|
show_compressed: number;
|
||||||
|
preprocessing: number;
|
||||||
|
autofilter: number;
|
||||||
|
partition_limit: number;
|
||||||
|
alpha_compression: number;
|
||||||
|
alpha_filtering: number;
|
||||||
|
alpha_quality: number;
|
||||||
|
lossless: number;
|
||||||
|
exact: number;
|
||||||
|
image_hint: number;
|
||||||
|
emulate_jpeg_size: number;
|
||||||
|
thread_level: number;
|
||||||
|
low_memory: number;
|
||||||
|
near_lossless: number;
|
||||||
|
use_delta_palette: number;
|
||||||
|
use_sharp_yuv: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let emscriptenModule: Promise<WebPModule>;
|
||||||
|
|
||||||
|
export default async function encode(
|
||||||
|
data: ImageData,
|
||||||
|
options: EncodeOptions,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
if (!emscriptenModule) {
|
||||||
|
emscriptenModule = initEmscriptenModule(webpEncoder, wasmUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = await emscriptenModule;
|
||||||
|
const resultView = module.encode(data.data, data.width, data.height, options);
|
||||||
|
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||||
|
return resultView.buffer as ArrayBuffer;
|
||||||
|
}
|
||||||
3
src/static-build/components/base/all.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
html {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
44
src/static-build/components/base/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* 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 { h, FunctionalComponent, RenderableProps } from 'preact';
|
||||||
|
import styles from 'css-bundle:./all.css';
|
||||||
|
import clientBundleURL, { imports } from 'client-bundle:client/index.tsx';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BasePage: FunctionalComponent<Props> = ({
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
}: RenderableProps<Props>) => {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{title ? `${title} - ` : ''}Squoosh</title>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, minimum-scale=1.0"
|
||||||
|
></meta>
|
||||||
|
<link rel="stylesheet" href={styles} />
|
||||||
|
<script src={clientBundleURL} defer />
|
||||||
|
{imports.map((v) => (
|
||||||
|
<link rel="preload" as="script" href={v} />
|
||||||
|
))}
|
||||||
|
</head>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasePage;
|
||||||
25
src/static-build/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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 { h } from 'preact';
|
||||||
|
|
||||||
|
import { renderPage, writeFiles } from './utils';
|
||||||
|
import IndexPage from './pages/index';
|
||||||
|
|
||||||
|
interface Output {
|
||||||
|
[outputPath: string]: string;
|
||||||
|
}
|
||||||
|
const toOutput: Output = {
|
||||||
|
'index.html': renderPage(<IndexPage />),
|
||||||
|
};
|
||||||
|
|
||||||
|
writeFiles(toOutput);
|
||||||
25
src/static-build/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
|
|
||||||
|
declare module 'client-bundle:*' {
|
||||||
|
const url: string;
|
||||||
|
export default url;
|
||||||
|
export const imports: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'css-bundle:*' {
|
||||||
|
const url: string;
|
||||||
|
export default url;
|
||||||
|
export const inline: string;
|
||||||
|
}
|
||||||
21
src/static-build/pages/index/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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 { h, FunctionalComponent } from 'preact';
|
||||||
|
import BasePage from 'static-build/components/base';
|
||||||
|
|
||||||
|
const IndexPage: FunctionalComponent<{}> = () => (
|
||||||
|
<BasePage>
|
||||||
|
<h1>Hi</h1>
|
||||||
|
</BasePage>
|
||||||
|
);
|
||||||
|
export default IndexPage;
|
||||||
8
src/static-build/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../generic-tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"references": []
|
||||||
|
}
|
||||||
46
src/static-build/utils.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 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 fsp } from 'fs';
|
||||||
|
import { join as joinPath } from 'path';
|
||||||
|
|
||||||
|
import render from 'preact-render-to-string';
|
||||||
|
import { VNode } from 'preact';
|
||||||
|
|
||||||
|
export function renderPage(vnode: VNode) {
|
||||||
|
return '<!DOCTYPE html>' + render(vnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OutputMap {
|
||||||
|
[path: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeFiles(toOutput: OutputMap) {
|
||||||
|
Promise.all(
|
||||||
|
Object.entries(toOutput).map(async ([path, content]) => {
|
||||||
|
const pathParts = ['.tmp', 'build', 'static', ...path.split('/')];
|
||||||
|
await fsp.mkdir(joinPath(...pathParts.slice(0, -1)), { recursive: true });
|
||||||
|
const fullPath = joinPath(...pathParts);
|
||||||
|
try {
|
||||||
|
await fsp.writeFile(fullPath, content, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to write ' + fullPath);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -1,7 +1,10 @@
|
|||||||
import { canvasEncodeTest } from '../generic/util';
|
import { canvasEncodeTest } from '../generic/util';
|
||||||
|
|
||||||
export interface EncodeOptions { }
|
export interface EncodeOptions {}
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-bmp';
|
export const type = 'browser-bmp';
|
||||||
export const label = 'Browser BMP';
|
export const label = 'Browser BMP';
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { canvasEncodeTest } from '../generic/util';
|
import { canvasEncodeTest } from '../generic/util';
|
||||||
|
|
||||||
export interface EncodeOptions {}
|
export interface EncodeOptions {}
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-gif';
|
export const type = 'browser-gif';
|
||||||
export const label = 'Browser GIF';
|
export const label = 'Browser GIF';
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { canvasEncodeTest } from '../generic/util';
|
import { canvasEncodeTest } from '../generic/util';
|
||||||
|
|
||||||
export interface EncodeOptions { }
|
export interface EncodeOptions {}
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-jp2';
|
export const type = 'browser-jp2';
|
||||||
export const label = 'Browser JPEG 2000';
|
export const label = 'Browser JPEG 2000';
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
export interface EncodeOptions { quality: number; }
|
export interface EncodeOptions {
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
quality: number;
|
||||||
|
}
|
||||||
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-jpeg';
|
export const type = 'browser-jpeg';
|
||||||
export const label = 'Browser JPEG';
|
export const label = 'Browser JPEG';
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { canvasEncodeTest } from '../generic/util';
|
import { canvasEncodeTest } from '../generic/util';
|
||||||
|
|
||||||
export interface EncodeOptions { }
|
export interface EncodeOptions {}
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-pdf';
|
export const type = 'browser-pdf';
|
||||||
export const label = 'Browser PDF';
|
export const label = 'Browser PDF';
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
export interface EncodeOptions {}
|
export interface EncodeOptions {}
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-png';
|
export const type = 'browser-png';
|
||||||
export const label = 'Browser PNG';
|
export const label = 'Browser PNG';
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { canvasEncodeTest } from '../generic/util';
|
import { canvasEncodeTest } from '../generic/util';
|
||||||
|
|
||||||
export interface EncodeOptions { }
|
export interface EncodeOptions {}
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-tiff';
|
export const type = 'browser-tiff';
|
||||||
export const label = 'Browser TIFF';
|
export const label = 'Browser TIFF';
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import { canvasEncodeTest } from '../generic/util';
|
import { canvasEncodeTest } from '../generic/util';
|
||||||
|
|
||||||
export interface EncodeOptions { quality: number; }
|
export interface EncodeOptions {
|
||||||
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
quality: number;
|
||||||
|
}
|
||||||
|
export interface EncoderState {
|
||||||
|
type: typeof type;
|
||||||
|
options: EncodeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export const type = 'browser-webp';
|
export const type = 'browser-webp';
|
||||||
export const label = 'Browser WebP';
|
export const label = 'Browser WebP';
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { builtinDecode, sniffMimeType, canDecodeImageType } from '../lib/util';
|
import { builtinDecode, sniffMimeType, canDecodeImageType } from '../lib/util';
|
||||||
import Processor from './processor';
|
import Processor from './processor';
|
||||||
|
|
||||||
export async function decodeImage(blob: Blob, processor: Processor): Promise<ImageData> {
|
export async function decodeImage(
|
||||||
|
blob: Blob,
|
||||||
|
processor: Processor,
|
||||||
|
): Promise<ImageData> {
|
||||||
const mimeType = await sniffMimeType(blob);
|
const mimeType = await sniffMimeType(blob);
|
||||||
const canDecode = await canDecodeImageType(mimeType);
|
const canDecode = await canDecodeImageType(mimeType);
|
||||||
|
|
||||||
@@ -17,34 +17,34 @@ export interface EncoderSupportMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type EncoderState =
|
export type EncoderState =
|
||||||
identity.EncoderState |
|
| identity.EncoderState
|
||||||
oxiPNG.EncoderState |
|
| oxiPNG.EncoderState
|
||||||
mozJPEG.EncoderState |
|
| mozJPEG.EncoderState
|
||||||
webP.EncoderState |
|
| webP.EncoderState
|
||||||
avif.EncoderState |
|
| avif.EncoderState
|
||||||
browserPNG.EncoderState |
|
| browserPNG.EncoderState
|
||||||
browserJPEG.EncoderState |
|
| browserJPEG.EncoderState
|
||||||
browserWebP.EncoderState |
|
| browserWebP.EncoderState
|
||||||
browserGIF.EncoderState |
|
| browserGIF.EncoderState
|
||||||
browserTIFF.EncoderState |
|
| browserTIFF.EncoderState
|
||||||
browserJP2.EncoderState |
|
| browserJP2.EncoderState
|
||||||
browserBMP.EncoderState |
|
| browserBMP.EncoderState
|
||||||
browserPDF.EncoderState;
|
| browserPDF.EncoderState;
|
||||||
|
|
||||||
export type EncoderOptions =
|
export type EncoderOptions =
|
||||||
identity.EncodeOptions |
|
| identity.EncodeOptions
|
||||||
oxiPNG.EncodeOptions |
|
| oxiPNG.EncodeOptions
|
||||||
mozJPEG.EncodeOptions |
|
| mozJPEG.EncodeOptions
|
||||||
webP.EncodeOptions |
|
| webP.EncodeOptions
|
||||||
avif.EncodeOptions |
|
| avif.EncodeOptions
|
||||||
browserPNG.EncodeOptions |
|
| browserPNG.EncodeOptions
|
||||||
browserJPEG.EncodeOptions |
|
| browserJPEG.EncodeOptions
|
||||||
browserWebP.EncodeOptions |
|
| browserWebP.EncodeOptions
|
||||||
browserGIF.EncodeOptions |
|
| browserGIF.EncodeOptions
|
||||||
browserTIFF.EncodeOptions |
|
| browserTIFF.EncodeOptions
|
||||||
browserJP2.EncodeOptions |
|
| browserJP2.EncodeOptions
|
||||||
browserBMP.EncodeOptions |
|
| browserBMP.EncodeOptions
|
||||||
browserPDF.EncodeOptions;
|
| browserPDF.EncodeOptions;
|
||||||
|
|
||||||
export type EncoderType = keyof typeof encoderMap;
|
export type EncoderType = keyof typeof encoderMap;
|
||||||
|
|
||||||
@@ -72,11 +72,14 @@ export const encoders = Array.from(Object.values(encoderMap));
|
|||||||
export const encodersSupported = Promise.resolve().then(async () => {
|
export const encodersSupported = Promise.resolve().then(async () => {
|
||||||
const encodersSupported: EncoderSupportMap = {};
|
const encodersSupported: EncoderSupportMap = {};
|
||||||
|
|
||||||
await Promise.all(encoders.map(async (encoder) => {
|
await Promise.all(
|
||||||
// If the encoder provides a featureTest, call it, otherwise assume supported.
|
encoders.map(async (encoder) => {
|
||||||
const isSupported = !('featureTest' in encoder) || await encoder.featureTest();
|
// If the encoder provides a featureTest, call it, otherwise assume supported.
|
||||||
encodersSupported[encoder.type] = isSupported;
|
const isSupported =
|
||||||
}));
|
!('featureTest' in encoder) || (await encoder.featureTest());
|
||||||
|
encodersSupported[encoder.type] = isSupported;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return encodersSupported;
|
return encodersSupported;
|
||||||
});
|
});
|
||||||
@@ -8,8 +8,8 @@ interface EncodeOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: EncodeOptions,
|
options: EncodeOptions;
|
||||||
onChange(newOptions: EncodeOptions): void,
|
onChange(newOptions: EncodeOptions): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QualityOptionArg {
|
interface QualityOptionArg {
|
||||||
@@ -19,11 +19,7 @@ interface QualityOptionArg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function qualityOption(opts: QualityOptionArg = {}) {
|
export default function qualityOption(opts: QualityOptionArg = {}) {
|
||||||
const {
|
const { min = 0, max = 100, step = 1 } = opts;
|
||||||
min = 0,
|
|
||||||
max = 100,
|
|
||||||
step = 1,
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
class QualityOptions extends Component<Props, {}> {
|
class QualityOptions extends Component<Props, {}> {
|
||||||
@bind
|
@bind
|
||||||