local icons
Summary: Currently icons were always fetched remotely. We used a service worker to prefetch and cache some icons, that were critical to the UI. In this diff, we are bundling icons at build time, with the app. In utils/icons.js we still specfify the list of icons which should be bundled. These are downloaded as part of the build step and bundled with the app. We are downloading the icons in 1x and 2x (the two most common pixel densities). Reviewed By: jknoxville Differential Revision: D16620764 fbshipit-source-id: 965a7793ad1f08aebb292606add00218429cdaf4
This commit is contained in:
committed by
Facebook Github Bot
parent
1717fba410
commit
16e913a819
@@ -17,6 +17,8 @@ const {
|
|||||||
getVersionNumber,
|
getVersionNumber,
|
||||||
genMercurialRevision,
|
genMercurialRevision,
|
||||||
} = require('./build-utils.js');
|
} = require('./build-utils.js');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const {ICONS, getIconURL} = require('../src/utils/icons.js');
|
||||||
|
|
||||||
function generateManifest(versionNumber) {
|
function generateManifest(versionNumber) {
|
||||||
const filePath = path.join(__dirname, '..', 'dist');
|
const filePath = path.join(__dirname, '..', 'dist');
|
||||||
@@ -112,11 +114,51 @@ function copyStaticFolder(buildFolder) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadIcons(buildFolder) {
|
||||||
|
const iconURLs = Object.entries(ICONS).reduce((acc, [name, sizes]) => {
|
||||||
|
acc.push(
|
||||||
|
// get icons in @1x and @2x
|
||||||
|
...sizes.map(size => ({name, size, density: 1})),
|
||||||
|
...sizes.map(size => ({name, size, density: 2})),
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
iconURLs.map(({name, size, density}) =>
|
||||||
|
fetch(getIconURL(name, size, density))
|
||||||
|
.then(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(`Could not download the icon: ${name}`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
res =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const fileStream = fs.createWriteStream(
|
||||||
|
path.join(
|
||||||
|
buildFolder,
|
||||||
|
'icons',
|
||||||
|
`${name}-${size}@${density}x.png`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
res.body.pipe(fileStream);
|
||||||
|
res.body.on('error', reject);
|
||||||
|
fileStream.on('finish', resolve);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch(console.error),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const dir = await buildFolder();
|
const dir = await buildFolder();
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('Created build directory', dir);
|
console.log('Created build directory', dir);
|
||||||
copyStaticFolder(dir);
|
copyStaticFolder(dir);
|
||||||
|
await downloadIcons(dir);
|
||||||
await compileDefaultPlugins(path.join(dir, 'defaultPlugins'));
|
await compileDefaultPlugins(path.join(dir, 'defaultPlugins'));
|
||||||
await compile(dir, path.join(__dirname, '..', 'src', 'init.js'));
|
await compile(dir, path.join(__dirname, '..', 'src', 'init.js'));
|
||||||
const versionNumber = getVersionNumber();
|
const versionNumber = getVersionNumber();
|
||||||
|
|||||||
18
src/init.js
18
src/init.js
@@ -8,7 +8,6 @@
|
|||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import {ContextMenuProvider} from 'flipper';
|
import {ContextMenuProvider} from 'flipper';
|
||||||
import {precachedIcons} from './utils/icons.js';
|
|
||||||
import GK from './fb-stubs/GK.js';
|
import GK from './fb-stubs/GK.js';
|
||||||
import {init as initLogger} from './fb-stubs/Logger';
|
import {init as initLogger} from './fb-stubs/Logger';
|
||||||
import App from './App.js';
|
import App from './App.js';
|
||||||
@@ -49,23 +48,6 @@ const AppFrame = () => (
|
|||||||
function init() {
|
function init() {
|
||||||
// $FlowFixMe: this element exists!
|
// $FlowFixMe: this element exists!
|
||||||
ReactDOM.render(<AppFrame />, document.getElementById('root'));
|
ReactDOM.render(<AppFrame />, document.getElementById('root'));
|
||||||
// $FlowFixMe: service workers exist!
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register(
|
|
||||||
process.env.NODE_ENV === 'production'
|
|
||||||
? path.join(__dirname, 'serviceWorker.js')
|
|
||||||
: './serviceWorker.js',
|
|
||||||
)
|
|
||||||
.then((r: ServiceWorkerRegistration) => {
|
|
||||||
const client = r.installing || r.active;
|
|
||||||
if (client != null) {
|
|
||||||
client.postMessage({precachedIcons});
|
|
||||||
} else {
|
|
||||||
console.error('Service worker registration failed: ', r);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
|
|
||||||
initLauncherHooks(config(), store);
|
initLauncherHooks(config(), store);
|
||||||
const sessionId = store.getState().application.sessionId;
|
const sessionId = store.getState().application.sessionId;
|
||||||
initCrashReporter(sessionId || '');
|
initCrashReporter(sessionId || '');
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from '../styled/index.js';
|
import styled from '../styled/index.js';
|
||||||
const PropTypes = require('prop-types');
|
import PropTypes from 'prop-types';
|
||||||
import {getIconUrl} from '../../utils/icons.js';
|
import {getIconURL} from '../../utils/icons.js';
|
||||||
|
|
||||||
const ColoredIconBlack = styled('img')(({size}) => ({
|
const ColoredIconBlack = styled('img')(({size}) => ({
|
||||||
height: size,
|
height: size,
|
||||||
@@ -91,7 +91,11 @@ export default class Glyph extends React.Component<{
|
|||||||
className={className}
|
className={className}
|
||||||
color={color}
|
color={color}
|
||||||
size={size}
|
size={size}
|
||||||
src={getIconUrl(name, size, variant)}
|
src={getIconURL(
|
||||||
|
variant === 'outline' ? `${name}-outline` : name,
|
||||||
|
size,
|
||||||
|
typeof window !== 'undefined' ? window.devicePixelRatio : 1,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,106 +5,49 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// list of icons that are prefetched in the service worker when launching the app
|
/* This file needs to be plain JS to be imported by scripts/build-release.js */
|
||||||
export const precachedIcons: Array<string> = [
|
/* eslint-disable import/no-commonjs */
|
||||||
{
|
|
||||||
name: 'arrow-right',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'caution-octagon',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'caution-triangle',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'info-circle',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'magic-wand',
|
|
||||||
size: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'magnifying-glass',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minus-circle',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mobile',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'box',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'desktop',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bug',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'posts',
|
|
||||||
size: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rocket',
|
|
||||||
size: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tools',
|
|
||||||
size: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'triangle-down',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'triangle-right',
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'chevron-right',
|
|
||||||
size: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'chevron-down',
|
|
||||||
size: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'star',
|
|
||||||
size: 16,
|
|
||||||
variant: 'filled',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'star',
|
|
||||||
size: 16,
|
|
||||||
variant: 'outline',
|
|
||||||
},
|
|
||||||
].map(icon =>
|
|
||||||
getIconUrl(icon.name, icon.size || undefined, icon.variant || undefined),
|
|
||||||
);
|
|
||||||
|
|
||||||
export function getIconUrl(
|
const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32];
|
||||||
name: string,
|
const DENSITIES = [1, 1.5, 2, 3, 4];
|
||||||
size?: number = 16,
|
const fs = require('fs');
|
||||||
variant?: 'filled' | 'outline' = 'filled',
|
const path = require('path');
|
||||||
): string {
|
const {remote} = require('electron');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ICONS: {
|
||||||
|
'arrow-right': [12],
|
||||||
|
'caution-octagon': [16],
|
||||||
|
'caution-triangle': [16],
|
||||||
|
'info-circle': [16],
|
||||||
|
'magic-wand': [20],
|
||||||
|
'magnifying-glass': [20],
|
||||||
|
'minus-circle': [12],
|
||||||
|
mobile: [12],
|
||||||
|
box: [12],
|
||||||
|
desktop: [12],
|
||||||
|
bug: [12],
|
||||||
|
posts: [20],
|
||||||
|
rocket: [20],
|
||||||
|
tools: [20],
|
||||||
|
'triangle-down': [12],
|
||||||
|
'triangle-right': [12],
|
||||||
|
'chevron-right': [8],
|
||||||
|
'chevron-down': [8],
|
||||||
|
star: [16],
|
||||||
|
'star-outline': [16],
|
||||||
|
},
|
||||||
|
|
||||||
|
// $FlowFixMe: not using flow in this file
|
||||||
|
getIconURL(name, size, density) {
|
||||||
if (name.indexOf('/') > -1) {
|
if (name.indexOf('/') > -1) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32];
|
let requestedSize = size;
|
||||||
const SCALE = [1, 1.5, 2, 3, 4];
|
|
||||||
|
|
||||||
let requestedSize: number = size;
|
|
||||||
if (!AVAILABLE_SIZES.includes(size)) {
|
if (!AVAILABLE_SIZES.includes(size)) {
|
||||||
// find the next largest size
|
// find the next largest size
|
||||||
const possibleSize: ?number = AVAILABLE_SIZES.find(size => {
|
const possibleSize = AVAILABLE_SIZES.find(size => {
|
||||||
return size > requestedSize;
|
return size > requestedSize;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,22 +59,34 @@ export function getIconUrl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestedScale: number =
|
if (!DENSITIES.includes(density)) {
|
||||||
typeof window !== 'undefined' ? window.devicePixelRatio : 1;
|
|
||||||
|
|
||||||
if (!SCALE.includes(requestedScale)) {
|
|
||||||
// find the next largest size
|
// find the next largest size
|
||||||
const possibleScale: ?number = SCALE.find(scale => {
|
const possibleDensity = DENSITIES.find(scale => {
|
||||||
return scale > requestedScale;
|
return scale > density;
|
||||||
});
|
});
|
||||||
|
|
||||||
// set to largest size if the real size is larger than what we have
|
// set to largest size if the real size is larger than what we have
|
||||||
if (possibleScale == null) {
|
if (possibleDensity == null) {
|
||||||
requestedScale = Math.max(...SCALE);
|
density = Math.max(...DENSITIES);
|
||||||
} else {
|
} else {
|
||||||
requestedScale = possibleScale;
|
density = possibleDensity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `https://external.xx.fbcdn.net/assets/?name=${name}&variant=${variant}&size=${requestedSize}&set=facebook_icons&density=${requestedScale}x`;
|
let variant = 'filled';
|
||||||
|
if (name.endsWith('-outline')) {
|
||||||
|
name = name.replace('-outline', '');
|
||||||
|
variant = 'outline';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localPath = path.join('icons', `${name}-${size}@${density}x.png`);
|
||||||
|
// resolve icon locally if possible
|
||||||
|
if (
|
||||||
|
remote &&
|
||||||
|
fs.existsSync(path.join(remote.app.getAppPath(), localPath))
|
||||||
|
) {
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
return `https://external.xx.fbcdn.net/assets/?name=${name}&variant=${variant}&size=${requestedSize}&set=facebook_icons&density=${density}x`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2018-present Facebook.
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
const CACHE_NAME = 'v1';
|
|
||||||
|
|
||||||
self.addEventListener('message', e => {
|
|
||||||
if (e.data.precachedIcons) {
|
|
||||||
caches.open(CACHE_NAME).then(cache => cache.addAll(e.data.precachedIcons));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('fetch', function(event) {
|
|
||||||
if (event.request.url.startsWith('https://external.xx.fbcdn.net/assets/')) {
|
|
||||||
event.respondWith(
|
|
||||||
// Cache falling back to the network
|
|
||||||
caches.match(event.request).then(cacheResponse => {
|
|
||||||
return (
|
|
||||||
cacheResponse ||
|
|
||||||
fetch(event.request).then(response => {
|
|
||||||
const clone = response.clone();
|
|
||||||
// write to cache
|
|
||||||
caches
|
|
||||||
.open(CACHE_NAME)
|
|
||||||
.then(cache => cache.put(event.request, clone));
|
|
||||||
return response;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "system",
|
"module": "system",
|
||||||
|
"lib": ["es7", "dom"],
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"outFile": "../../built/local/tsc.js",
|
"outFile": "../../built/local/tsc.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user