mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 01:07:18 +00:00
Compare commits
11 Commits
analytics
...
readme-typ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68bb2edb39 | ||
|
|
9c85618aff | ||
|
|
aebeff8b4c | ||
|
|
8d63125b13 | ||
|
|
2ca97ef586 | ||
|
|
a1a00f0bfb | ||
|
|
6870b135b7 | ||
|
|
a0f1379feb | ||
|
|
9b17322478 | ||
|
|
f562bad286 | ||
|
|
6994cc3d15 |
31
README.md
31
README.md
@@ -1,5 +1,32 @@
|
||||
# Squoosh!
|
||||
|
||||
Squoosh will be an image compression web app that allows you to dive into the
|
||||
advanced options provided by various image compressors.
|
||||
Squoosh is an image compression web app that allows you to dive into the advanced options provided
|
||||
by various image compressors.
|
||||
|
||||
# Privacy
|
||||
|
||||
Google Analytics is used to record the following:
|
||||
|
||||
* [Basic visit data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
|
||||
* Before and after image size once an image is downloaded. These values are rounded to the nearest
|
||||
kilobyte.
|
||||
|
||||
Image compression is handled locally; no additional data is sent to the server.
|
||||
|
||||
# Building locally
|
||||
|
||||
Clone the repo, and:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
You'll get an error on first build because of [a stupid bug we haven't fixed
|
||||
yet](https://github.com/GoogleChromeLabs/squoosh/issues/251).
|
||||
|
||||
You can run the development server with:
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
18
_headers.ejs
Normal file
18
_headers.ejs
Normal file
@@ -0,0 +1,18 @@
|
||||
# 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
|
||||
47
config/asset-template-plugin.js
Normal file
47
config/asset-template-plugin.js
Normal file
@@ -0,0 +1,47 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
29
package-lock.json
generated
29
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "squoosh",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -578,6 +578,27 @@
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
|
||||
"dev": true
|
||||
},
|
||||
"assets-webpack-plugin": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/assets-webpack-plugin/-/assets-webpack-plugin-3.9.7.tgz",
|
||||
"integrity": "sha512-yxo4MlSb++B88qQFE27Wf56ykGaDHZeKcSbrstSFOOwOxv33gWXtM49+yfYPSErlXPAMT5lVy3YPIhWlIFjYQw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"escape-string-regexp": "^1.0.3",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"mkdirp": "^0.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
|
||||
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"assign-symbols": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
||||
@@ -7923,6 +7944,12 @@
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
|
||||
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "squoosh",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.2",
|
||||
"license": "apache-2.0",
|
||||
"scripts": {
|
||||
"start": "webpack serve --host 0.0.0.0 --hot",
|
||||
@@ -19,6 +19,7 @@
|
||||
"@types/pretty-bytes": "^5.1.0",
|
||||
"@types/webassembly-js-api": "0.0.1",
|
||||
"@webcomponents/custom-elements": "^1.2.1",
|
||||
"assets-webpack-plugin": "^3.9.7",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-jsx-pragmatic": "^1.0.2",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
@@ -36,6 +37,7 @@
|
||||
"copy-webpack-plugin": "^4.5.3",
|
||||
"critters-webpack-plugin": "^2.0.1",
|
||||
"css-loader": "^0.28.11",
|
||||
"ejs": "^2.6.1",
|
||||
"exports-loader": "^0.7.0",
|
||||
"file-drop-element": "^0.0.9",
|
||||
"file-loader": "^1.1.11",
|
||||
|
||||
@@ -48,6 +48,15 @@ export default class Output extends Component<Props, State> {
|
||||
const leftDraw = this.leftDrawable();
|
||||
const rightDraw = this.rightDrawable();
|
||||
|
||||
// Reset the pinch zoom, which may have an position set from the previous view, after pressing
|
||||
// the back button.
|
||||
this.pinchZoomLeft!.setTransform({
|
||||
allowChangeEvent: true,
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
});
|
||||
|
||||
if (this.canvasLeft && leftDraw) {
|
||||
drawDataToCanvas(this.canvasLeft, leftDraw);
|
||||
}
|
||||
|
||||
@@ -129,6 +129,11 @@ export default class Intro extends Component<Props, State> {
|
||||
<ul class={style.relatedLinks}>
|
||||
<li><a href="https://github.com/GoogleChromeLabs/squoosh/">View the code</a></li>
|
||||
<li><a href="https://github.com/GoogleChromeLabs/squoosh/issues">Report a bug</a></li>
|
||||
<li>
|
||||
<a href="https://github.com/GoogleChromeLabs/squoosh/blob/master/README.md#privacy">
|
||||
Privacy
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -60,11 +60,16 @@ export default class Results extends Component<Props, State> {
|
||||
|
||||
@bind
|
||||
onDownload() {
|
||||
// GA can’t do floats. So we round to ints. We're deliberately rounding to nearest kilobyte to
|
||||
// avoid cases where exact image sizes leak something interesting about the user.
|
||||
const before = Math.round(this.props.source!.file.size / 1024);
|
||||
const after = Math.round(this.props.imageFile!.size / 1024);
|
||||
const change = Math.round(after / before * 1000);
|
||||
|
||||
ga('send', 'event', 'compression', 'download', {
|
||||
// GA can’t do floats. So we round to ints.
|
||||
metric1: Math.floor(this.props.source!.file.size),
|
||||
metric2: Math.floor(this.props.imageFile!.size),
|
||||
metric3: Math.floor(this.props.imageFile!.size / this.props.source!.file.size * 1000),
|
||||
metric1: before,
|
||||
metric2: after,
|
||||
metric3: change,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
18
src/index.ts
18
src/index.ts
@@ -13,11 +13,13 @@ if (!('customElements' in self)) {
|
||||
init();
|
||||
}
|
||||
|
||||
window.ga = window.ga || ((...args) => (ga.q = ga.q || []).push(args));
|
||||
ga('create', 'UA-128752250-1', 'auto');
|
||||
ga('set', 'transport', 'beacon');
|
||||
ga('send', 'pageview');
|
||||
// Load the GA script
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://www.google-analytics.com/analytics.js';
|
||||
document.head!.appendChild(s);
|
||||
if (typeof PRERENDER === 'undefined') {
|
||||
window.ga = window.ga || ((...args) => (ga.q = ga.q || []).push(args));
|
||||
ga('create', 'UA-128752250-1', 'auto');
|
||||
ga('set', 'transport', 'beacon');
|
||||
ga('send', 'pageview');
|
||||
// Load the GA script
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://www.google-analytics.com/analytics.js';
|
||||
document.head!.appendChild(s);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ async function updateReady(reg: ServiceWorkerRegistration): Promise<void> {
|
||||
|
||||
/** Set up the service worker and monitor changes */
|
||||
export async function offliner(showSnack: SnackBarElement['showSnackbar']) {
|
||||
// This needs to be a typeof because Webpack.
|
||||
if (typeof PRERENDER === 'boolean') return;
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
navigator.serviceWorker.register('../sw');
|
||||
}
|
||||
|
||||
@@ -56,8 +56,6 @@ export async function cacheBasics(cacheName: string, buildAssets: string[]) {
|
||||
const toCache = ['/', '/assets/favicon.ico'];
|
||||
|
||||
const prefixesToCache = [
|
||||
// First interaction JS & CSS:
|
||||
'first-interaction.',
|
||||
// Main app JS & CSS:
|
||||
'main-app.',
|
||||
// Service worker handler:
|
||||
|
||||
@@ -14,6 +14,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
|
||||
const WorkerPlugin = require('worker-plugin');
|
||||
const AutoSWPlugin = require('./config/auto-sw-plugin');
|
||||
const CrittersPlugin = require('critters-webpack-plugin');
|
||||
const AssetTemplatePlugin = require('./config/asset-template-plugin');
|
||||
|
||||
function readJson (filename) {
|
||||
return JSON.parse(fs.readFileSync(filename));
|
||||
@@ -225,7 +226,7 @@ module.exports = function (_, env) {
|
||||
// For now we're not doing SSR.
|
||||
new HtmlPlugin({
|
||||
filename: path.join(__dirname, 'build/index.html'),
|
||||
template: '!!prerender-loader?string!src/index.html',
|
||||
template: isProd ? '!!prerender-loader?string!src/index.html' : 'src/index.html',
|
||||
minify: isProd && {
|
||||
collapseWhitespace: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
@@ -238,8 +239,11 @@ module.exports = function (_, env) {
|
||||
compile: true
|
||||
}),
|
||||
|
||||
new AutoSWPlugin({
|
||||
version: VERSION
|
||||
new AutoSWPlugin({ version: VERSION }),
|
||||
|
||||
isProd && new AssetTemplatePlugin({
|
||||
template: path.join(__dirname, '_headers.ejs'),
|
||||
filename: '_headers',
|
||||
}),
|
||||
|
||||
new ScriptExtHtmlPlugin({
|
||||
|
||||
Reference in New Issue
Block a user