You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
5.4 KiB
223 lines
5.4 KiB
// From https://github.com/FormidableLabs/webpack-dashboard/blob/7f99b31c5f00a7818d8129cb8a8fc6eb1b71799c/plugin/index.js |
|
// Modified by Guillaume Chau (Akryum) |
|
|
|
/* eslint-disable max-params, max-statements */ |
|
'use strict' |
|
|
|
const path = require('path') |
|
const fs = require('fs-extra') |
|
const webpack = require('webpack') |
|
const { IpcMessenger } = require('@vue/cli-shared-utils') |
|
const { analyzeBundle } = require('./analyzeBundle') |
|
|
|
const ID = 'vue-cli-dashboard-plugin' |
|
const ONE_SECOND = 1000 |
|
const FILENAME_QUERY_REGEXP = /\?.*$/ |
|
|
|
const ipc = new IpcMessenger() |
|
|
|
function getTimeMessage (timer) { |
|
let time = Date.now() - timer |
|
|
|
if (time >= ONE_SECOND) { |
|
time /= ONE_SECOND |
|
time = Math.round(time) |
|
time += 's' |
|
} else { |
|
time += 'ms' |
|
} |
|
|
|
return ` (${time})` |
|
} |
|
|
|
class DashboardPlugin { |
|
constructor (options) { |
|
this.type = options.type |
|
if (this.type === 'build' && options.moduleBuild) { |
|
this.type = 'build-modern' |
|
} |
|
this.watching = false |
|
this.autoDisconnect = !options.keepAlive |
|
} |
|
|
|
cleanup () { |
|
this.sendData = null |
|
if (this.autoDisconnect) ipc.disconnect() |
|
} |
|
|
|
apply (compiler) { |
|
let sendData = this.sendData |
|
let timer |
|
let inProgress = false |
|
|
|
let assetSources = new Map() |
|
|
|
if (!sendData) { |
|
sendData = data => ipc.send({ |
|
webpackDashboardData: { |
|
type: this.type, |
|
value: data |
|
} |
|
}) |
|
} |
|
|
|
// Progress status |
|
let progressTime = Date.now() |
|
const progressPlugin = new webpack.ProgressPlugin((percent, msg) => { |
|
// in webpack 5, progress plugin will continue sending progresses even after the done hook |
|
// for things like caching, causing the progress indicator stuck at 0.99 |
|
// so we have to use a flag to stop sending such `compiling` progress data |
|
if (!inProgress) { |
|
return |
|
} |
|
|
|
// Debouncing |
|
const time = Date.now() |
|
if (time - progressTime > 300) { |
|
progressTime = time |
|
sendData([ |
|
{ |
|
type: 'status', |
|
value: 'Compiling' |
|
}, |
|
{ |
|
type: 'progress', |
|
value: percent |
|
}, |
|
{ |
|
type: 'operations', |
|
value: msg + getTimeMessage(timer) |
|
} |
|
]) |
|
} |
|
}) |
|
progressPlugin.apply(compiler) |
|
|
|
compiler.hooks.watchRun.tap(ID, c => { |
|
this.watching = true |
|
}) |
|
|
|
compiler.hooks.run.tap(ID, c => { |
|
this.watching = false |
|
}) |
|
|
|
compiler.hooks.compile.tap(ID, () => { |
|
inProgress = true |
|
timer = Date.now() |
|
|
|
sendData([ |
|
{ |
|
type: 'status', |
|
value: 'Compiling' |
|
}, |
|
{ |
|
type: 'progress', |
|
value: 0 |
|
} |
|
]) |
|
}) |
|
|
|
compiler.hooks.invalid.tap(ID, () => { |
|
sendData([ |
|
{ |
|
type: 'status', |
|
value: 'Invalidated' |
|
}, |
|
{ |
|
type: 'progress', |
|
value: 0 |
|
}, |
|
{ |
|
type: 'operations', |
|
value: 'idle' |
|
} |
|
]) |
|
}) |
|
|
|
compiler.hooks.failed.tap(ID, () => { |
|
sendData([ |
|
{ |
|
type: 'status', |
|
value: 'Failed' |
|
}, |
|
{ |
|
type: 'operations', |
|
value: `idle${getTimeMessage(timer)}` |
|
} |
|
]) |
|
inProgress = false |
|
}) |
|
|
|
compiler.hooks.afterEmit.tap(ID, compilation => { |
|
assetSources = new Map() |
|
for (const name in compilation.assets) { |
|
const asset = compilation.assets[name] |
|
const filename = name.replace(FILENAME_QUERY_REGEXP, '') |
|
try { |
|
assetSources.set(filename, asset.source()) |
|
} catch (e) { |
|
const webpackFs = compiler.outputFileSystem |
|
const fullPath = (webpackFs.join || path.join)(compiler.options.output.path, filename) |
|
const buf = webpackFs.readFileSync(fullPath) |
|
assetSources.set(filename, buf.toString()) |
|
} |
|
} |
|
}) |
|
|
|
compiler.hooks.done.tap(ID, stats => { |
|
let statsData = stats.toJson() |
|
// Sometimes all the information is located in `children` array |
|
if ((!statsData.assets || !statsData.assets.length) && statsData.children && statsData.children.length) { |
|
statsData = statsData.children[0] |
|
} |
|
|
|
const outputPath = compiler.options.output.path |
|
statsData.assets.forEach(asset => { |
|
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it) |
|
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '') |
|
asset.fullPath = path.join(outputPath, asset.name) |
|
}) |
|
// Analyze the assets and update sizes on assets and modules |
|
analyzeBundle(statsData, assetSources) |
|
|
|
const hasErrors = stats.hasErrors() |
|
|
|
sendData([ |
|
{ |
|
type: 'status', |
|
value: hasErrors ? 'Failed' : 'Success' |
|
}, |
|
{ |
|
type: 'progress', |
|
value: 1 |
|
}, |
|
{ |
|
type: 'operations', |
|
value: `idle${getTimeMessage(timer)}` |
|
} |
|
]) |
|
inProgress = false |
|
|
|
const statsFile = path.resolve(process.cwd(), `./node_modules/.stats-${this.type}.json`) |
|
fs.writeJson(statsFile, { |
|
errors: hasErrors, |
|
warnings: stats.hasWarnings(), |
|
data: statsData |
|
}).then(() => { |
|
sendData([ |
|
{ |
|
type: 'stats' |
|
} |
|
]) |
|
|
|
if (!this.watching) { |
|
this.cleanup() |
|
} |
|
}).catch(error => { |
|
console.error(error) |
|
}) |
|
}) |
|
} |
|
} |
|
|
|
module.exports = DashboardPlugin
|
|
|