From 1b66af274bc74b7cf1a270f7fbf4fbf2d98c4c7c Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Wed, 6 Jun 2018 18:46:39 +0200 Subject: [PATCH 1/3] Add Bundle Report --- generators/app/templates/gulp/tasks/report.js | 233 ++++++++++++++++++ generators/app/templates/package.json | 4 + 2 files changed, 237 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..850db304 --- /dev/null +++ b/generators/app/templates/gulp/tasks/report.js @@ -0,0 +1,233 @@ +'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() + .forEach(link => { + const file = files[link]; + const escapedLink = escapeHTML(link); + links.push( + ` + + + + + ` + ); + }); + + 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..393c80ca 100644 --- a/generators/app/templates/package.json +++ b/generators/app/templates/package.json @@ -5,8 +5,10 @@ "description": "<%= nameSlug %>", "scripts": { "build": "gulp build", + "build-report": "gulp build && gulp report", "dev": "gulp", "lint": "gulp lint-js && gulp lint-css", + "report": "gulp report", "start": "gulp", "watch": "gulp" }, @@ -82,8 +84,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", From 3abeda37e2e090027b0a4563755f3b935f47985d Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Mon, 20 Aug 2018 11:45:10 +0200 Subject: [PATCH 2/3] Remove report command --- generators/app/templates/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/generators/app/templates/package.json b/generators/app/templates/package.json index 393c80ca..b35cb563 100644 --- a/generators/app/templates/package.json +++ b/generators/app/templates/package.json @@ -8,7 +8,6 @@ "build-report": "gulp build && gulp report", "dev": "gulp", "lint": "gulp lint-js && gulp lint-css", - "report": "gulp report", "start": "gulp", "watch": "gulp" }, From c223b838f608e15127b52f402e4627737a1138e0 Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Mon, 20 Aug 2018 20:37:03 +0200 Subject: [PATCH 3/3] Use map instead of forEach --- generators/app/templates/gulp/tasks/report.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/generators/app/templates/gulp/tasks/report.js b/generators/app/templates/gulp/tasks/report.js index 850db304..5b1ffdbe 100644 --- a/generators/app/templates/gulp/tasks/report.js +++ b/generators/app/templates/gulp/tasks/report.js @@ -128,15 +128,13 @@ function startServer({ destBase }) { Notes` ); - const links = []; - - Object.keys(files) + const links = Object.keys(files) .sort() - .forEach(link => { + .map(link => { const file = files[link]; const escapedLink = escapeHTML(link); - links.push( - ` + return ` + ${ file.hasSourceMap ? `${escapedLink}` @@ -145,8 +143,8 @@ function startServer({ destBase }) { ${formatSize(file.size)} ${formatSize(file.sizeGzipped)} ${escapeHTML(file.notes.join(', '))} - ` - ); + + `; }); res.write(links.join(''));