Skip to content

Commit

Permalink
feat(fusuma): improve output logs
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroppy committed Mar 5, 2021
1 parent d972d1a commit f07666b
Show file tree
Hide file tree
Showing 9 changed files with 30,828 additions and 27,958 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ You can see your Note for each slide and the next slide on the Host screen.
Fusuma supports improving performance, SEO, and so on as default.

<img src="./site/docs/assets/lighthouse.png" width="600px">
<img src="./site/docs/assets/lighthouse.png" width="500px">

Fusuma analyzes the slide's performance, and outputs like below.

<img src="./output-logs.png" width="500px">

---

Expand Down
Binary file added output-logs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58,393 changes: 30,523 additions & 27,870 deletions packages/fusuma/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/fusuma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"regenerator-runtime": "^0.13.7",
"rmfr": "^2.0.0",
"style-loader": "^2.0.0",
"table": "^6.0.7",
"terser-webpack-plugin": "^5.1.1",
"webp-loader": "^0.6.0",
"webpack": "^5.24.3",
Expand Down
27 changes: 24 additions & 3 deletions packages/fusuma/src/server/dynamicRenderingServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const fileServer = require('./fileServer');
async function dynamicRenderingServer(outputDirPath, publicPath, spinner, isThumbnail = true) {
spinner.setContent({ color: 'cyan', text: 'Rendering components to HTML...' });

const logs = {
network: [],
performance: {},
};
const port = 5445;
const browser = await puppeteer.launch({
chromeWebSecurity: false,
Expand All @@ -26,10 +30,27 @@ async function dynamicRenderingServer(outputDirPath, publicPath, spinner, isThum
width: 1200,
height: 630,
});

page.on('request', (request) => {
const url = request.url();

if (url.includes(`http://localhost:${port}${publicPath}`)) {
logs.network.push(url.split(`http://localhost:${port}${publicPath}`).pop());
} else {
logs.network.push(url);
}
});

await page.goto(url, {
waitUntil: ['load', 'networkidle2'],
});

const performanceTimingJson = await page.evaluate(() =>
JSON.stringify(window.performance.timing)
);

logs.performance = JSON.parse(performanceTimingJson);

try {
await unlink(htmlPath);
await writeFile(htmlPath, await page.content());
Expand Down Expand Up @@ -67,9 +88,9 @@ async function dynamicRenderingServer(outputDirPath, publicPath, spinner, isThum
}
}

await page.close();
await browser.close();
app.close();
await Promise.all([page.close(), browser.close(), new Promise((r) => app.close(r))]);

return logs;
}

module.exports = dynamicRenderingServer;
18 changes: 11 additions & 7 deletions packages/fusuma/src/tasks/build.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use strict';

const Spinner = require('../cli/Spinner');
const { info, warn } = require('../cli/log');
const { warn } = require('../cli/log');
const deleteDir = require('../utils/deleteDir');
const getRemoteOriginUrl = require('../utils/getRemoteOriginUrl');
const buildLogs = require('../utils/buildLogs');
const { build: webpackBuild } = require('../webpack');
const outputBuildInfo = require('../webpack/outputBuildInfo');
const outputBuildInfo = require('../webpack/getChunks');
const dynamicRenderingServer = require('../server/dynamicRenderingServer');

async function build(config, isConsoleOutput = true) {
Expand Down Expand Up @@ -40,16 +41,19 @@ async function build(config, isConsoleOutput = true) {
}
}

await dynamicRenderingServer(outputDirPath, config.build.publicPath, spinner, neededThumbnail);
const logs = await dynamicRenderingServer(
outputDirPath,
config.build.publicPath,
spinner,
neededThumbnail
);

spinner.stop();

if (isConsoleOutput) {
const logs = outputBuildInfo(stats);
const last = logs.splice(-1);
const chunks = outputBuildInfo(stats);

console.info(logs.join('\n'));
info('build', last);
buildLogs({ ...logs, ...chunks });
}
}

Expand Down
194 changes: 194 additions & 0 deletions packages/fusuma/src/utils/buildLogs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
'use strict';

const { table, getBorderCharacters } = require('table');
const chalk = require('chalk');
const prettyBytes = require('pretty-bytes');
const { javascript } = require('webpack');

const tableConfig = {
border: getBorderCharacters('void'),
columnDefault: {
paddingLeft: 0,
paddingRight: 1,
},
columns: {
3: {
alignment: 'right',
paddingLeft: 1,
},
},
drawHorizontalLine: () => false,
};

