1/** 2 * Command line application to run Lottie-Web perf on a Lottie file in the 3 * browser and then exporting that result. 4 * 5 */ 6const puppeteer = require('puppeteer'); 7const express = require('express'); 8const fs = require('fs'); 9const commandLineArgs = require('command-line-args'); 10const commandLineUsage= require('command-line-usage'); 11const fetch = require('node-fetch'); 12 13const opts = [ 14 { 15 name: 'input', 16 typeLabel: '{underline file}', 17 description: 'The Lottie JSON file to process.' 18 }, 19 { 20 name: 'output', 21 typeLabel: '{underline file}', 22 description: 'The perf file to write. Defaults to perf.json', 23 }, 24 { 25 name: 'use_gpu', 26 description: 'Whether we should run in non-headless mode with GPU.', 27 type: Boolean, 28 }, 29 { 30 name: 'port', 31 description: 'The port number to use, defaults to 8081.', 32 type: Number, 33 }, 34 { 35 name: 'lottie_player', 36 description: 'The path to lottie.min.js, defaults to a local npm install location.', 37 type: String, 38 }, 39 { 40 name: 'backend', 41 description: 'Which lottie-web backend to use. Options: canvas or svg.', 42 type: String, 43 }, 44 { 45 name: 'help', 46 alias: 'h', 47 type: Boolean, 48 description: 'Print this usage guide.' 49 }, 50]; 51 52const usage = [ 53 { 54 header: 'Lottie-Web Perf', 55 content: 'Command line application to run Lottie-Web perf.', 56 }, 57 { 58 header: 'Options', 59 optionList: opts, 60 }, 61]; 62 63// Parse and validate flags. 64const options = commandLineArgs(opts); 65 66if (options.backend != 'canvas' && options.backend != 'svg') { 67 console.error('You must supply a lottie-web backend (canvas, svg).'); 68 console.log(commandLineUsage(usage)); 69 process.exit(1); 70} 71 72if (!options.output) { 73 options.output = 'perf.json'; 74} 75if (!options.port) { 76 options.port = 8081; 77} 78if (!options.lottie_player) { 79 options.lottie_player = 'node_modules/lottie-web/build/player/lottie.min.js'; 80} 81 82if (options.help) { 83 console.log(commandLineUsage(usage)); 84 process.exit(0); 85} 86 87if (!options.input) { 88 console.error('You must supply a Lottie JSON filename.'); 89 console.log(commandLineUsage(usage)); 90 process.exit(1); 91} 92 93// Start up a web server to serve the three files we need. 94let lottieJS = fs.readFileSync(options.lottie_player, 'utf8'); 95let lottieJSON = fs.readFileSync(options.input, 'utf8'); 96let driverHTML; 97if (options.backend == 'svg') { 98 console.log('Using lottie-web-perf.html'); 99 driverHTML = fs.readFileSync('lottie-web-perf.html', 'utf8'); 100} else { 101 console.log('Using lottie-web-canvas-perf.html'); 102 driverHTML = fs.readFileSync('lottie-web-canvas-perf.html', 'utf8'); 103} 104 105// Find number of frames from the lottie JSON. 106let lottieJSONContent = JSON.parse(lottieJSON); 107const totalFrames = lottieJSONContent.op - lottieJSONContent.ip; 108console.log('Total frames: ' + totalFrames); 109 110const app = express(); 111app.get('/', (req, res) => res.send(driverHTML)); 112app.get('/res/lottie.js', (req, res) => res.send(lottieJS)); 113app.get('/res/lottie.json', (req, res) => res.send(lottieJSON)); 114app.listen(options.port, () => console.log('- Local web server started.')) 115 116// Utility function. 117async function wait(ms) { 118 await new Promise(resolve => setTimeout(() => resolve(), ms)); 119 return ms; 120} 121 122const targetURL = "http://localhost:" + options.port + "/#" + totalFrames; 123const viewPort = {width: 1000, height: 1000}; 124 125// Drive chrome to load the web page from the server we have running. 126async function driveBrowser() { 127 console.log('- Launching chrome for ' + options.input); 128 let browser; 129 let page; 130 const headless = !options.use_gpu; 131 let browser_args = [ 132 '--no-sandbox', 133 '--disable-setuid-sandbox', 134 '--window-size=' + viewPort.width + ',' + viewPort.height, 135 ]; 136 if (options.use_gpu) { 137 browser_args.push('--ignore-gpu-blacklist'); 138 browser_args.push('--ignore-gpu-blocklist'); 139 browser_args.push('--enable-gpu-rasterization'); 140 } 141 console.log("Running with headless: " + headless + " args: " + browser_args); 142 try { 143 browser = await puppeteer.launch({headless: headless, args: browser_args}); 144 page = await browser.newPage(); 145 await page.setViewport(viewPort); 146 } catch (e) { 147 console.log('Could not open the browser.', e); 148 process.exit(1); 149 } 150 151 console.log("Loading " + targetURL); 152 try { 153 // Start trace. 154 await page.tracing.start({ 155 path: options.output, 156 screenshots: false, 157 categories: ["blink", "cc", "gpu"] 158 }); 159 160 await page.goto(targetURL, { 161 timeout: 60000, 162 waitUntil: 'networkidle0' 163 }); 164 165 console.log('- Waiting 60s for run to be done.'); 166 await page.waitForFunction('window._lottieWebDone === true', { 167 timeout: 60000, 168 }); 169 170 // Stop trace. 171 await page.tracing.stop(); 172 } catch(e) { 173 console.log('Timed out while loading or drawing. Either the JSON file was ' + 174 'too big or hit a bug in the player.', e); 175 await browser.close(); 176 process.exit(1); 177 } 178 179 await browser.close(); 180 // Need to call exit() because the web server is still running. 181 process.exit(0); 182} 183 184driveBrowser(); 185