| /* |
| MIT License http://www.opensource.org/licenses/mit-license.php |
| Author Tobias Koppers @sokra |
| */ |
| "use strict"; |
| |
| const validateOptions = require("schema-utils"); |
| const schema = require("../schemas/plugins/ProgressPlugin.json"); |
| |
| /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */ |
| /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */ |
| |
| const createDefaultHandler = profile => { |
| let lineCaretPosition = 0; |
| let lastMessage = ""; |
| let lastState; |
| let lastStateTime; |
| |
| const defaultHandler = (percentage, msg, ...args) => { |
| let state = msg; |
| const details = args.filter(v => v.length); |
| const maxLineLength = process.stderr.columns || Infinity; |
| if (percentage < 1) { |
| percentage = Math.floor(percentage * 100); |
| msg = `${percentage}% ${msg}`; |
| if (percentage < 100) { |
| msg = ` ${msg}`; |
| } |
| if (percentage < 10) { |
| msg = ` ${msg}`; |
| } |
| |
| if (details.length) { |
| const maxTotalDetailsLength = maxLineLength - msg.length; |
| const totalDetailsLength = details.reduce( |
| (a, b) => a + b.length, |
| details.length // account for added space before each detail text |
| ); |
| const maxDetailLength = |
| totalDetailsLength < maxTotalDetailsLength |
| ? Infinity |
| : Math.floor(maxTotalDetailsLength / details.length); |
| |
| for (let detail of details) { |
| if (!detail) continue; |
| if (detail.length + 1 > maxDetailLength) { |
| const truncatePrefix = "..."; |
| detail = `${truncatePrefix}${detail.substr( |
| -(maxDetailLength - truncatePrefix.length - 1) |
| )}`; |
| } |
| msg += ` ${detail}`; |
| } |
| } |
| } |
| if (profile) { |
| state = state.replace(/^\d+\/\d+\s+/, ""); |
| if (percentage === 0) { |
| lastState = null; |
| lastStateTime = Date.now(); |
| } else if (state !== lastState || percentage === 1) { |
| const now = Date.now(); |
| if (lastState) { |
| const stateMsg = `${now - lastStateTime}ms ${lastState}`; |
| goToLineStart(stateMsg); |
| process.stderr.write(stateMsg + "\n"); |
| lineCaretPosition = 0; |
| } |
| lastState = state; |
| lastStateTime = now; |
| } |
| } |
| if (lastMessage !== msg) { |
| goToLineStart(msg); |
| msg = msg.substring(0, maxLineLength); |
| process.stderr.write(msg); |
| lastMessage = msg; |
| } |
| }; |
| |
| const goToLineStart = nextMessage => { |
| let str = ""; |
| for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) { |
| str += "\b \b"; |
| } |
| for (var i = 0; i < lineCaretPosition; i++) { |
| str += "\b"; |
| } |
| lineCaretPosition = nextMessage.length; |
| if (str) process.stderr.write(str); |
| }; |
| |
| return defaultHandler; |
| }; |
| |
| class ProgressPlugin { |
| /** |
| * @param {ProgressPluginArgument} options options |
| */ |
| constructor(options) { |
| if (typeof options === "function") { |
| options = { |
| handler: options |
| }; |
| } |
| |
| options = options || {}; |
| validateOptions(schema, options, "Progress Plugin"); |
| options = Object.assign({}, ProgressPlugin.defaultOptions, options); |
| |
| this.profile = options.profile; |
| this.handler = options.handler; |
| this.modulesCount = options.modulesCount; |
| this.showEntries = options.entries; |
| this.showModules = options.modules; |
| this.showActiveModules = options.activeModules; |
| } |
| |
| apply(compiler) { |
| const { modulesCount } = this; |
| const handler = this.handler || createDefaultHandler(this.profile); |
| const showEntries = this.showEntries; |
| const showModules = this.showModules; |
| const showActiveModules = this.showActiveModules; |
| if (compiler.compilers) { |
| const states = new Array(compiler.compilers.length); |
| compiler.compilers.forEach((compiler, idx) => { |
| new ProgressPlugin((p, msg, ...args) => { |
| states[idx] = [p, msg, ...args]; |
| handler( |
| states |
| .map(state => (state && state[0]) || 0) |
| .reduce((a, b) => a + b) / states.length, |
| `[${idx}] ${msg}`, |
| ...args |
| ); |
| }).apply(compiler); |
| }); |
| } else { |
| let lastModulesCount = 0; |
| let lastEntriesCount = 0; |
| let moduleCount = modulesCount; |
| let entriesCount = 1; |
| let doneModules = 0; |
| let doneEntries = 0; |
| const activeModules = new Set(); |
| let lastActiveModule = ""; |
| |
| const update = () => { |
| const percentByModules = |
| doneModules / Math.max(lastModulesCount, moduleCount); |
| const percentByEntries = |
| doneEntries / Math.max(lastEntriesCount, entriesCount); |
| |
| const items = [ |
| 0.1 + Math.max(percentByModules, percentByEntries) * 0.6, |
| "building" |
| ]; |
| if (showEntries) { |
| items.push(`${doneEntries}/${entriesCount} entries`); |
| } |
| if (showModules) { |
| items.push(`${doneModules}/${moduleCount} modules`); |
| } |
| if (showActiveModules) { |
| items.push(`${activeModules.size} active`); |
| items.push(lastActiveModule); |
| } |
| handler(...items); |
| }; |
| |
| const moduleAdd = module => { |
| moduleCount++; |
| if (showActiveModules) { |
| const ident = module.identifier(); |
| if (ident) { |
| activeModules.add(ident); |
| lastActiveModule = ident; |
| } |
| } |
| update(); |
| }; |
| |
| const entryAdd = (entry, name) => { |
| entriesCount++; |
| update(); |
| }; |
| |
| const moduleDone = module => { |
| doneModules++; |
| if (showActiveModules) { |
| const ident = module.identifier(); |
| if (ident) { |
| activeModules.delete(ident); |
| if (lastActiveModule === ident) { |
| lastActiveModule = ""; |
| for (const m of activeModules) { |
| lastActiveModule = m; |
| } |
| } |
| } |
| } |
| update(); |
| }; |
| |
| const entryDone = (entry, name) => { |
| doneEntries++; |
| update(); |
| }; |
| |
| compiler.hooks.compilation.tap("ProgressPlugin", compilation => { |
| if (compilation.compiler.isChild()) return; |
| lastModulesCount = moduleCount; |
| lastEntriesCount = entriesCount; |
| moduleCount = entriesCount = 0; |
| doneModules = doneEntries = 0; |
| handler(0, "compiling"); |
| |
| compilation.hooks.buildModule.tap("ProgressPlugin", moduleAdd); |
| compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone); |
| compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone); |
| |
| compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd); |
| compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone); |
| compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone); |
| |
| const hooks = { |
| finishModules: "finish module graph", |
| seal: "sealing", |
| beforeChunks: "chunk graph", |
| afterChunks: "after chunk graph", |
| optimizeDependenciesBasic: "basic dependencies optimization", |
| optimizeDependencies: "dependencies optimization", |
| optimizeDependenciesAdvanced: "advanced dependencies optimization", |
| afterOptimizeDependencies: "after dependencies optimization", |
| optimize: "optimizing", |
| optimizeModulesBasic: "basic module optimization", |
| optimizeModules: "module optimization", |
| optimizeModulesAdvanced: "advanced module optimization", |
| afterOptimizeModules: "after module optimization", |
| optimizeChunksBasic: "basic chunk optimization", |
| optimizeChunks: "chunk optimization", |
| optimizeChunksAdvanced: "advanced chunk optimization", |
| afterOptimizeChunks: "after chunk optimization", |
| optimizeTree: "module and chunk tree optimization", |
| afterOptimizeTree: "after module and chunk tree optimization", |
| optimizeChunkModulesBasic: "basic chunk modules optimization", |
| optimizeChunkModules: "chunk modules optimization", |
| optimizeChunkModulesAdvanced: "advanced chunk modules optimization", |
| afterOptimizeChunkModules: "after chunk modules optimization", |
| reviveModules: "module reviving", |
| optimizeModuleOrder: "module order optimization", |
| advancedOptimizeModuleOrder: "advanced module order optimization", |
| beforeModuleIds: "before module ids", |
| moduleIds: "module ids", |
| optimizeModuleIds: "module id optimization", |
| afterOptimizeModuleIds: "module id optimization", |
| reviveChunks: "chunk reviving", |
| optimizeChunkOrder: "chunk order optimization", |
| beforeChunkIds: "before chunk ids", |
| optimizeChunkIds: "chunk id optimization", |
| afterOptimizeChunkIds: "after chunk id optimization", |
| recordModules: "record modules", |
| recordChunks: "record chunks", |
| beforeHash: "hashing", |
| contentHash: "content hashing", |
| afterHash: "after hashing", |
| recordHash: "record hash", |
| beforeModuleAssets: "module assets processing", |
| beforeChunkAssets: "chunk assets processing", |
| additionalChunkAssets: "additional chunk assets processing", |
| record: "recording", |
| additionalAssets: "additional asset processing", |
| optimizeChunkAssets: "chunk asset optimization", |
| afterOptimizeChunkAssets: "after chunk asset optimization", |
| optimizeAssets: "asset optimization", |
| afterOptimizeAssets: "after asset optimization", |
| afterSeal: "after seal" |
| }; |
| const numberOfHooks = Object.keys(hooks).length; |
| Object.keys(hooks).forEach((name, idx) => { |
| const title = hooks[name]; |
| const percentage = (idx / numberOfHooks) * 0.25 + 0.7; |
| compilation.hooks[name].intercept({ |
| name: "ProgressPlugin", |
| context: true, |
| call: () => { |
| handler(percentage, title); |
| }, |
| tap: (context, tap) => { |
| if (context) { |
| // p is percentage from 0 to 1 |
| // args is any number of messages in a hierarchical matter |
| context.reportProgress = (p, ...args) => { |
| handler(percentage, title, tap.name, ...args); |
| }; |
| } |
| handler(percentage, title, tap.name); |
| } |
| }); |
| }); |
| }); |
| compiler.hooks.emit.intercept({ |
| name: "ProgressPlugin", |
| context: true, |
| call: () => { |
| handler(0.95, "emitting"); |
| }, |
| tap: (context, tap) => { |
| if (context) { |
| context.reportProgress = (p, ...args) => { |
| handler(0.95, "emitting", tap.name, ...args); |
| }; |
| } |
| handler(0.95, "emitting", tap.name); |
| } |
| }); |
| compiler.hooks.afterEmit.intercept({ |
| name: "ProgressPlugin", |
| context: true, |
| call: () => { |
| handler(0.98, "after emitting"); |
| }, |
| tap: (context, tap) => { |
| if (context) { |
| context.reportProgress = (p, ...args) => { |
| handler(0.98, "after emitting", tap.name, ...args); |
| }; |
| } |
| handler(0.98, "after emitting", tap.name); |
| } |
| }); |
| compiler.hooks.done.tap("ProgressPlugin", () => { |
| handler(1, ""); |
| }); |
| } |
| } |
| } |
| |
| ProgressPlugin.defaultOptions = { |
| profile: false, |
| modulesCount: 500, |
| modules: true, |
| activeModules: true, |
| // TODO webpack 5 default this to true |
| entries: false |
| }; |
| |
| module.exports = ProgressPlugin; |