From d62620ef70e60ee5fb0cf7a4738cf72793a3c6b6 Mon Sep 17 00:00:00 2001 From: Jakub Bogucki <610941+jakub300@users.noreply.github.com> Date: Thu, 6 Sep 2018 18:44:22 +0200 Subject: [PATCH] Add Bundle Report (#378) --- generators/app/templates/gulp/tasks/report.js | 231 ++++++++++++++++++ generators/app/templates/package.json | 3 + 2 files changed, 234 insertions(+) create mode 100644 generators/app/templates/gulp/tasks/report.js diff --git a/generators/app/templates/gulp/tasks/report.js b/generators/app/templates/gulp/tasks/report.js new file mode 100644 index 00000000..5b1ffdbe --- /dev/null +++ b/generators/app/templates/gulp/tasks/report.js @@ -0,0 +1,231 @@ +'use strict'; + +const path = require('path'); +const through = require('through2'); +const zlib = require('zlib'); +const http = require('http'); +const fs = require('fs'); +const explore = require('source-map-explorer'); +const os = require('os'); +const opn = require('opn'); + +const MEBIBYTE = 1024 * 1024; + +const files = {}; +const exploredCache = new Map(); + +function listFiles({ destBase }) { + return through.obj(function getInfoAboutFile(file, enc, callback) { + const relativePath = path.relative(destBase, file.path); + const bufferContents = Buffer.isBuffer(file.contents) + ? file.contents + : Buffer.from(file.contents); + + files[relativePath] = { + path: file.path, + hasSourceMap: false, + size: bufferContents.length, + sizeGzipped: -1, + notes: [], + }; + + zlib.gzip(bufferContents, (err, zipped) => { + if (err) { + throw err; + } + + files[relativePath].sizeGzipped = zipped.length; + callback(); + }); + + this.push(file); + }); +} + +function markSouceMaps({ destBase }) { + return through.obj((file, enc, callback) => { + const relativePath = path.relative(destBase, file.path); + + if (!files[relativePath]) { + return; + } + + if (file.sourceMap && file.sourceMap.preExistingComment) { + if (file.sourceMap.sources.length > 1) { + files[relativePath].hasSourceMap = true; + } else { + files[relativePath].notes.push('only one file'); + } + } + + callback(); + }); +} + +function escapeHTML(s) { + return s + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +} + +function hasFile(resolvedPath) { + if (!resolvedPath.endsWith('.html')) { + return false; + } + + const sourcePath = resolvedPath.slice(0, -5); + + return fs.existsSync(sourcePath); +} + +function formatSize(n) { + if (n < 1024) { + return `${n} B`; + } else if (n < MEBIBYTE) { + return `${(n / 1024).toFixed(2)} KiB`; + } + return `${(n / MEBIBYTE).toFixed(2)} MiB`; +} + +// https://github.com/shakyShane/dev-ip/blob/9f5a1b6154a16db88ca276c08426867c55924e61/lib/dev-ip.js +function getIp() { + const networkInterfaces = os.networkInterfaces(); + const matches = []; + + Object.keys(networkInterfaces).forEach(item => { + networkInterfaces[item].forEach(address => { + if (address.internal === false && address.family === 'IPv4') { + matches.push(address.address); + } + }); + }); + + return matches; +} + +function startServer({ destBase }) { + const root = path.resolve(destBase); + const server = http.createServer((req, res) => { + const { url } = req; + const resolvedPath = path.resolve(root, path.join(root, url)); + + if (!resolvedPath.startsWith(root)) { + res.writeHead(403); + res.end(); + return; + } + + if (url === '/') { + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + }); + + res.write( + `Chisel SourceMap Reports + + ` + ); + + const links = Object.keys(files) + .sort() + .map(link => { + const file = files[link]; + const escapedLink = escapeHTML(link); + return ` + + + + + + + `; + }); + + res.write(links.join('')); + + res.write('
NameSizeGzipped sizeNotes
${ + file.hasSourceMap + ? `${escapedLink}` + : escapedLink + }${formatSize(file.size)}${formatSize(file.sizeGzipped)}${escapeHTML(file.notes.join(', '))}
'); + + res.end(); + } else if (hasFile(resolvedPath)) { + const sourcePath = resolvedPath.slice(0, -5); + let explored; + + if (!exploredCache.has(sourcePath)) { + try { + explored = explore(sourcePath, { html: true }); + exploredCache.set(sourcePath, explored); + } catch (e) { + res.writeHead(500, { + 'Content-Type': 'text/plain; charset=utf-8', + }); + res.write('Error when generating report:\n'); + res.end(e.message); + return; + } + } else { + explored = exploredCache.get(sourcePath); + } + + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + }); + + res.end(explored.html); + } else { + res.writeHead(404); + res.end(); + } + }); + + server.listen(err => { + if (err) { + throw err; + } + + const { port } = server.address(); + + const urls = ['localhost', ...getIp()].map( + host => `http://${host}:${port}` + ); + + console.log(`Bundle Report is started at ${urls.join(', ')}`); + console.log('Use Ctrl+C to close it'); + + opn(urls[0]).catch(() => { + console.log( + 'Failed to open browser. Please open one of above adressess manually' + ); + }); + }); +} + +module.exports = function reportTaskCreator(gulp, plugins, config) { + const { dest, src } = config; + + gulp.task('report-prepare', () => + gulp + .src([ + path.join(dest.base, dest.scripts, '**/*'), + path.join(dest.base, dest.styles, '**/*'), + '!**/*.map', + ]) + .pipe(listFiles({ destBase: dest.base })) + .pipe(plugins.sourcemaps.init({ loadMaps: true })) + .pipe(markSouceMaps({ destBase: dest.base })) + ); + + gulp.task( + 'report', + ['report-prepare'], + () => + new Promise(() => { + startServer({ destBase: dest.base }); + }) + ); +}; diff --git a/generators/app/templates/package.json b/generators/app/templates/package.json index ecf12165..b35cb563 100644 --- a/generators/app/templates/package.json +++ b/generators/app/templates/package.json @@ -5,6 +5,7 @@ "description": "<%= nameSlug %>", "scripts": { "build": "gulp build", + "build-report": "gulp build && gulp report", "dev": "gulp", "lint": "gulp lint-js && gulp lint-css", "start": "gulp", @@ -82,8 +83,10 @@ "ignore": "^3.3.8", "lodash": "^4.17.5", "multipipe": "^2.0.3", + "opn": "^5.3.0", "pre-commit": "^1.2.2", "prettier": "1.11.1", + "source-map-explorer": "^1.6.0", "stylelint": "^9.2.1", "through2": "^2.0.3", "vinyl-named": "^1.1.0",