diff --git a/commands/by.js b/commands/by.js index 7bdd8f4..b9d0568 100644 --- a/commands/by.js +++ b/commands/by.js @@ -1,6 +1,8 @@ /* @flow */ const { analyze, print, getStats } = require("../lib"); +const validate = require("../lib/validate"); +const { log, invalidStatsJson } = require("../lib/messages"); module.exports = function byCommand( statsFilePath /*: string */, @@ -8,6 +10,12 @@ module.exports = function byCommand( pattern /*: string */ ) { const stats = getStats(statsFilePath); + + if (!validate(stats)) { + log(invalidStatsJson(statsFilePath)); + process.exit(1); + } + const ignore = flags.ignore ? flags.ignore.split(",") : []; const report = analyze(stats, ignore).filter(mod => { return ( diff --git a/commands/default.js b/commands/default.js index 56c922f..1392fd3 100644 --- a/commands/default.js +++ b/commands/default.js @@ -2,6 +2,8 @@ const mm = require("micromatch"); const { analyze, print, getStats } = require("../lib"); +const validate = require("../lib/validate"); +const { log, invalidStatsJson } = require("../lib/messages"); /*:: type Flags = { @@ -22,6 +24,12 @@ module.exports = function defaultCommand( pattern /*: string*/ ) { const stats = getStats(statsFilePath); + + if (!validate(stats)) { + log(invalidStatsJson(statsFilePath)); + process.exit(1); + } + const ignore = flags.ignore ? flags.ignore.split(",") : []; const report = analyze(stats, ignore).filter(module => { if (pattern && mm.isMatch(module.name, pattern)) { diff --git a/fixtures/invalid-no-reasons.json b/fixtures/invalid-no-reasons.json new file mode 100644 index 0000000..5bbed6b --- /dev/null +++ b/fixtures/invalid-no-reasons.json @@ -0,0 +1,13 @@ +{ + "chunks": [ + { + "modules": [ + { + "id": "module1", + "name": "module-name", + "size": 500 + } + ] + } + ] +} diff --git a/fixtures/valid.json b/fixtures/valid.json new file mode 100644 index 0000000..cc2ed3d --- /dev/null +++ b/fixtures/valid.json @@ -0,0 +1,14 @@ +{ + "chunks": [ + { + "modules": [ + { + "id": "module1", + "name": "module-name", + "size": 500, + "reasons": [] + } + ] + } + ] +} diff --git a/lib/utils/__tests__/get-stats.js b/lib/__tests__/get-stats.js similarity index 100% rename from lib/utils/__tests__/get-stats.js rename to lib/__tests__/get-stats.js diff --git a/lib/__tests__/validate.js b/lib/__tests__/validate.js new file mode 100644 index 0000000..c9a27b1 --- /dev/null +++ b/lib/__tests__/validate.js @@ -0,0 +1,20 @@ +const test = require("ava"); +const fixtures = require("fixturez"); +const f = fixtures(__dirname); +const validate = require("../validate"); +const getStats = require("../get-stats"); + +test(`should return false for an invalid stats file that doesn't have "reasons" for modules`, t => { + const stats = getStats(f.find("invalid-no-reasons.json")); + t.falsy(validate(stats)); +}); + +test(`should return false for an invalid stats file that doesn't have niether chunks nor modules`, t => { + const stats = getStats(f.find("empty-stats.json")); + t.falsy(validate(stats)); +}); + +test("should return true for a valid stats file", t => { + const stats = getStats(f.find("valid.json")); + t.truthy(validate(stats)); +}); diff --git a/lib/analyze.js b/lib/analyze.js index 2ae9884..5302da9 100644 --- a/lib/analyze.js +++ b/lib/analyze.js @@ -1,7 +1,8 @@ /* @flow */ /*:: export type WebpackStats = { - chunks: Array + chunks?: Array, + modules: Array }; export type WebpackChunk = { @@ -93,7 +94,9 @@ const getModuleType = (name /*: string */) => name.startsWith("multi ") ? "entry" : isNodeModules(name) ? "module" : "file"; const flattenChunks = (stats /*: WebpackStats */) /*: Array */ => - stats.chunks.reduce((modules, chunk) => modules.concat(chunk.modules), []); + stats.chunks + ? stats.chunks.reduce((modules, chunk) => modules.concat(chunk.modules), []) + : stats.modules; const isNodeModules = (identifier /*: string */) => identifier.indexOf("node_modules") > -1; diff --git a/lib/utils/get-stats.js b/lib/get-stats.js similarity index 90% rename from lib/utils/get-stats.js rename to lib/get-stats.js index b03ff23..2bd7067 100644 --- a/lib/utils/get-stats.js +++ b/lib/get-stats.js @@ -3,7 +3,7 @@ const path = require("path"); /*:: -import type { WebpackStats } from '../analyze'; +import type { WebpackStats } from './analyze'; */ const getAbsoultePath = (filePath /*: string */) => diff --git a/lib/index.js b/lib/index.js index f12de8c..a0ce257 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,6 +2,6 @@ const analyze = require("./analyze"); const print = require("./print"); -const getStats = require("./utils/get-stats"); +const getStats = require("./get-stats"); module.exports = { analyze, print, getStats }; diff --git a/lib/messages.js b/lib/messages.js new file mode 100644 index 0000000..b3a6ce0 --- /dev/null +++ b/lib/messages.js @@ -0,0 +1,32 @@ +/* @flow */ + +const chalk = require("chalk"); + +function log(msg /*: Array */) { + console.log(msg.join("\n")); +} + +function redBadge(label /*: string*/) { + return chalk.bgRed.black(` ${label} `); +} + +function errorBadge() { + return redBadge("ERROR"); +} + +function invalidStatsJson(file /*: string */) { + return [ + chalk.red( + `${errorBadge()} Stats file ${chalk.bold( + `"${file}"` + )} doesn't contain "reasons" why modules are included...` + ), + `${chalk.yellow("Whybundled")} needs "reasons" to function properly.`, + "", + `To add reason check webpack documentation about stats configuration: ${chalk.blue( + "https://webpack.js.org/configuration/stats/#stats" + )}` + ]; +} + +module.exports = { log, invalidStatsJson }; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..6887ded --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,28 @@ +/* @flow */ + +/*:: +import type { WebpackStats } from "./analyze"; +*/ + +function has(obj) { + return function(prop) { + return obj.hasOwnProperty(prop); + }; +} + +function isValidModule(sample) { + const isProp = has(sample); + if (!isProp("reasons")) return false; + return true; +} + +module.exports = function vlidateStatsJson(stats /*: WebpackStats */) { + if ( + !stats || + ((!stats.chunks || !stats.chunks[0].modules) && !stats.modules) + ) { + return false; + } + const samples = (stats.modules || stats.chunks[0].modules).slice(0, 10); + return samples.some(isValidModule); +};