ui
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
class BuildCallbackPlugin {
|
||||
/**
|
||||
* Create a new plugin instance.
|
||||
*
|
||||
* @param {(stats: import("webpack").Stats) => any|Promise<any>} callback
|
||||
*/
|
||||
constructor(callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tapPromise('BuildCallbackPlugin', async stats => {
|
||||
return await this.callback(stats);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BuildCallbackPlugin;
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
const _ = require('lodash');
|
||||
const chalk = require('chalk');
|
||||
const Table = require('cli-table3');
|
||||
const readline = require('readline');
|
||||
const stripAnsi = require('strip-ansi');
|
||||
const { formatSize } = require('webpack/lib/SizeFormatHelpers');
|
||||
const { version } = require('../../package.json');
|
||||
|
||||
/**
|
||||
* @typedef {object} BuildOutputOptions
|
||||
* @property {boolean} clearConsole
|
||||
* @property {boolean} showRelated
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef {object} StatsAsset
|
||||
* @property {string} name
|
||||
* @property {number} size
|
||||
* @property {StatsAsset[]|{}} related
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef {object} StatsData
|
||||
* @property {StatsAsset[]} assets
|
||||
**/
|
||||
|
||||
class BuildOutputPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {BuildOutputOptions} options
|
||||
*/
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.patched = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
// TODO: Refactor setup to allow removing this check
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return;
|
||||
}
|
||||
|
||||
compiler.hooks.done.tap('BuildOutputPlugin', stats => {
|
||||
if (stats.hasErrors()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.options.clearConsole) {
|
||||
this.clearConsole();
|
||||
}
|
||||
|
||||
let data = stats.toJson({
|
||||
assets: true,
|
||||
builtAt: true,
|
||||
hash: true,
|
||||
performance: true,
|
||||
relatedAssets: this.options.showRelated
|
||||
});
|
||||
|
||||
this.heading(`Laravel Mix v${version}`);
|
||||
|
||||
console.log(chalk.green.bold(`✔ Compiled Successfully in ${data.time}ms`));
|
||||
|
||||
if (data.assets.length) {
|
||||
console.log(this.statsTable(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a block section heading.
|
||||
*
|
||||
* @param {string} text
|
||||
*/
|
||||
heading(text) {
|
||||
console.log();
|
||||
|
||||
console.log(chalk.bgBlue.white.bold(this.section(text)));
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a block section.
|
||||
*
|
||||
* @param {string} text
|
||||
*/
|
||||
section(text) {
|
||||
const padLength = 3;
|
||||
const padding = ' '.repeat(padLength);
|
||||
|
||||
text = `${padding}${text}${padding}`;
|
||||
|
||||
const line = ' '.repeat(text.length);
|
||||
|
||||
return `${line}\n${text}\n${line}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the stats table.
|
||||
*
|
||||
* @param {StatsData} data
|
||||
* @returns {string}
|
||||
*/
|
||||
statsTable(data) {
|
||||
const assets = this.sortAssets(data);
|
||||
|
||||
const table = new Table({
|
||||
head: [chalk.bold('File'), chalk.bold('Size')],
|
||||
colWidths: [35],
|
||||
colAligns: ['right'],
|
||||
style: {
|
||||
head: [],
|
||||
compact: true
|
||||
}
|
||||
});
|
||||
|
||||
for (const asset of assets) {
|
||||
table.push([chalk.green(asset.name), formatSize(asset.size)]);
|
||||
}
|
||||
|
||||
this.extendTableWidth(table);
|
||||
this.monkeyPatchTruncate();
|
||||
|
||||
return table.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {StatsData} data
|
||||
*/
|
||||
sortAssets(data) {
|
||||
let assets = data.assets;
|
||||
|
||||
assets = _.flatMap(assets, asset => [
|
||||
asset,
|
||||
...(Array.isArray(asset.related) ? asset.related : [])
|
||||
]);
|
||||
|
||||
assets = _.orderBy(assets, ['name', 'size'], ['asc', 'asc']);
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the entire screen.
|
||||
*/
|
||||
clearConsole() {
|
||||
const blank = '\n'.repeat(process.stdout.rows);
|
||||
console.log(blank);
|
||||
|
||||
readline.cursorTo(process.stdout, 0, 0);
|
||||
readline.clearScreenDown(process.stdout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the width of the table
|
||||
*
|
||||
* Currently only increases the file column size
|
||||
*
|
||||
* @param {import("cli-table3").Table} table
|
||||
* @param {number|null} targetWidth
|
||||
* @param {number} maxWidth
|
||||
*/
|
||||
extendTableWidth(table, targetWidth = null, maxWidth = Infinity) {
|
||||
targetWidth = targetWidth === null ? process.stdout.columns : targetWidth;
|
||||
|
||||
if (!targetWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tableWidth = this.calculateTableWidth(table);
|
||||
const fileColIncrease = Math.min(targetWidth - tableWidth, maxWidth - tableWidth);
|
||||
|
||||
if (fileColIncrease <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
table.options.colWidths[0] += fileColIncrease;
|
||||
}
|
||||
|
||||
// Yeah, I know.
|
||||
monkeyPatchTruncate() {
|
||||
if (this.patched) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.patched = true;
|
||||
|
||||
// @ts-ignore
|
||||
const utils = require('cli-table3/src/utils');
|
||||
const oldTruncate = utils.truncate;
|
||||
|
||||
// cli-table3 can only do truncation at the end
|
||||
// We want the asset name to be truncated at the beginning if it's too long
|
||||
// FIXME: We really should set truncation location via a paramter or something
|
||||
// (or PR support for alignment-based truncation)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {number} desiredLength
|
||||
* @param {string} truncateChar
|
||||
*/
|
||||
utils.truncate = (str, desiredLength, truncateChar) => {
|
||||
if (stripAnsi(str).length > desiredLength) {
|
||||
str = `…${str.substr(-desiredLength + 2)}`;
|
||||
}
|
||||
|
||||
return oldTruncate(str, desiredLength, truncateChar);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the width of the CLI Table
|
||||
*
|
||||
* `table.width` does not report the correct width
|
||||
* because it includes ANSI control characters
|
||||
*
|
||||
* @internal
|
||||
* @param {import("cli-table3").Table} table
|
||||
*/
|
||||
calculateTableWidth(table) {
|
||||
const firstRow = table.toString().split('\n')[0];
|
||||
|
||||
return stripAnsi(firstRow).length;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BuildOutputPlugin;
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* This plugin ensures that vue styles are always appended to the end of CSS files
|
||||
*/
|
||||
class AppendVueStylesPlugin {
|
||||
/** @param {import("webpack").Compiler} compiler */
|
||||
apply(compiler) {
|
||||
const name = 'AppendVueStylesPlugin';
|
||||
|
||||
compiler.hooks.compilation.tap(name, compilation => {
|
||||
compilation.hooks.optimizeChunks.tap(name, chunks => {
|
||||
this.reorderModules(compilation.chunkGraph, chunks);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("webpack").ChunkGraph} graph
|
||||
* @param {Iterable<import("webpack").Chunk>} chunks
|
||||
*/
|
||||
reorderModules(graph, chunks) {
|
||||
const queue = this.collectCssChunks(graph, chunks);
|
||||
|
||||
// Find the last module in the bundle
|
||||
let largestIndex = 0;
|
||||
|
||||
for (const { module, group } of queue) {
|
||||
largestIndex = Math.max(largestIndex, group.getModulePostOrderIndex(module));
|
||||
}
|
||||
|
||||
// Push all vue assets after it in their original order
|
||||
for (const { module, group } of queue) {
|
||||
if (module.identifier().includes('?vue')) {
|
||||
group.setModulePostOrderIndex(
|
||||
module,
|
||||
largestIndex + group.getModulePostOrderIndex(module)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("webpack").ChunkGraph} graph
|
||||
* @param {Iterable<import("webpack").Chunk>} chunks
|
||||
*/
|
||||
collectCssChunks(graph, chunks) {
|
||||
const queue = [];
|
||||
|
||||
for (const chunk of chunks) {
|
||||
for (const module of graph.getChunkModulesIterable(chunk)) {
|
||||
if (module.type !== 'css/mini-extract') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const group of chunk.groupsIterable) {
|
||||
queue.push({ module, chunk, group });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppendVueStylesPlugin;
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
let Log = require('../Log');
|
||||
let collect = require('collect.js');
|
||||
|
||||
class CustomTasksPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {import('../Mix')} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tapPromise(this.constructor.name, async stats => {
|
||||
await this.runTasks(stats);
|
||||
|
||||
if (this.mix.components.get('version') && !this.mix.isUsing('hmr')) {
|
||||
this.applyVersioning();
|
||||
}
|
||||
|
||||
if (this.mix.inProduction()) {
|
||||
await this.minifyAssets();
|
||||
}
|
||||
|
||||
if (this.mix.isWatching()) {
|
||||
this.mix.tasks.forEach(task => task.watch(this.mix.isPolling()));
|
||||
}
|
||||
|
||||
this.mix.manifest.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the task.
|
||||
*
|
||||
* @param {import("../tasks/Task")} task
|
||||
* @param {import("webpack").Stats} stats
|
||||
*/
|
||||
async runTask(task, stats) {
|
||||
await Promise.resolve(task.run());
|
||||
|
||||
await Promise.allSettled(task.assets.map(asset => this.addAsset(asset, stats)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add asset to the webpack stats.
|
||||
*
|
||||
* @param {import("../File")} asset
|
||||
* @param {import("webpack").Stats} stats
|
||||
*/
|
||||
async addAsset(asset, stats) {
|
||||
// Skip adding directories to the manifest
|
||||
// TODO: We should probably add the directory but skip hashing
|
||||
if (asset.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = asset.pathFromPublic();
|
||||
|
||||
// Add the asset to the manifest
|
||||
this.mix.manifest.add(path);
|
||||
|
||||
// Update the Webpack assets list for better terminal output.
|
||||
stats.compilation.assets[path] = {
|
||||
size: () => asset.size(),
|
||||
emitted: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute potentially asynchronous tasks sequentially.
|
||||
*
|
||||
* @param stats
|
||||
* @param index
|
||||
*/
|
||||
runTasks(stats, index = 0) {
|
||||
if (index === this.mix.tasks.length) return Promise.resolve();
|
||||
|
||||
const task = this.mix.tasks[index];
|
||||
|
||||
return this.runTask(task, stats).then(() => this.runTasks(stats, index + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the given asset file.
|
||||
*/
|
||||
async minifyAssets() {
|
||||
const assets = collect(this.mix.tasks)
|
||||
.where('constructor.name', '!==', 'VersionFilesTask')
|
||||
.flatMap(({ assets }) => assets);
|
||||
|
||||
const tasks = assets.map(async asset => {
|
||||
try {
|
||||
await asset.minify();
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
`Whoops! We had trouble minifying "${asset.relativePath()}". ` +
|
||||
`Perhaps you need to use mix.babel() instead?`
|
||||
);
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(tasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version all files that are present in the manifest.
|
||||
*/
|
||||
applyVersioning() {
|
||||
collect(this.mix.manifest.get()).each((value, key) =>
|
||||
this.mix.manifest.hash(key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomTasksPlugin;
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
class ManifestPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {import("../Mix")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
// TODO: Simplify in Mix 7 -- Here for backwards compat if a plugin creates this class directly
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync('ManifestPlugin', (curCompiler, callback) => {
|
||||
let stats = curCompiler.getStats().toJson();
|
||||
|
||||
// Handle the creation of the mix-manifest.json file.
|
||||
this.mix.manifest.transform(stats).refresh();
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ManifestPlugin;
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
let dotenv = require('dotenv');
|
||||
let expand = require('dotenv-expand');
|
||||
|
||||
/** @internal */
|
||||
class MixDefinitionsPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {string} envPath
|
||||
* @param {Record<string, string>} [additionalEnv]
|
||||
*/
|
||||
constructor(envPath, additionalEnv = {}) {
|
||||
this.envPath = envPath;
|
||||
this.additionalEnv = additionalEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('webpack').Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
this.plugin.apply(compiler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all MIX_ definitions for Webpack's DefinePlugin.
|
||||
*/
|
||||
get env() {
|
||||
// Load .env, if it exists, into process.env
|
||||
expand(dotenv.config({ path: this.envPath }));
|
||||
|
||||
// Take everything from process.env that beings with MIX_
|
||||
const regex = /^MIX_/i;
|
||||
const existing = Object.fromEntries(
|
||||
Object.entries(process.env).filter(([key]) => regex.test(key))
|
||||
);
|
||||
|
||||
// Merge in env vaiues from:
|
||||
// - process.env
|
||||
// - the .env file
|
||||
// - the additional env provided to the plugin
|
||||
return {
|
||||
...existing,
|
||||
...this.additionalEnv
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the necessary definitions and add them to the DefinePlugin.
|
||||
*/
|
||||
get plugin() {
|
||||
const { EnvironmentPlugin } = require('webpack');
|
||||
|
||||
return new EnvironmentPlugin(this.env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all MIX_ definitions for Webpack's DefinePlugin.
|
||||
* This is no longer used but here for backwards compat.
|
||||
*
|
||||
* @deprecated
|
||||
* @param {Record<string, string>} additionalEnv
|
||||
*/
|
||||
getDefinitions(additionalEnv) {
|
||||
return Object.fromEntries(
|
||||
Object.entries({ ...this.env, ...additionalEnv }).map(([key, value]) => {
|
||||
return [`process.env.${key}`, JSON.stringify(value)];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the necessary definitions and add them to the DefinePlugin.
|
||||
*
|
||||
* Here for backwards compat only
|
||||
* @deprecated
|
||||
* @param {Record<string, string>} additionalEnv
|
||||
*/
|
||||
static build(additionalEnv) {
|
||||
return new MixDefinitionsPlugin(global.Mix.paths.root('.env'), additionalEnv)
|
||||
.plugin;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MixDefinitionsPlugin;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
let File = require('../File');
|
||||
let path = require('path');
|
||||
|
||||
class MockEntryPlugin {
|
||||
/**
|
||||
* @param {import('../Mix')} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
// TODO: Remove in Mix 7 -- Here for backwards compat if a plugin requires this file
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the deletion of the temporary mix.js
|
||||
* output file that was generated by webpack.
|
||||
*
|
||||
* This file is created when the user hasn't
|
||||
* requested any JavaScript compilation, but
|
||||
* webpack still requires an entry.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tap('MockEntryPlugin', stats => {
|
||||
const assets = stats.toJson().assets || [];
|
||||
const temporaryOutputFile = assets.find(asset => asset.name === 'mix.js');
|
||||
|
||||
if (!temporaryOutputFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete stats.compilation.assets[temporaryOutputFile.name];
|
||||
|
||||
File.find(
|
||||
path.resolve(this.mix.config.publicPath, temporaryOutputFile.name)
|
||||
).delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockEntryPlugin;
|
||||
Reference in New Issue
Block a user