function buildLogs(logs) {
const outputStack = [];
const syncChunk = chalk.yellow('○');
const asyncChunk = chalk.cyan('○');

{
const outputStack = [];
const tti = logs.performance.domInteractive - logs.performance.navigationStart;
outputStack.push(['TTI (Time to Interactive)', `${chalk.yellow(tti)} ms`]);
const ttfb = logs.performance.responseStart - logs.performance.requestStart;
outputStack.push(['TTFB (Time to First Byte)', `${chalk.yellow(ttfb)} ms`]);
const ttlb = logs.performance.responseEnd - logs.performance.requestStart;
outputStack.push(['TTLB (Time to Last Byte)', `${chalk.yellow(ttlb)} ms`]);
console.log(table(outputStack, tableConfig));
}

// header
outputStack.push(['', '', 'size', 'gzip']);
outputStack.push([
` ┌─`,
`${syncChunk} /`,
chalk.green(prettyBytes(logs.assets['index.html'].size)),
'',
]);
outputStack.push([' │', '', '', '']);

let runtimeFileNum = 0;
let runtimeGzipFileNum = 0;
const assetsTree = {};

for (const asset of logs.network) {
// html, external assets
if (asset.includes('http')) {
// assetsTree['network'] = {};
} else {
const name = asset.split('.')[0];

runtimeFileNum++;

// assets
if (!asset.includes('.js') && !asset.includes('.css')) {
if (!assetsTree['assets']) {
assetsTree['assets'] = {
size: 0,
gzSize: 0,
files: {},
};
}

assetsTree['assets'].files[asset] = {
gzSize: 0,
...logs.assets[asset],
isAsync: false,
};
assetsTree['assets'].size += logs.assets[asset].size;

const gz = logs.assets[`${asset}.gz`];

if (gz) {
assetsTree['assets'].gzSize += gz.size;
runtimeGzipFileNum++;
}
} else {
if (!Number(name)) {
// initial
if (!assetsTree[name]) {
assetsTree[name] = {
size: 0,
gzSize: 0,
files: {},
};
}

for (const file of logs.chunks[name].files) {
if (!assetsTree[name].files[file]) {
const info = logs.assets[file];
const gz = logs.assets[`${file}.gz`];

assetsTree[name].files[file] = {
...info,
isAsync: false,
};

assetsTree[name].size += info.size;

if (gz) {
runtimeGzipFileNum++;
assetsTree[name].files[file].gzSize = gz.size;
assetsTree[name].gzSize += gz.size;
}
}
}
} else {
// async
for (const file of logs.chunks[name].files) {
const info = logs.assets[file];
const gz = logs.assets[`${file}.gz`];

assetsTree['runtime'].files[file] = {
...info,
isAsync: true,
};
assetsTree['runtime'].size += info.size;
if (gz) {
runtimeGzipFileNum++;
assetsTree['runtime'].files[file].gzSize = gz.size;
assetsTree['runtime'].gzSize += gz.size;
}
}
}
}
}
}

const topFieldNum = Object.keys(assetsTree).length;
let runtimeFileSize = 0;
let runtimeGzipFileSize = 0;

Object.entries(assetsTree).forEach(([initialName, initialValue], topIndex) => {
runtimeFileSize += initialValue.size;
runtimeGzipFileSize += initialValue.gzSize;

outputStack.push([
topFieldNum - 1 !== topIndex ? ' ├─' : ' └─',
`[${initialName}]`,
chalk.green(prettyBytes(initialValue.size)),
initialValue.gzSize !== 0 ? chalk.yellow(prettyBytes(initialValue.gzSize)) : '',
]);

Object.entries(initialValue.files).forEach(([name, { size, gzSize, isAsync }], index) => {
outputStack.push([
topFieldNum - 1 > topIndex ? ' │' : '',
`${Object.keys(initialValue.files).length - 1 !== index ? '├─' : '└─'} ${
isAsync ? asyncChunk : syncChunk
} ${name}`,
chalk.blue(prettyBytes(size)),
gzSize !== 0 ? chalk.blue(prettyBytes(gzSize)) : '',
]);
});

if (topFieldNum - 1 !== topIndex) {
outputStack.push([' │', '', '', '']);
}
});

console.log(table(outputStack, tableConfig));
console.log(`${syncChunk} initial ${asyncChunk} async`);
console.log();

{
const outputStack = [];

outputStack.push([
'runtime file size totals',
chalk.green(prettyBytes(runtimeFileSize)),
`(${runtimeFileNum} files)`,
chalk.yellow(prettyBytes(runtimeGzipFileSize)),
`(${runtimeGzipFileNum} files)`,
]);
outputStack.push([
'file size totals',
chalk.green(prettyBytes(logs.size.total)),
`(${logs.num.total} files)`,
chalk.yellow(prettyBytes(logs.size.gz)),
`(${logs.num.gz} files)`,
]);

console.log(table(outputStack, tableConfig));
}
}

module.exports = buildLogs;
70 changes: 70 additions & 0 deletions packages/fusuma/src/webpack/getChunks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

function getRequiredFilesFromParentChunks(parentChunks, allChunks) {
let requiredFiles = [];

for (let i = 0; i < parentChunks.length; i++) {
const parentChunkId = parentChunks[i];
const parentChunk = allChunks.find((chunk) => chunk.id === parentChunkId);

if (parentChunk) {
if (parentChunk.parents.length > 0) {
requiredFiles = getRequiredFilesFromParentChunks(parentChunk.parents, allChunks);
}
requiredFiles = requiredFiles.concat(parentChunk.files);
}
}

return requiredFiles;
}

function outputBuildInfo(res) {
const logs = {
size: {
total: 0,
gz: 0,
},
num: {
total: 0,
gz: 0,
},
chunks: {},
assets: {},
};
const stats = res.toJson();

for (const chunk of stats.chunks) {
const { names, parents, id, hash, files } = chunk;
const name = names[0] || id;
const requiredFiles = [
...getRequiredFilesFromParentChunks(parents, stats.chunks),
...chunk.files,
];

logs.chunks[`${name}`] = {
hash,
files,
deps: requiredFiles,
};
}

Object.entries(res.compilation.assets).forEach(([name, asset]) => {
const size = asset.size();

logs.size.total += size;
logs.num.total++;

if (name.includes('.gz')) {
logs.size.gz += size;
logs.num.gz++;
}

logs.assets[name] = {
size,
};
});

return logs;
}

module.exports = outputBuildInfo;
Loading

0 comments on commit f07666b

Please sign in to comment.