From 16e913a8191760ac3a83a76cc8b200d6c4c9a633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=BCchele?= Date: Fri, 2 Aug 2019 08:56:23 -0700 Subject: [PATCH] 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 --- scripts/build-release.js | 42 ++++++++ src/init.js | 18 ---- src/ui/components/Glyph.js | 10 +- src/utils/icons.js | 199 ++++++++++++++----------------------- static/serviceWorker.js | 35 ------- tsconfig.json | 1 + 6 files changed, 127 insertions(+), 178 deletions(-) delete mode 100644 static/serviceWorker.js diff --git a/scripts/build-release.js b/scripts/build-release.js index 426ab9db3..df2047e4f 100755 --- a/scripts/build-release.js +++ b/scripts/build-release.js @@ -17,6 +17,8 @@ const { getVersionNumber, genMercurialRevision, } = require('./build-utils.js'); +const fetch = require('node-fetch'); +const {ICONS, getIconURL} = require('../src/utils/icons.js'); function generateManifest(versionNumber) { 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 () => { const dir = await buildFolder(); // eslint-disable-next-line no-console console.log('Created build directory', dir); copyStaticFolder(dir); + await downloadIcons(dir); await compileDefaultPlugins(path.join(dir, 'defaultPlugins')); await compile(dir, path.join(__dirname, '..', 'src', 'init.js')); const versionNumber = getVersionNumber(); diff --git a/src/init.js b/src/init.js index 1e0b59909..75ccbd140 100644 --- a/src/init.js +++ b/src/init.js @@ -8,7 +8,6 @@ import {Provider} from 'react-redux'; import ReactDOM from 'react-dom'; import {ContextMenuProvider} from 'flipper'; -import {precachedIcons} from './utils/icons.js'; import GK from './fb-stubs/GK.js'; import {init as initLogger} from './fb-stubs/Logger'; import App from './App.js'; @@ -49,23 +48,6 @@ const AppFrame = () => ( function init() { // $FlowFixMe: this element exists! ReactDOM.render(, 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); const sessionId = store.getState().application.sessionId; initCrashReporter(sessionId || ''); diff --git a/src/ui/components/Glyph.js b/src/ui/components/Glyph.js index f64203d3c..461152536 100644 --- a/src/ui/components/Glyph.js +++ b/src/ui/components/Glyph.js @@ -7,8 +7,8 @@ import React from 'react'; import styled from '../styled/index.js'; -const PropTypes = require('prop-types'); -import {getIconUrl} from '../../utils/icons.js'; +import PropTypes from 'prop-types'; +import {getIconURL} from '../../utils/icons.js'; const ColoredIconBlack = styled('img')(({size}) => ({ height: size, @@ -91,7 +91,11 @@ export default class Glyph extends React.Component<{ className={className} color={color} size={size} - src={getIconUrl(name, size, variant)} + src={getIconURL( + variant === 'outline' ? `${name}-outline` : name, + size, + typeof window !== 'undefined' ? window.devicePixelRatio : 1, + )} /> ); } diff --git a/src/utils/icons.js b/src/utils/icons.js index 7e82897dd..34f98763f 100644 --- a/src/utils/icons.js +++ b/src/utils/icons.js @@ -5,133 +5,88 @@ * @format */ -// list of icons that are prefetched in the service worker when launching the app -export const precachedIcons: Array = [ - { - 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), -); +/* This file needs to be plain JS to be imported by scripts/build-release.js */ +/* eslint-disable import/no-commonjs */ -export function getIconUrl( - name: string, - size?: number = 16, - variant?: 'filled' | 'outline' = 'filled', -): string { - if (name.indexOf('/') > -1) { - return name; - } +const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32]; +const DENSITIES = [1, 1.5, 2, 3, 4]; +const fs = require('fs'); +const path = require('path'); +const {remote} = require('electron'); - const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32]; - const SCALE = [1, 1.5, 2, 3, 4]; +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], + }, - let requestedSize: number = size; - if (!AVAILABLE_SIZES.includes(size)) { - // find the next largest size - const possibleSize: ?number = AVAILABLE_SIZES.find(size => { - return size > requestedSize; - }); - - // set to largest size if the real size is larger than what we have - if (possibleSize == null) { - requestedSize = Math.max(...AVAILABLE_SIZES); - } else { - requestedSize = possibleSize; + // $FlowFixMe: not using flow in this file + getIconURL(name, size, density) { + if (name.indexOf('/') > -1) { + return name; } - } - let requestedScale: number = - typeof window !== 'undefined' ? window.devicePixelRatio : 1; + let requestedSize = size; + if (!AVAILABLE_SIZES.includes(size)) { + // find the next largest size + const possibleSize = AVAILABLE_SIZES.find(size => { + return size > requestedSize; + }); - if (!SCALE.includes(requestedScale)) { - // find the next largest size - const possibleScale: ?number = SCALE.find(scale => { - return scale > requestedScale; - }); - - // set to largest size if the real size is larger than what we have - if (possibleScale == null) { - requestedScale = Math.max(...SCALE); - } else { - requestedScale = possibleScale; + // set to largest size if the real size is larger than what we have + if (possibleSize == null) { + requestedSize = Math.max(...AVAILABLE_SIZES); + } else { + requestedSize = possibleSize; + } } - } - return `https://external.xx.fbcdn.net/assets/?name=${name}&variant=${variant}&size=${requestedSize}&set=facebook_icons&density=${requestedScale}x`; -} + if (!DENSITIES.includes(density)) { + // find the next largest size + const possibleDensity = DENSITIES.find(scale => { + return scale > density; + }); + + // set to largest size if the real size is larger than what we have + if (possibleDensity == null) { + density = Math.max(...DENSITIES); + } else { + density = possibleDensity; + } + } + + 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`; + }, +}; diff --git a/static/serviceWorker.js b/static/serviceWorker.js deleted file mode 100644 index db48e3abe..000000000 --- a/static/serviceWorker.js +++ /dev/null @@ -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; - }) - ); - }), - ); - } -}); diff --git a/tsconfig.json b/tsconfig.json index 08217bba6..44f675aea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "system", + "lib": ["es7", "dom"], "removeComments": true, "preserveConstEnums": true, "outFile": "../../built/local/tsc.js",