Skip to content

Commit

Permalink
feat(cli): better loading ui (scullyio#800)
Browse files Browse the repository at this point in the history
* feat(cli): better loading ui

* refactor(scully): small refactor to "smothen" spinner display

Co-authored-by: sanderelias <[email protected]>
Co-authored-by: Sander Elias <[email protected]>
  • Loading branch information
3 people authored Aug 4, 2020
1 parent cd948bc commit 0141e26
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { addNgIdAttribute } from './addNgIdAttribute';
import { getIdAttrName } from './getIdAttrName';
import { getScript } from './getScript';
import { insertContent } from './insertContent';
import * as readline from 'readline';

export const scullyBegin = '<!--scullyContent-begin-->';
export const scullyEnd = '<!--scullyContent-end-->';
Expand All @@ -14,12 +15,16 @@ export function injectHtml(html: string, additionalHTML: string, route: HandledR
try {
attr = getIdAttrName(html.split('<scully-content')[1].split('>')[0].trim());
} catch (e) {
// clear any progress
if (process.stdout.cursorTo) {
readline.clearLine(process.stdout, 0);
}
logWarn(`
----------------
Error, missing "${yellow('<scully-content>')}" in route "${yellow(route.route)}"
without <scully-content> we can not render this route.
Make sure it is in there, and not inside any conditionals (*ngIf)
You can check this by opening "${yellow(`http${ssl ? 'S' : ''}://localhost:4200/${route.route}`)}"
You can check this by opening "${yellow(`http${ssl ? 'S' : ''}://localhost:4200/${route.route.replace(/^\//, '')}`)}"
when you serve your app with ${yellow('ng serve')} and then in the browsers console run:
${yellow(`document.querySelector('scully-content')`)}
----------------
Expand Down
8 changes: 6 additions & 2 deletions libs/scully/src/lib/routerPlugins/contentFolderPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FilePlugin } from '../pluginManagement/Plugin.interfaces';
import { readFileAndCheckPrePublishSlug } from '../renderPlugins/content-render-utils/readFileAndCheckPrePublishSlug';
import { scullyConfig } from '../utils/config';
import { RouteTypeContentFolder } from '../utils/interfacesandenums';
import { log, logWarn, yellow } from '../utils/log';
import { log, logWarn, yellow, printProgress } from '../utils/log';
import { HandledRoute } from './handledRoute.interface';

let basePath: string;
Expand All @@ -22,7 +22,9 @@ export async function contentFolderPlugin(angularRoute: string, conf: RouteTypeC
const baseRoute = angularRoute.split(':' + param)[0];
basePath = join(scullyConfig.homeFolder, paramConfig.folder);
log(`Finding files in folder "${yellow(basePath)}"`);
return await checkSourceIsDirectoryAndRun(basePath, baseRoute, conf);
const handledRoutes = await checkSourceIsDirectoryAndRun(basePath, baseRoute, conf);
printProgress(handledRoutes.length, 'content files added');
return handledRoutes;
}

async function checkSourceIsDirectoryAndRun(path, baseRoute, conf) {
Expand All @@ -44,6 +46,8 @@ async function checkSourceIsDirectoryAndRun(path, baseRoute, conf) {
);
} else {
handledRoutes.push(...(await addHandleRoutes(sourceFile, baseRoute, templateFile, conf, ext)));
// await new Promise((r) => setTimeout(() => r(), 2000));
printProgress(handledRoutes.length, 'content files added');
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions libs/scully/src/lib/routerPlugins/jsonRoutePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { configValidator, registerPlugin } from '../pluginManagement/pluginRepository';
import { registerPlugin } from '../pluginManagement/pluginRepository';
import { deepGet } from '../utils/deepGet';
import { httpGetJson } from '../utils/httpGetJson';
import { RouteTypeJson } from '../utils/interfacesandenums';
import { logError, yellow } from '../utils/log';
import { logError, printProgress, yellow } from '../utils/log';
import { routeSplit } from '../utils/routeSplit';
import { HandledRoute } from './handledRoute.interface';
import { deepGet } from '../utils/deepGet';
import { renderTemplate } from './renderTemplate';

export const jsonRoutePlugin = async (route: string, conf: RouteTypeJson): Promise<HandledRoute[]> => {
Expand All @@ -16,6 +16,7 @@ export const jsonRoutePlugin = async (route: string, conf: RouteTypeJson): Promi
console.error(`missing config for parameters (${missingParams.join(',')}) in route: ${route}. Skipping`);
return [{ route, type: conf.type }];
}
printProgress(undefined, `Json Route plugin loading data for "${yellow(route)}"`);

/** helper to get the data, parses out the context, and the property */
const loadData = (param, context = {}): Promise<any[]> => {
Expand Down
3 changes: 2 additions & 1 deletion libs/scully/src/lib/routerPlugins/traverseAppRoutesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { join } from 'path';
import { scanRoutes, sge } from '../utils/cli-options';
import { scullyConfig } from '../utils/config';
import { existFolder } from '../utils/fsFolder';
import { green, log, logError, logWarn, yellow } from '../utils/log';
import { green, log, logError, logWarn, yellow, printProgress } from '../utils/log';
import { createFolderFor } from '../utils/createFolderFor';
import { scullySystem, registerPlugin } from '../pluginManagement/pluginRepository';

Expand Down Expand Up @@ -35,6 +35,7 @@ Using stored unhandled routes!.
} catch {}
}
log('traversing app for routes');
printProgress(undefined, 'Loading guess-parser');
const excludedFiles =
scullyConfig.guessParserOptions && scullyConfig.guessParserOptions.excludedFiles
? scullyConfig.guessParserOptions.excludedFiles
Expand Down
5 changes: 3 additions & 2 deletions libs/scully/src/lib/systemPlugins/storeRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { HandledRoute } from '../routerPlugins/handledRoute.interface';
import { watch } from '../utils/cli-options';
import { scullyConfig } from '../utils/config';
import { createFolderFor } from '../utils/createFolderFor';
import { log, logError, logWarn, yellow } from '../utils/log';
import { log, logError, logWarn, yellow, printProgress } from '../utils/log';
import { registerPlugin, scullySystem } from '../pluginManagement';

export const routesFileName = '/assets/scully-routes.json';
Expand Down Expand Up @@ -35,8 +35,9 @@ async function storeRoutesPlugin(routes: HandledRoute[]) {
...r.data,
}))
);
const write = (file) => {
const write = (file, i, collection) => {
createFolderFor(file);
printProgress(i + 1, 'Creating Route List:', collection.length);
writeFileSync(file, jsonResult);
};
files.forEach(write);
Expand Down
7 changes: 4 additions & 3 deletions libs/scully/src/lib/utils/asyncPool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { waitForIt } from '../renderPlugins/puppeteerRenderPlugin';
import { logWarn, printProgress } from './log';
import { log, logWarn, printProgress } from './log';
import { performance } from 'perf_hooks';

const progressTime = 100;
Expand All @@ -21,7 +21,8 @@ export async function asyncPool<T>(MaxParralellTasks: number, array: T[], taskFn
executing.push(e);
const now = performance.now();
if (now - logTime > progressTime) {
printProgress(Math.max(array.length - ret.length, executing.length));
const tasksLeft = Math.max(array.length - ret.length, executing.length);
printProgress(array.length + 1 - tasksLeft, 'Rendering Routes:', array.length);
logTime = now;
}
if (executing.length >= MaxParralellTasks) {
Expand All @@ -30,9 +31,9 @@ export async function asyncPool<T>(MaxParralellTasks: number, array: T[], taskFn
}
while (executing.length > 0) {
/** inform used tasks are still running. */
printProgress(executing.length);
await Promise.race([...executing, waitForIt(progressTime)]);
}
printProgress(array.length, 'Rendering Routes:', array.length);
return Promise.all(ret);
}

Expand Down
6 changes: 5 additions & 1 deletion libs/scully/src/lib/utils/handlers/defaultAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { launchedBrowser } from '../../renderPlugins/launchedBrowser';
import { HandledRoute } from '../../routerPlugins/handledRoute.interface';
import { baseFilter } from '../cli-options';
import { loadConfig } from '../config';
import { log } from '../log';
import { log, startProgress, stopProgress } from '../log';
import { handleAllDone } from './handleAllDone';
import { handleRouteDiscoveryDone } from './handleRouteDiscoveryDone';
import { handleTravesal } from './handleTravesal';
Expand All @@ -17,6 +17,8 @@ registerPlugin(scullySystem, generateAll, plugin);
async function plugin(localBaseFilter = baseFilter): Promise<HandledRoute[]> {
await loadConfig;
try {
// maintain progress ui

const unhandledRoutes = await findPlugin(handleTravesal)();

const handledRoutes = await routeDiscovery(unhandledRoutes, localBaseFilter);
Expand All @@ -34,6 +36,8 @@ async function plugin(localBaseFilter = baseFilter): Promise<HandledRoute[]> {
await discoveryDone;
/** fire off the allDone plugins */
await handleAllDone(processedRoutes);

// stop progress ui
return processedRoutes;
} catch (e) {
// TODO: add better error handling
Expand Down
87 changes: 68 additions & 19 deletions libs/scully/src/lib/utils/log.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import chalk from 'chalk';
import { appendFile } from 'fs';
import { join } from 'path';
import * as readline from 'readline';

import { scullyConfig } from '../utils/config';
import { noLog } from './cli-options';
import { findAngularJsonPath } from './findAngularJsonPath';
import { interval } from 'rxjs';
import { tap } from 'rxjs/operators';

export const orange = chalk.hex('#FFA500');
export const { white, red, yellow, green }: { [x: string]: any } = chalk;
Expand All @@ -17,6 +21,61 @@ export const enum LogSeverity {
const logFilePath = join(findAngularJsonPath(), 'scully.log');
const logToFile = (string) => new Promise((res, rej) => appendFile(logFilePath, string, (e) => (e ? rej(e) : res())));

function* spinTokens() {
const tokens = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let current = 0;
while (true) {
yield tokens[current];
current += 1;
if (current === tokens.length) {
current = 0;
}
}
}

export const spinToken = spinTokens();

const state = {
interval: 250,
startTime: Date.now(),
lastMessage: '',
intervalSub: undefined,
lastSpin: spinToken.next().value,
};

function writeProgress(msg = state.lastMessage) {
/** cursorTo isn't there in CI, don't write progress in CI at all. */
if (process.stdout.cursorTo) {
if (Date.now() - state.startTime > state.interval) {
// tslint:disable-next-line: no-unused-expression
state.lastSpin = spinToken.next().value;
state.startTime = Date.now();
}
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0, null);
process.stdout.write(`${state.lastSpin} ${msg}`);
state.lastMessage = msg;
}
}

export function startProgress() {
/** cursorTo isn't there in CI, don't write progress in CI at all. */
if (process.stdout.cursorTo) {
state.intervalSub = interval(state.interval)
.pipe(tap(() => writeProgress()))
.subscribe();
}
}
export function stopProgress() {
state.lastMessage = '';
if (state.intervalSub) {
state.intervalSub.unsubscribe();
}
if (process.stdout.cursorTo) {
readline.clearLine(process.stdout, 0);
}
}

export const log = (...a) => enhancedLog(white, LogSeverity.normal, ...a);
export const logError = (...a) => enhancedLog(red, LogSeverity.error, ...a);
export const logWrite = (...a) => enhancedLog(white, LogSeverity.error, ...a);
Expand All @@ -33,35 +92,25 @@ function enhancedLog(colorFn, severity: LogSeverity, ...args: any[]) {
if (severity >= scullyConfig.logFileSeverity && out.length > 0) {
logToFile(out.filter((i) => i).join('\r\n'))
.then(() => logToFile('\r\n'))
.catch((e) => console.log('error while loggin to file', e));
.catch((e) => console.log('error while logging to file', e));
}
// tslint:disable-next-line: no-unused-expression
process.stdout.cursorTo && process.stdout.cursorTo(0);
process.stdout.write(colorFn(...out));
process.stdout.write('\n');
writeProgress();
}

function makeRelative(txt: string) {
const h = scullyConfig?.homeFolder || process.cwd();
return txt.replace(h, '.');
}

function* spinTokens() {
const tokens = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let current = 0;
while (true) {
yield tokens[current];
current += 1;
if (current === tokens.length) {
current = 0;
}
}
function isNumber(text: any) {
return typeof text === 'number';
}

export const spinToken = spinTokens();
export function printProgress(tasks: number, text = 'Tasks left:'): void {
// process.stdout.clearLine();
// tslint:disable-next-line: no-unused-expression
process.stdout.cursorTo && process.stdout.cursorTo(0);
process.stdout.write(`${spinToken.next().value} ${orange(text)} ${yellow(tasks)} `);
export function printProgress(tasks: number | boolean, text = 'Tasks Left:', total?: number): void {
const number = isNumber(tasks) ? `${yellow(tasks)}` : '';
const maxNumber = isNumber(total) ? `${yellow('/' + total)}` : '';
const msg = `${orange(text)} ${number}${maxNumber}`;
writeProgress(msg);
}
10 changes: 5 additions & 5 deletions libs/scully/src/lib/utils/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import { performance, PerformanceObserver, PerformanceObserverCallback } from 'p
import { watch, ssl } from './cli-options';
import { scullyConfig } from './config';
import { generateAll } from './handlers/defaultAction';
import { log, yellow, green, printProgress } from './log';
import { log, yellow, green, startProgress, printProgress, stopProgress } from './log';
import { performanceIds } from './performanceIds';
import { reloadAll } from '../watchMode';
import { findPlugin } from '../pluginManagement';

let measurePerfPerf: number;
/**
* Starts the entire process
* @param config:ScullyConfig
*/
export const startScully = (url?: string) => {
startProgress();
printProgress(false, 'warming up');
return new Promise((resolve) => {
performance.mark('startDuration');
performanceIds.add('Duration');
Expand All @@ -23,8 +24,7 @@ export const startScully = (url?: string) => {
obs.observe({ entryTypes: ['measure'], buffered: true });
const numberOfRoutesProm = findPlugin(generateAll)(url)
.then((routes) => {
log(`measuring performance`);
measurePerfPerf = Date.now();
printProgress(false, 'calculate timings');
performance.mark('stopDuration');
/** measure all performance checks */
try {
Expand All @@ -46,6 +46,7 @@ export const startScully = (url?: string) => {
const singleTime = duration / numberOfRoutes;
const routesProSecond = Math.ceil((1000 / singleTime) * 100) / 100;
// console.table(durations)
stopProgress();
reloadAll();
log(`
Generating took ${yellow(Math.floor(seconds * 100) / 100)} seconds for ${yellow(numberOfRoutes)} pages:
Expand Down Expand Up @@ -80,7 +81,6 @@ function measurePerformance(resolve: (value?: unknown) => void): PerformanceObse
observer.disconnect();
performanceIds.clear();
resolve(durations);
log(`measuring performance took ${Math.floor((Date.now() - measurePerfPerf) * 100) / 100}Ms`);
};
}

Expand Down

0 comments on commit 0141e26

Please sign in to comment.