node_modules

This commit is contained in:
saeid
2019-07-15 13:07:31 +04:30
parent d3735c6d30
commit ce1a3ebf9c
35713 changed files with 3639219 additions and 1 deletions
+106
View File
@@ -0,0 +1,106 @@
let webpack = require('webpack');
let path = require('path');
class Api {
/**
* Enable sourcemap support.
*
* @param {Boolean} generateForProduction
* @param {string} devType
* @param {string} productionType
*/
sourceMaps(
generateForProduction = true,
devType = 'eval-source-map',
productionType = 'source-map'
) {
let type = devType;
if (Mix.inProduction()) {
type = generateForProduction ? productionType : false;
}
Config.sourcemaps = type;
return this;
}
/**
* Override the default path to your project's public directory.
*
* @param {string} defaultPath
*/
setPublicPath(defaultPath) {
Config.publicPath = path.normalize(defaultPath.replace(/\/$/, ''));
return this;
}
/**
* Set a prefix for all generated asset paths.
*
* @param {string} path
*/
setResourceRoot(path) {
Config.resourceRoot = path;
return this;
}
/**
* Merge custom config with the provided webpack.config file.
*
* @param {object} config
*/
webpackConfig(config) {
config = typeof config == 'function' ? config(webpack) : config;
Config.webpackConfig = require('webpack-merge').smart(
Config.webpackConfig,
config
);
return this;
}
/**
* Merge custom Babel config with Mix's default.
*
* @param {object} config
*/
babelConfig(config) {
Config.babelConfig = config;
return this;
}
/* Set Mix-specific options.
*
* @param {object} options
*/
options(options) {
Config.merge(options);
return this;
}
/**
* Register a Webpack build event handler.
*
* @param {Function} callback
*/
then(callback) {
Mix.listen('build', callback);
return this;
}
/**
* Helper for determining a production environment.
*/
inProduction() {
return Mix.inProduction();
}
}
module.exports = Api;
+81
View File
@@ -0,0 +1,81 @@
let assert = require('assert');
let Dependencies = require('./Dependencies');
let argv = require('yargs').argv;
class Assert {
/**
* Assert that the call the mix.js() is valid.
*
* @param {*} entry
* @param {*} output
*/
static js(entry, output) {
assert(
typeof entry === 'string' || Array.isArray(entry),
'mix.js() is missing required parameter 1: entry'
);
assert(
typeof output === 'string',
'mix.js() is missing required parameter 2: output'
);
}
/**
* Assert that the calls to mix.sass() and mix.less() are valid.
*
* @param {string} type
* @param {string} src
* @param {string} output
*/
static preprocessor(type, src, output) {
assert(
typeof src === 'string',
`mix.${type}() is missing required parameter 1: src`
);
assert(
typeof output === 'string',
`mix.${type}() is missing required parameter 2: output`
);
}
/**
* Assert that calls to mix.combine() are valid.
*
* @param {string} src
* @param {File} output
*/
static combine(src, output) {
assert(
output.isFile(),
'mix.combine() requires a full output file path as the second argument.'
);
}
/**
* Assert that the given file exists.
*
* @param {string} file
*/
static exists(file) {
assert(
File.exists(file),
`Whoops, you are trying to compile ${file}, but that file does not exist.`
);
}
/**
* Assert that the necessary dependencies are available.
*
* @param {Array} list
* @param {Boolean} abortOnComplete
*/
static dependencies(dependencies, abortOnComplete = false) {
if (argv['$0'].includes('ava')) return;
new Dependencies(dependencies).install(abortOnComplete);
}
}
module.exports = Assert;
+61
View File
@@ -0,0 +1,61 @@
let merge = require('babel-merge');
class BabelConfig {
/**
* Generate the appropriate Babel configuration for the build.
*
* @param {Object} mixBabelConfig
* @param {String} babelRcPath
*/
static generate(mixBabelConfig, babelRcPath) {
return merge.all(
[
BabelConfig.default(),
new BabelConfig().fetchBabelRc(babelRcPath),
mixBabelConfig
],
{
arrayMerge: (destinationArray, sourceArray, options) =>
sourceArray
}
);
}
/**
* Fetch the user's .babelrc config file, if any.
*
* @param {String} path
*/
fetchBabelRc(path) {
return File.exists(path) ? JSON.parse(File.find(path).read()) : {};
}
/**
* Fetch the default Babel configuration.
*/
static default() {
return {
cacheDirectory: true,
presets: [
[
'@babel/preset-env',
{
modules: false,
forceAllTransforms: true
}
]
],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
[
'@babel/plugin-transform-runtime',
{
helpers: false
}
]
]
};
}
}
module.exports = BabelConfig;
+103
View File
@@ -0,0 +1,103 @@
let childProcess = require('child_process');
let File = require('./File');
let Log = require('./Log');
let argv = require('yargs').argv;
class Dependencies {
/**
* Create a new Dependencies instance.
*
* @param {Object} dependencies
*/
constructor(dependencies) {
this.dependencies = dependencies;
}
/**
* Install all dependencies that aren't available.
*
* @param {Boolean} abortOnComplete
* @param {Boolean} forceNpm
*/
install(abortOnComplete = false, forceNpm = false) {
this.dependencies
.reject(dependency => {
try {
return require.resolve(
dependency.replace(/(?!^@)@.+$/, '')
);
} catch (e) {}
})
.tap(dependencies => {
this.execute(
this.buildInstallCommand(dependencies, forceNpm),
dependencies,
abortOnComplete
);
});
}
/**
* Execute the provided console command.
*
* @param {string} command
* @param {Boolean} abortOnComplete
*/
execute(command, dependencies, abortOnComplete) {
Log.feedback(
'Additional dependencies must be installed. This will only take a moment.'
);
Log.feedback(`Running: ${command}`);
childProcess.execSync(command);
Log.feedback(
'Okay, done. The following packages have been installed and saved to your package.json dependencies list:'
);
dependencies.forEach(d => Log.feedback('- ' + d));
this.respond(abortOnComplete);
}
/**
* Build the dependency install command.
*
* @param {Object} dependencies
* @param {Boolean} forceNpm
*/
buildInstallCommand(dependencies, forceNpm = false) {
dependencies = [].concat(dependencies).join(' ');
if (!forceNpm) {
try {
childProcess.execSync('command -v yarn >/dev/null');
return `yarn add ${dependencies} --dev --production=false`;
} catch (e) {}
}
return `npm install ${dependencies} --save-dev --production=false`;
}
/**
* Complete the install process.
*
* @param {Boolean} abortOnComplete
*/
respond(abortOnComplete) {
if (abortOnComplete) {
Log.feedback(
typeof abortOnComplete === 'string'
? abortOnComplete
: 'Finished. Please run Mix again.'
);
if (!argv['$0'].includes('ava')) {
process.exit();
}
}
}
}
module.exports = Dependencies;
+45
View File
@@ -0,0 +1,45 @@
class Dispatcher {
/**
* Create a new Dispatcher instance.
*/
constructor() {
this.events = {};
}
/**
* Listen for the given event.
*
* @param {string|Array} events
* @param {Function} handler
*/
listen(events, handler) {
events = [].concat(events);
events.forEach(event => {
this.events[event] = (this.events[event] || []).concat(handler);
});
return this;
}
/**
* Trigger all handlers for the given event.
*
* @param {string} event
* @param {*} data
*/
fire(event, data) {
if (!this.events[event]) return false;
this.events[event].forEach(handler => handler(data));
}
/**
* Fetch all registered event listeners.
*/
all() {
return this.events;
}
}
module.exports = Dispatcher;
+266
View File
@@ -0,0 +1,266 @@
let os = require('os');
let md5 = require('md5');
let path = require('path');
let fs = require('fs-extra');
let Terser = require('terser');
let UglifyCss = require('clean-css');
class File {
/**
* Create a new instance.
*
* @param {string} filePath
*/
constructor(filePath) {
this.absolutePath = path.resolve(filePath);
this.filePath = this.relativePath();
this.segments = this.parse();
}
/**
* Static constructor.
*
* @param {string} file
*/
static find(file) {
return new File(file);
}
/**
* Get the size of the file.
*/
size() {
return fs.statSync(this.path()).size;
}
/**
* Determine if the given file exists.
*
* @param {string} file
*/
static exists(file) {
return fs.existsSync(file);
}
/**
* Delete/Unlink the current file.
*/
delete() {
if (fs.existsSync(this.path())) {
fs.unlinkSync(this.path());
}
}
/**
* Get the name of the file.
*/
name() {
return this.segments.file;
}
/**
* Get the name of the file, minus the extension.
*/
nameWithoutExtension() {
return this.segments.name;
}
/**
* Get the extension of the file.
*/
extension() {
return this.segments.ext;
}
/**
* Get the absolute path to the file.
*/
path() {
return this.absolutePath;
}
/**
* Get the relative path to the file, from the project root.
*/
relativePath() {
return path.relative(Mix.paths.root(), this.path());
}
/**
* Get the absolute path to the file, minus the extension.
*/
pathWithoutExtension() {
return this.segments.pathWithoutExt;
}
/**
* Force the file's relative path to begin from the public path.
*
* @param {string|null} publicPath
*/
forceFromPublic(publicPath) {
publicPath = publicPath || Config.publicPath;
if (!this.relativePath().startsWith(publicPath)) {
return new File(path.join(publicPath, this.relativePath()));
}
return this;
}
/**
* Get the path to the file, starting at the project's public dir.
*
* @param {string|null} publicPath
*/
pathFromPublic(publicPath) {
publicPath = publicPath || Config.publicPath;
let extra = this.filePath.startsWith(publicPath) ? publicPath : '';
return this.path().replace(Mix.paths.root(extra), '');
}
/**
* Get the base directory of the file.
*/
base() {
return this.segments.base;
}
/**
* Determine if the file is a directory.
*/
isDirectory() {
return this.segments.isDir;
}
/**
* Determine if the path is a file, and not a directory.
*/
isFile() {
return this.segments.isFile;
}
/**
* Write the given contents to the file.
*
* @param {string} body
*/
write(body) {
if (typeof body === 'object') {
body = JSON.stringify(body, null, 4);
}
body = body + os.EOL;
fs.writeFileSync(this.absolutePath, body);
return this;
}
/**
* Read the file's contents.
*/
read() {
return fs.readFileSync(this.path(), 'utf8');
}
/**
* Calculate the proper version hash for the file.
*/
version() {
return md5(this.read()).substr(0, 20);
}
/**
* Create all nested directories.
*/
makeDirectories() {
fs.ensureDirSync(this.base());
return this;
}
/**
* Copy the current file to a new location.
*
* @param {string} destination
*/
copyTo(destination) {
fs.copySync(this.path(), destination);
return this;
}
/**
* Minify the file, if it is CSS or JS.
*/
minify() {
if (this.extension() === '.js') {
this.write(
Terser.minify(this.read(), Config.terser.terserOptions).code
);
}
if (this.extension() === '.css') {
this.write(
new UglifyCss(Config.cleanCss).minify(this.read()).styles
);
}
return this;
}
/**
* Rename the file.
*
* @param {string} to
*/
rename(to) {
to = path.join(this.base(), to);
fs.renameSync(this.path(), to);
return new File(to);
}
/**
* It can append to the current path.
*
* @param {string} append
*/
append(append) {
return new File(path.join(this.path(), append));
}
/**
* Determine if the file path contains the given text.
*
* @param {string} text
*/
contains(text) {
return this.path().includes(text);
}
/**
* Parse the file path.
*/
parse() {
let parsed = path.parse(this.absolutePath);
return {
path: this.filePath,
absolutePath: this.absolutePath,
pathWithoutExt: path.join(parsed.dir, `${parsed.name}`),
isDir: !parsed.ext && !parsed.name.endsWith('*'),
isFile: !!parsed.ext,
name: parsed.name,
ext: parsed.ext,
file: parsed.base,
base: parsed.dir
};
}
}
module.exports = File;
+110
View File
@@ -0,0 +1,110 @@
let concatenate = require('concatenate');
let babel = require('@babel/core');
let glob = require('glob');
let Log = require('./Log');
class FileCollection {
/**
* Create a new FileCollection instance.
*
* @param {Array|string} files
*/
constructor(files = []) {
this.files = [].concat(files);
}
/**
* Fetch the underlying files.
*/
get() {
return this.files;
}
/**
* Merge all files in the collection into one.
*
* @param {object} output
* @param {object} wantsBabel
*/
merge(output, wantsBabel = false) {
let contents = concatenate.sync(
this.files,
output.makeDirectories().path()
);
if (this.shouldCompileWithBabel(wantsBabel, output)) {
output.write(this.babelify(contents));
}
return new File(output.makeDirectories().path());
}
/**
* Determine if we should add a Babel pass to the concatenated file.
*
* @param {Boolean} wantsBabel
* @param {Object} output
*/
shouldCompileWithBabel(wantsBabel, output) {
return wantsBabel && output.extension() === '.js';
}
/**
* Apply Babel to the given contents.
*
* @param {string} contents
*/
babelify(contents) {
let babelConfig = Config.babel();
delete babelConfig.cacheDirectory;
return babel.transform(contents, babelConfig).code;
}
/**
* Copy the src files to the given destination.
*
* @param {string} destination
* @param {string|array|null} src
*/
copyTo(destination, src = this.files) {
this.assets = this.assets || [];
this.destination = destination;
if (Array.isArray(src)) {
src.forEach(file => this.copyTo(destination, new File(file)));
return;
}
if (src.isDirectory()) {
return src.copyTo(destination.path());
}
if (src.contains('*')) {
let files = glob.sync(src.path(), { nodir: true });
if (!files.length) {
Log.feedback(
`Notice: The ${src.path()} search produced no matches.`
);
}
return this.copyTo(destination, files);
}
if (destination.isDirectory()) {
destination = destination.append(src.name());
}
src.copyTo(destination.path());
this.assets = this.assets.concat(destination);
return destination.path();
}
}
module.exports = FileCollection;
+66
View File
@@ -0,0 +1,66 @@
class Log {
/**
* Log basic info to the console.
*
* @param {String} message
* @param {String} color
*/
static info(message, color = 'default') {
console.log(Log.colors()[color], message);
Log.reset();
}
/**
* Log feedback info to the console.
*
* @param {String} message
* @param {String} color
*/
static feedback(message, color = 'green') {
Log.line('\t' + message, color);
}
/**
* Log error info to the console.
*
* @param {String} message
* @param {String} color
*/
static error(message, color = 'red') {
Log.line(message, color);
}
/**
* Log a new line of info to the console.
*
* @param {String} message
* @param {String} color
*/
static line(message, color = 'default') {
Log.info(message, color);
}
/**
* Reset the default color for future console.logs.
*
* @param {String} message
* @param {String} color
*/
static reset() {
console.log(Log.colors()['default'], '');
}
/**
* The colors lookup table.
*/
static colors() {
return {
default: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m'
};
}
}
module.exports = Log;
+130
View File
@@ -0,0 +1,130 @@
let objectValues = require('lodash').values;
let without = require('lodash').without;
let path = require('path');
class Manifest {
/**
* Create a new Manifest instance.
*
* @param {string} name
*/
constructor(name = 'mix-manifest.json') {
this.manifest = {};
this.name = name;
}
/**
* Get the underlying manifest collection.
*/
get(file = null) {
if (file) {
return path.posix.join(
Config.publicPath,
this.manifest[this.normalizePath(file)]
);
}
return sortObjectKeys(this.manifest);
}
/**
* Add the given path to the manifest file.
*
* @param {string} filePath
*/
add(filePath) {
filePath = this.normalizePath(filePath);
let original = filePath.replace(/\?id=\w{20}/, '');
this.manifest[original] = filePath;
return this;
}
/**
* Add a new hashed key to the manifest.
*
* @param {string} file
*/
hash(file) {
let hash = new File(path.join(Config.publicPath, file)).version();
let filePath = this.normalizePath(file);
this.manifest[filePath] = filePath + '?id=' + hash;
return this;
}
/**
* Transform the Webpack stats into the shape we need.
*
* @param {object} stats
*/
transform(stats) {
this.flattenAssets(stats).forEach(this.add.bind(this));
return this;
}
/**
* Refresh the mix-manifest.js file.
*/
refresh() {
File.find(this.path())
.makeDirectories()
.write(this.manifest);
}
/**
* Retrieve the JSON output from the manifest file.
*/
read() {
return JSON.parse(File.find(this.path()).read());
}
/**
* Get the path to the manifest file.
*/
path() {
return path.join(Config.publicPath, this.name);
}
/**
* Flatten the generated stats assets into an array.
*
* @param {Object} stats
*/
flattenAssets(stats) {
let assets = Object.assign({}, stats.assetsByChunkName);
// If there's a temporary mix.js chunk, we can safely remove it.
if (assets.mix) {
assets.mix = without(assets.mix, 'mix.js');
}
return flatten(assets);
}
/**
* Prepare the provided path for processing.
*
* @param {string} filePath
*/
normalizePath(filePath) {
if (Config.publicPath && filePath.startsWith(Config.publicPath)) {
filePath = filePath.substring(Config.publicPath.length);
}
filePath = filePath.replace(/\\/g, '/');
if (!filePath.startsWith('/')) {
filePath = '/' + filePath;
}
return filePath;
}
}
module.exports = Manifest;
+123
View File
@@ -0,0 +1,123 @@
let Paths = require('./Paths');
let Manifest = require('./Manifest');
let Dispatcher = require('./Dispatcher');
let Components = require('./components/Components');
let isFunction = require('lodash').isFunction;
class Mix {
/**
* Create a new instance.
*/
constructor() {
this.paths = new Paths();
this.manifest = new Manifest();
this.dispatcher = new Dispatcher();
this.tasks = [];
this.bundlingJavaScript = false;
this.components = new Components();
}
/**
* Determine if the given config item is truthy.
*
* @param {string} tool
*/
isUsing(tool) {
return !!Config[tool];
}
/**
* Determine if Mix is executing in a production environment.
*/
inProduction() {
return Config.production;
}
/**
* Determine if Mix should watch files for changes.
*/
isWatching() {
return (
process.argv.includes('--watch') || process.argv.includes('--hot')
);
}
/**
* Determine if polling is used for file watching
*/
isPolling() {
return this.isWatching() && process.argv.includes('--watch-poll');
}
/**
* Determine if Mix sees a particular tool or framework.
*
* @param {string} tool
*/
sees(tool) {
if (tool === 'laravel') {
return File.exists('./artisan');
}
return false;
}
/**
* Determine if the given npm package is installed.
*
* @param {string} npmPackage
*/
seesNpmPackage(npmPackage) {
try {
require.resolve(npmPackage);
return true;
} catch (e) {
return false;
}
}
/**
* Determine if Mix should activate hot reloading.
*/
shouldHotReload() {
new File(path.join(Config.publicPath, 'hot')).delete();
return this.isUsing('hmr');
}
/**
* Queue up a new task.
*
* @param {Task} task
*/
addTask(task) {
this.tasks.push(task);
}
/**
* Listen for the given event.
*
* @param {string} event
* @param {Function} callback
*/
listen(event, callback) {
this.dispatcher.listen(event, callback);
}
/**
* Dispatch the given event.
*
* @param {string} event
* @param {*} data
*/
dispatch(event, data) {
if (isFunction(data)) {
data = data();
}
this.dispatcher.fire(event, data);
}
}
module.exports = Mix;
+45
View File
@@ -0,0 +1,45 @@
let argv = require('yargs').argv;
class Paths {
/**
* Create a new Paths instance.
*/
constructor() {
if (argv['$0'].includes('ava')) {
this.rootPath = path.resolve(__dirname, '../');
} else {
this.rootPath = process.cwd();
}
}
/**
* Set the root path to resolve webpack.mix.js.
*
* @param {string} path
*/
setRootPath(path) {
this.rootPath = path;
return this;
}
/**
* Determine the path to the user's webpack.mix.js file.
*/
mix() {
return this.root(
argv.env && argv.env.mixfile ? argv.env.mixfile : 'webpack.mix'
);
}
/**
* Determine the project root.
*
* @param {string|null} append
*/
root(append = '') {
return path.resolve(this.rootPath, append);
}
}
module.exports = Paths;
+140
View File
@@ -0,0 +1,140 @@
class Entry {
/**
* Create a new Entry instance.
*/
constructor() {
this.structure = {};
this.base = '';
}
/**
* Fetch the underlying entry structure.
*/
get() {
return this.structure;
}
/**
* Get the object keys for the structure.
*/
keys() {
return Object.keys(this.structure);
}
/**
* Add a key key-val pair to the structure.
*
* @param {string} key
* @param {mixed} val
*/
add(key, val) {
this.structure[key] = (this.structure[key] || []).concat(val);
return this;
}
/**
* Add a new key-val pair, based on a given output path.
*
* @param {mixed} val
* @param {Object} output
* @param {Object} fallback
*/
addFromOutput(val, output, fallback) {
output = this.normalizePath(output, fallback);
return this.add(this.createName(output), val);
}
/**
* Add a vendor extraction.
*
* @param {Object} extraction
*/
addExtraction(extraction) {
if (!Mix.bundlingJavaScript && !extraction.output) {
throw new Error(
'Please provide an output path as the second argument to mix.extract().'
);
}
let outputPath = extraction.output;
// If the extraction output path begins with a slash (forward or backward)
// then the path from the public directory determined below will be wrong
// on Windows, as a drive letter will be incorrectly prepended to
// (e.g. '/dist/vendor' -> 'C:\dist\vendor').
let startsWithSlash = ['\\', '/'].indexOf(outputPath[0]) >= 0;
outputPath = startsWithSlash ? outputPath.substr(1) : outputPath;
let vendorPath = outputPath
? new File(outputPath)
.pathFromPublic(Config.publicPath)
.replace(/\.js$/, '')
.replace(/\\/g, '/')
: path.join(this.base, 'vendor').replace(/\\/g, '/');
this.add(vendorPath, extraction.libs);
return vendorPath;
}
/**
* Add a default entry script to the structure.
*/
addDefault() {
this.add(
'mix',
new File(path.resolve(__dirname, 'mock-entry.js')).path()
);
}
/**
* Build the proper entry name, based on a given output.
*
* @param {Object} output
*/
createName(output) {
let name = output
.pathFromPublic(Config.publicPath)
.replace(/\.js$/, '')
.replace(/\\/g, '/');
this.base = path.parse(name).dir;
return name;
}
/**
* Normalize the given output path.
*
* @param {Object} output
* @param {Object} fallback
*/
normalizePath(output, fallback) {
// All output paths need to start at the project's public dir.
let pathFromPublicDir = output.pathFromPublic();
if (
!pathFromPublicDir.startsWith('/' + Config.publicPath) &&
!pathFromPublicDir.startsWith('\\' + Config.publicPath)
) {
output = new File(
path.join(Config.publicPath, output.pathFromPublic())
);
}
// If the output points to a directory, we'll grab a file name from the fallback src.
if (output.isDirectory()) {
output = new File(
path.join(
output.filePath,
fallback.nameWithoutExtension() + '.js'
)
);
}
return output;
}
}
module.exports = Entry;
+129
View File
@@ -0,0 +1,129 @@
let webpack = require('webpack');
let Entry = require('./Entry');
let webpackRules = require('./webpack-rules');
let webpackPlugins = require('./webpack-plugins');
let webpackDefaultConfig = require('./webpack-default');
process.noDeprecation = true;
class WebpackConfig {
/**
* Create a new instance.
*/
constructor() {
this.webpackConfig = webpackDefaultConfig();
}
/**
* Build the Webpack configuration object.
*/
build() {
this.buildEntry()
.buildOutput()
.buildRules()
.buildPlugins()
.buildResolving()
.mergeCustomConfig();
Mix.dispatch('configReady', this.webpackConfig);
return this.webpackConfig;
}
/**
* Build the entry object.
*/
buildEntry() {
let entry = new Entry();
if (!Mix.bundlingJavaScript) {
entry.addDefault();
}
Mix.dispatch('loading-entry', entry);
this.webpackConfig.entry = entry.get();
return this;
}
/**
* Build the output object.
*/
buildOutput() {
let http = process.argv.includes('--https') ? 'https' : 'http';
if (Mix.isUsing('hmr')) {
this.webpackConfig.devServer.host = Config.hmrOptions.host;
this.webpackConfig.devServer.port = Config.hmrOptions.port;
}
this.webpackConfig.output = {
path: path.resolve(Mix.isUsing('hmr') ? '/' : Config.publicPath),
filename: '[name].js',
chunkFilename: '[name].js',
publicPath: Mix.isUsing('hmr')
? `${http}://${Config.hmrOptions.host}:${
Config.hmrOptions.port
}/`
: '/'
};
return this;
}
/**
* Build the rules array.
*/
buildRules() {
this.webpackConfig.module.rules = this.webpackConfig.module.rules.concat(
webpackRules()
);
Mix.dispatch('loading-rules', this.webpackConfig.module.rules);
return this;
}
/**
* Build the plugins array.
*/
buildPlugins() {
this.webpackConfig.plugins = this.webpackConfig.plugins.concat(
webpackPlugins()
);
Mix.dispatch('loading-plugins', this.webpackConfig.plugins);
return this;
}
/**
* Build the resolve object.
*/
buildResolving() {
this.webpackConfig.resolve = {
extensions: ['*', '.wasm', '.mjs', '.js', '.jsx', '.json', '.vue'],
alias: {
vue$: 'vue/dist/vue.common.js'
}
};
return this;
}
/**
* Merge the user's custom Webpack configuration.
*/
mergeCustomConfig() {
if (Config.webpackConfig) {
this.webpackConfig = require('webpack-merge').smart(
this.webpackConfig,
Config.webpackConfig
);
}
}
}
module.exports = WebpackConfig;
View File
+64
View File
@@ -0,0 +1,64 @@
let TerserPlugin = require('terser-webpack-plugin');
let OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = function() {
return {
context: Mix.paths.root(),
mode: Mix.inProduction() ? 'production' : 'development',
entry: {},
output: {},
module: { rules: [] },
plugins: [],
stats: {
hash: false,
version: false,
timings: false,
children: false,
errorDetails: false,
entrypoints: false,
performance: Mix.inProduction(),
chunks: false,
modules: false,
reasons: false,
source: false,
publicPath: false,
builtAt: false
},
performance: {
hints: false
},
optimization: Mix.inProduction()
? {
minimizer: [
new TerserPlugin(Config.terser),
new OptimizeCSSAssetsPlugin({
cssProcessorPluginOptions: {
preset: ['default', Config.cssNano]
}
})
]
}
: {},
devtool: Config.sourcemaps,
devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
},
contentBase: path.resolve(Config.publicPath),
historyApiFallback: true,
noInfo: true,
compress: true,
quiet: true
}
};
};
+60
View File
@@ -0,0 +1,60 @@
let webpack = require('webpack');
let FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
let MixDefinitionsPlugin = require('../webpackPlugins/MixDefinitionsPlugin');
let BuildCallbackPlugin = require('../webpackPlugins/BuildCallbackPlugin');
let CustomTasksPlugin = require('../webpackPlugins/CustomTasksPlugin');
let ManifestPlugin = require('../webpackPlugins/ManifestPlugin');
let MockEntryPlugin = require('../webpackPlugins/MockEntryPlugin');
module.exports = function() {
let plugins = [];
// If the user didn't declare any JS compilation, we still need to
// use a temporary script to force a compile. This plugin will
// handle the process of deleting the compiled script.
if (!Mix.bundlingJavaScript) {
plugins.push(new MockEntryPlugin());
}
// Activate better error feedback in the console.
plugins.push(
new FriendlyErrorsWebpackPlugin({
clearConsole: Config.clearConsole
})
);
// Activate support for Mix_ .env definitions.
plugins.push(
MixDefinitionsPlugin.build({
NODE_ENV: Mix.inProduction()
? 'production'
: process.env.NODE_ENV || 'development'
})
);
// Add some general Webpack loader options.
plugins.push(
new webpack.LoaderOptionsPlugin({
minimize: Mix.isUsing('purifyCss') ? false : Mix.inProduction(),
options: {
context: __dirname,
output: {
path: './'
}
}
})
);
// Handle all custom, non-webpack tasks.
plugins.push(new ManifestPlugin());
// Handle all custom, non-webpack tasks.
plugins.push(new CustomTasksPlugin());
// Notify the rest of our app when Webpack has finished its build.
plugins.push(
new BuildCallbackPlugin(stats => Mix.dispatch('build', stats))
);
return plugins;
};
+86
View File
@@ -0,0 +1,86 @@
module.exports = function() {
let rules = [];
// Add support for loading HTML files.
rules.push({
test: /\.html$/,
loaders: ['html-loader']
});
// Add support for loading images.
rules.push({
// only include svg that doesn't have font in the path or file name by using negative lookahead
test: /(\.(png|jpe?g|gif|webp)$|^((?!font).)*\.svg$)/,
loaders: [
{
loader: 'file-loader',
options: {
name: path => {
if (!/node_modules|bower_components/.test(path)) {
return (
Config.fileLoaderDirs.images +
'/[name].[ext]?[hash]'
);
}
return (
Config.fileLoaderDirs.images +
'/vendor/' +
path
.replace(/\\/g, '/')
.replace(
/((.*(node_modules|bower_components))|images|image|img|assets)\//g,
''
) +
'?[hash]'
);
},
publicPath: Config.resourceRoot
}
},
{
loader: 'img-loader',
options: Config.imgLoaderOptions
}
]
});
// Add support for loading fonts.
rules.push({
test: /(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)/,
loader: 'file-loader',
options: {
name: path => {
if (!/node_modules|bower_components/.test(path)) {
return Config.fileLoaderDirs.fonts + '/[name].[ext]?[hash]';
}
return (
Config.fileLoaderDirs.fonts +
'/vendor/' +
path
.replace(/\\/g, '/')
.replace(
/((.*(node_modules|bower_components))|fonts|font|assets)\//g,
''
) +
'?[hash]'
);
},
publicPath: Config.resourceRoot
}
});
// Add support for loading cursor files.
rules.push({
test: /\.(cur|ani)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]',
publicPath: Config.resourceRoot
}
});
return rules;
};
+32
View File
@@ -0,0 +1,32 @@
class Autoload {
/**
* Register the component.
*
* @param {Object} libs
* @return {void}
*/
register(libs) {
let aliases = {};
Object.keys(libs).forEach(library => {
[].concat(libs[library]).forEach(alias => {
aliases[alias] = library.includes('.')
? library.split('.')
: library;
});
});
this.aliases = aliases;
}
/**
* webpack plugins to be appended to the master config.
*/
webpackPlugins() {
let webpack = require('webpack');
return new webpack.ProvidePlugin(this.aliases);
}
}
module.exports = Autoload;
+10
View File
@@ -0,0 +1,10 @@
class AutomaticComponent {
/**
* Create a new component instance.
*/
constructor() {
this.passive = true;
}
}
module.exports = AutomaticComponent;
+66
View File
@@ -0,0 +1,66 @@
class Browsersync {
/**
* The API name for the component.
*/
name() {
return 'browserSync';
}
/**
* Required dependencies for the component.
*/
dependencies() {
this.requiresReload = true;
return ['browser-sync', 'browser-sync-webpack-plugin@2.0.1'];
}
/**
* Register the component.
*
* @param {Object} userConfig
*/
register(userConfig) {
this.userConfig =
typeof userConfig == 'string' ? { proxy: userConfig } : userConfig;
}
/**
* webpack plugins to be appended to the master config.
*/
webpackPlugins() {
let BrowserSyncPlugin = require('browser-sync-webpack-plugin');
return new BrowserSyncPlugin(this.config(), { reload: false });
}
/**
* Build the BrowserSync configuration.
*/
config() {
return Object.assign(
{
host: 'localhost',
port: 3000,
proxy: 'app.test',
files: [
'app/**/*.php',
'resources/views/**/*.php',
'public/js/**/*.js',
'public/css/**/*.css'
],
snippetOptions: {
rule: {
match: /(<\/body>|<\/pre>)/i,
fn: function(snippet, match) {
return snippet + match;
}
}
}
},
this.userConfig
);
}
}
module.exports = Browsersync;
+24
View File
@@ -0,0 +1,24 @@
let JavaScript = require('./JavaScript');
class Coffee extends JavaScript {
/**
* Required dependencies for the component.
*/
dependencies() {
return ['coffee-loader', 'coffeescript'].concat();
}
/**
* webpack rules to be appended to the master config.
*/
webpackRules() {
return [
{
test: /\.coffee$/,
use: ['coffee-loader']
}
].concat(super.webpackRules());
}
}
module.exports = Coffee;
+54
View File
@@ -0,0 +1,54 @@
let ConcatFilesTask = require('../tasks/ConcatenateFilesTask');
let Assert = require('../Assert');
let _ = require('lodash');
let glob = require('glob');
class Combine {
/**
* The API name for the component.
*/
name() {
return ['combine', 'scripts', 'babel', 'styles', 'minify'];
}
/**
*
* Register the component.
*
* @param {*} src
* @param {string} output
* @param {Boolean} babel
*/
register(src, output = '', babel = false) {
if (this.caller === 'babel') {
babel = true;
}
if (this.caller === 'minify') {
if (Array.isArray(src)) {
src.forEach(file => this.register(file));
return this;
}
output = src.replace(/\.([a-z]{2,})$/i, '.min.$1');
}
output = new File(output);
Assert.combine(src, output);
if (typeof src === 'string' && File.find(src).isDirectory()) {
src = _.pull(
glob.sync(path.join(src, '**/*'), { nodir: true }),
output.relativePath()
);
}
let task = new ConcatFilesTask({ src, output, babel });
Mix.addTask(task);
}
}
module.exports = Combine;
+173
View File
@@ -0,0 +1,173 @@
let mix = require('../index');
let Assert = require('../Assert');
let webpackMerge = require('webpack-merge');
let components = [
'JavaScript',
'Preact',
'React',
'Coffee',
'TypeScript',
'Less',
'Sass',
'Stylus',
'PostCss',
'Css',
'Browsersync',
'Combine',
'Copy',
'Autoload',
'Version',
'Extend',
'Extract',
'Notifications',
'DisableNotifications',
'PurifyCss',
'DumpWebpackConfig'
];
class ComponentFactory {
/**
* Install all default components.
*/
installAll() {
components
.map(name => require(`./${name}`))
.forEach(this.install.bind(this));
}
/**
* Install a component.
*
* @param {Component} Component
*/
install(Component) {
let component =
typeof Component === 'function' ? new Component() : Component;
this.registerComponent(component);
Mix.listen('init', () => {
if (!component.activated && !component.passive) {
return;
}
component.dependencies && this.installDependencies(component);
component.boot && component.boot();
component.babelConfig && this.applyBabelConfig(component);
Mix.listen('loading-entry', entry => {
if (component.webpackEntry) {
component.webpackEntry(entry);
}
});
Mix.listen('loading-rules', rules => {
component.webpackRules && this.applyRules(rules, component);
});
Mix.listen('loading-plugins', plugins => {
component.webpackPlugins &&
this.applyPlugins(plugins, component);
});
Mix.listen('configReady', config => {
component.webpackConfig && component.webpackConfig(config);
});
});
}
/**
* Register the component.
*
* @param {Object} component
*/
registerComponent(component) {
[]
.concat(
typeof component.name === 'function'
? component.name()
: component.constructor.name.toLowerCase()
)
.forEach(name => {
mix[name] = (...args) => {
Mix.components.record(name, component);
component.caller = name;
component.register && component.register(...args);
component.activated = true;
return mix;
};
// If we're dealing with a passive component that doesn't
// need to be explicitly triggered by the user, we'll
// call it now.
if (component.passive) {
mix[name]();
}
// Components can optionally write to the Mix API directly.
if (component.mix) {
Object.keys(component.mix()).forEach(name => {
mix[name] = component.mix()[name];
});
}
});
}
/**
* Install the component's dependencies.
*
* @param {Object} component
*/
installDependencies(component) {
[]
.concat(component.dependencies())
.filter(dependency => dependency)
.tap(dependencies => {
Assert.dependencies(dependencies, component.requiresReload);
});
}
/**
*
* Apply the Babel configuration for the component.
*
* @param {Object} component
*/
applyBabelConfig(component) {
Config.babelConfig = webpackMerge.smart(
Config.babelConfig,
component.babelConfig()
);
}
/**
*
* Apply the webpack rules for the component.
*
* @param {Object} component
*/
applyRules(rules, component) {
tap(component.webpackRules(), newRules => {
newRules && rules.push(...[].concat(newRules));
});
}
/**
*
* Apply the webpack plugins for the component.
*
* @param {Object} component
*/
applyPlugins(plugins, component) {
tap(component.webpackPlugins(), newPlugins => {
newPlugins && plugins.push(...[].concat(newPlugins));
});
}
}
module.exports = ComponentFactory;
+43
View File
@@ -0,0 +1,43 @@
class Components {
/**
* Create a new Components instance.
*/
constructor() {
this.components = [];
}
/**
* Record a newly registered component.
*
* @param {string} name
* @param {Component} component
*/
record(name, component) {
this.components[name] = component;
}
/**
* Retrieve a recorded component.
*
* @param {string} name
*/
get(name) {
return this.components[name];
}
/**
* Determine if the given component name has been registered.
*/
has(name) {
return this.components.hasOwnProperty(name);
}
/**
* Retrieve all components.
*/
all() {
return this.components;
}
}
module.exports = Components;
+22
View File
@@ -0,0 +1,22 @@
let CopyFilesTask = require('../tasks/CopyFilesTask');
class Copy {
/**
* The API name for the component.
*/
name() {
return ['copy', 'copyDirectory'];
}
/**
* Register the component.
*
* @param {*} from
* @param {string} to
*/
register(from, to) {
Mix.addTask(new CopyFilesTask({ from, to: new File(to) }));
}
}
module.exports = Copy;
+125
View File
@@ -0,0 +1,125 @@
let AutomaticComponent = require('./AutomaticComponent');
class Css extends AutomaticComponent {
/**
* webpack rules to be appended to the master config.
*/
webpackRules() {
return [
{
test: /\.css$/,
loaders: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
{
loader: 'postcss-loader',
options: this.postCssOptions()
}
]
},
{
test: /\.scss$/,
exclude: this.excludePathsFor('sass'),
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: this.postCssOptions()
},
{
loader: 'sass-loader',
options: {
precision: 8,
outputStyle: 'expanded'
}
}
]
},
{
test: /\.sass$/,
exclude: this.excludePathsFor('sass'),
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: this.postCssOptions()
},
{
loader: 'sass-loader',
options: {
precision: 8,
outputStyle: 'expanded',
indentedSyntax: true
}
}
]
},
{
test: /\.less$/,
exclude: this.excludePathsFor('less'),
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: this.postCssOptions()
},
'less-loader'
]
},
{
test: /\.styl(us)?$/,
exclude: this.excludePathsFor('stylus'),
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: this.postCssOptions()
},
'stylus-loader'
]
}
];
}
/**
* Paths to be excluded from the loader.
*
* @param {string} preprocessor
*/
excludePathsFor(preprocessor) {
let exclusions = Mix.components.get(preprocessor);
return exclusions
? exclusions.details.map(preprocessor => preprocessor.src.path())
: [];
}
/**
* Fetch the appropriate postcss plugins for the compile.
*/
postCssOptions() {
if (Mix.components.get('postCss')) {
return {
plugins: Mix.components.get('postCss').details[0].postCssPlugins
};
}
// If the user has a postcss.config.js file in their project root,
// postcss-loader will automatically read and fetch the plugins.
if (File.exists(Mix.paths.root('postcss.config.js'))) {
return {};
}
return { plugins: Config.postCss };
}
}
module.exports = Css;
+24
View File
@@ -0,0 +1,24 @@
class DisableNotifications {
/**
* The API name for the component.
*/
name() {
return ['disableNotifications', 'disableSuccessNotifications'];
}
/**
* Register the component.
*/
register() {
if (this.caller === 'disableSuccessNotifications') {
Config.notifications = {
onSuccess: false,
onFailure: true
};
} else {
Config.notifications = false;
}
}
}
module.exports = DisableNotifications;
+26
View File
@@ -0,0 +1,26 @@
let Log = require('../Log');
class DumpWebpackConfig {
/**
* The optional name to be used when called by Mix.
* Defaults to the class name, lowercased.
*/
name() {
return ['dumpWebpackConfig', 'dump'];
}
/**
* Register the component.
*/
register() {
Mix.listen('configReady', config => {
RegExp.prototype.toJSON = function() {
return this.toString();
};
Log.info(JSON.stringify(config, null, 2));
});
}
}
module.exports = DumpWebpackConfig;
+122
View File
@@ -0,0 +1,122 @@
/**
* This file represents an example component interface
* for Mix. All new components can be "inserted" into
* Mix, like so:
*
* // webpack.mix.js
*
* mix.extend('foo', new Example());
*
* mix.foo();
*/
class Example {
/**
* The optional name to be used when called by Mix.
* Defaults to the class name, lowercased.
*
* Ex: mix.example();
*
* @return {String|Array}
*/
name() {
// Example:
// return 'example';
// return ['example', 'alias'];
}
/**
* All dependencies that should be installed by Mix.
*
* @return {Array}
*/
dependencies() {
// Example:
// return ['typeScript', 'ts'];
}
/**
* Register the component.
*
* When your component is called, all user parameters
* will be passed to this method.
*
* Ex: register(src, output) {}
* Ex: mix.yourPlugin('src/path', 'output/path');
*
* @param {*} ...params
* @return {void}
*
*/
register() {
// Example:
// this.config = { proxy: arg };
}
/**
* Boot the component. This method is triggered after the
* user's webpack.mix.js file has executed.
*/
boot() {
// Example:
// if (Config.options.foo) {}
}
/**
* Append to the master Mix webpack entry object.
*
* @param {Entry} entry
* @return {void}
*/
webpackEntry(entry) {
// Example:
// entry.add('foo', 'bar');
}
/**
* Rules to be merged with the master webpack loaders.
*
* @return {Array|Object}
*/
webpackRules() {
// Example:
// return {
// test: /\.less$/,
// loaders: ['...']
// });
}
/*
* Plugins to be merged with the master webpack config.
*
* @return {Array|Object}
*/
webpackPlugins() {
// Example:
// return new webpack.ProvidePlugin(this.aliases);
}
/**
* Override the generated webpack configuration.
*
* @param {Object} webpackConfig
* @return {void}
*/
webpackConfig(webpackConfig) {
// Example:
// webpackConfig.resolve.extensions.push('.ts', '.tsx');
}
/**
* Babel config to be merged with Mix's defaults.
*
* @return {Object}
*/
babelConfig() {
// Example:
// return { presets: ['@babel/preset-react'] };
}
}
// Usage:
// mix.extend('example', new Example());
+31
View File
@@ -0,0 +1,31 @@
let ComponentFactory = require('./ComponentFactory');
class Extend {
/**
* Register the component.
*
* @param {string} name
* @param {Component} component
*/
register(name, component) {
if (typeof component !== 'function') {
component.name = () => name;
return new ComponentFactory().install(component);
}
new ComponentFactory().install({
name: () => name,
register(...args) {
this.args = args;
},
webpackConfig(config) {
component.call(this, config, ...this.args);
}
});
}
}
module.exports = Extend;
+126
View File
@@ -0,0 +1,126 @@
let webpackMerge = require('webpack-merge');
class Extract {
/**
* Create a new component instance.
*/
constructor() {
this.entry = null;
this.extractions = [];
}
/**
* The name of the component.
*
* mix.extract() or mix.extractVendor()
*/
name() {
return ['extract', 'extractVendors'];
}
/**
* Register the component.
*
* @param {*} libs
* @param {string} output
*/
register(libs = [], output) {
// If the user provides an output path as the first argument, they probably
// want to extract all node_module libraries to the specified file.
if (
arguments.length === 1 &&
typeof libs === 'string' &&
libs.endsWith('.js')
) {
output = libs;
libs = [];
}
this.extractions.push({ libs, output });
}
/**
* Assets to append to the webpack entry.
*
* @param {Entry} entry
*/
webpackEntry(entry) {
this.entry = entry;
this.extractions.forEach(extraction => {
extraction.output = this.extractionPath(extraction.output);
if (extraction.libs.length) {
this.entry.addExtraction(extraction);
}
});
}
webpackConfig(config) {
const newConfig = webpackMerge.smart(config, this.config());
config.optimization = newConfig.optimization;
}
config() {
return {
optimization: {
// If we are extracting vendor libraries, then we also need
// to extract Webpack's manifest file to assist with caching.
runtimeChunk: {
name: path
.join(this.entry.base, 'manifest')
.replace(/\\/g, '/')
},
splitChunks: this.createSplitChunks()
}
};
}
createSplitChunks() {
let config = { cacheGroups: {} };
for (const [index, extraction] of this.extractions.entries()) {
if (extraction.libs.length) {
config.cacheGroups[`vendor${index}`] = this.createCacheGroup(
extraction
);
}
}
// If the user didn't specify any libraries to extract,
// they likely want to extract all vendor libraries.
if (Object.keys(config.cacheGroups).length === 0) {
config.chunks = 'all';
config.name = this.extractions[0].output;
}
return config;
}
createCacheGroup(extraction) {
const libsPattern = extraction.libs.join('|');
const pattern = new RegExp(`(?<!node_modules.*)[\\\\/]node_modules[\\\\/](${libsPattern})[\\\\/]`, 'i');
return {
test: pattern,
name: extraction.output,
chunks: 'all',
enforce: true
};
}
extractionPath(outputPath) {
if (outputPath) {
return new File(outputPath)
.pathFromPublic(Config.publicPath)
.replace(/\.js$/, '')
.replace(/\\/g, '/');
}
return path.join(this.entry.base, 'vendor').replace(/\\/g, '/');
}
}
module.exports = Extract;
+97
View File
@@ -0,0 +1,97 @@
let glob = require('glob');
let Assert = require('../Assert');
let Vue = require('./Vue');
class JavaScript {
constructor() {
this.vue = new Vue();
this.toCompile = [];
JavaScript.vueWebpackConfigApplied = false;
}
/**
* The API name for the component.
*/
name() {
let name = this.constructor.name.toLowerCase();
return name === 'javascript' ? ['js', 'vue'] : name;
}
/**
* Required dependencies for the component.
*/
dependencies() {
return this.vue.dependencies();
}
/**
* Register the component.
*
* @param {*} entry
* @param {string} output
*/
register(entry, output) {
if (typeof entry === 'string' && entry.includes('*')) {
entry = glob.sync(entry);
}
Assert.js(entry, output);
entry = [].concat(entry).map(file => new File(file));
output = new File(output);
this.toCompile.push({ entry, output });
Mix.bundlingJavaScript = true;
}
/**
* Assets to append to the webpack entry.
*
* @param {Entry} entry
*/
webpackEntry(entry) {
this.toCompile.forEach(js => {
entry.addFromOutput(
js.entry.map(file => file.path()),
js.output,
js.entry[0]
);
});
}
/**
* webpack rules to be appended to the master config.
*/
webpackRules() {
return [].concat([
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: Config.babel()
}
]
}
]);
}
/**
* Override the generated webpack configuration.
*
* @param {Object} webpackConfig
*/
webpackConfig(webpackConfig) {
if (!JavaScript.vueWebpackConfigApplied) {
this.vue.webpackConfig(webpackConfig);
}
JavaScript.vueWebpackConfigApplied = true;
}
}
module.exports = JavaScript;
+30
View File
@@ -0,0 +1,30 @@
let Preprocessor = require('./Preprocessor');
class Less extends Preprocessor {
/**
* Required dependencies for the component.
*/
dependencies() {
return ['less-loader', 'less'];
}
/**
* Register the component.
*
* @param {*} src
* @param {string} output
* @param {Object} pluginOptions
* @param {Array} postCssPlugins
*/
register(src, output, pluginOptions = {}, postCssPlugins = []) {
return this.preprocess(
'less',
src,
output,
pluginOptions,
postCssPlugins
);
}
}
module.exports = Less;
+27
View File
@@ -0,0 +1,27 @@
let AutomaticComponent = require('./AutomaticComponent');
class Notifications extends AutomaticComponent {
/**
* webpack plugins to be appended to the master config.
*/
webpackPlugins() {
if (Mix.isUsing('notifications')) {
let WebpackNotifierPlugin = require('webpack-notifier');
return new WebpackNotifierPlugin({
title: 'Laravel Mix',
alwaysNotify: Config.notifications.onSuccess,
hint:
process.platform === 'linux'
? 'int:transient:1'
: undefined,
timeout: 2,
contentImage: Mix.paths.root(
'node_modules/laravel-mix/icons/laravel.png'
)
});
}
}
}
module.exports = Notifications;
+49
View File
@@ -0,0 +1,49 @@
let Assert = require('../Assert');
let Preprocessor = require('./Preprocessor');
class PostCss extends Preprocessor {
/**
* The API name for the component.
*/
name() {
return 'postCss';
}
/**
* Register the component.
*
* @param {*} src
* @param {string} output
* @param {Array} postCssPlugins
*/
register(src, output, postCssPlugins = []) {
Assert.preprocessor('postCss', src, output);
src = new File(src);
output = this.normalizeOutput(
new File(output),
src.nameWithoutExtension() + '.css'
);
this.details = (this.details || []).concat({
type: 'postCss',
src,
output,
postCssPlugins: [].concat(postCssPlugins)
});
}
/**
* Override the generated webpack configuration.
*
* @param {Object} config
*/
webpackConfig(config) {
config.module.rules.find(
rule => rule.test.toString() === '/\\.css$/'
).exclude = this.details.map(postCss => postCss.src.path());
}
}
module.exports = PostCss;
+21
View File
@@ -0,0 +1,21 @@
let JavaScript = require('./JavaScript');
class Preact extends JavaScript {
/**
* Required dependencies for the component.
*/
dependencies() {
return ['babel-preset-preact'].concat(super.dependencies());
}
/**
* Babel config to be merged with Mix's defaults.
*/
babelConfig() {
return {
presets: ['preact']
};
}
}
module.exports = Preact;
+183
View File
@@ -0,0 +1,183 @@
let Assert = require('../Assert');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
class Preprocessor {
/**
* Assets to append to the webpack entry.
*
* @param {Entry} entry
*/
webpackEntry(entry) {
this.details.forEach(detail => {
entry.add(entry.keys()[0], detail.src.path());
});
}
/**
* webpack rules to be appended to the master config.
*/
webpackRules() {
let rules = [];
this.details.forEach((preprocessor, index) => {
let outputPath = preprocessor.output.filePath
.replace(Config.publicPath + path.sep, path.sep)
.replace(/\\/g, '/');
tap(new ExtractTextPlugin(outputPath), extractPlugin => {
let loaders = [
{
loader: 'css-loader',
options: {
url: Config.processCssUrls,
sourceMap: Mix.isUsing('sourcemaps'),
importLoaders: 1
}
},
{
loader: 'postcss-loader',
options: {
sourceMap:
preprocessor.type === 'sass' &&
Config.processCssUrls
? true
: Mix.isUsing('sourcemaps'),
ident: `postcss${index}`,
plugins: (function() {
let plugins = Config.postCss;
if (
preprocessor.postCssPlugins &&
preprocessor.postCssPlugins.length
) {
plugins = preprocessor.postCssPlugins;
}
if (
Config.autoprefixer &&
Config.autoprefixer.enabled
) {
plugins.push(
require('autoprefixer')(
Config.autoprefixer.options
)
);
}
return plugins;
})()
}
}
];
if (preprocessor.type === 'sass' && Config.processCssUrls) {
loaders.push({
loader: 'resolve-url-loader',
options: {
sourceMap: true,
root: Mix.paths.root('node_modules')
}
});
}
if (preprocessor.type !== 'postCss') {
loaders.push({
loader: `${preprocessor.type}-loader`,
options: this.loaderOptions(preprocessor)
});
}
rules.push({
test: preprocessor.src.path(),
use: Mix.isUsing('hmr')
? ['style-loader', ...loaders]
: extractPlugin.extract({
fallback: 'style-loader',
use: loaders
})
});
this.extractPlugins = (this.extractPlugins || []).concat(
extractPlugin
);
});
});
return rules;
}
/**
* webpack plugins to be appended to the master config.
*/
webpackPlugins() {
return this.extractPlugins;
}
/**
* Prepare the preprocessor plugin options.
*
* @param {Object} preprocessor
*/
loaderOptions(preprocessor) {
tap(preprocessor.pluginOptions.implementation, implementation => {
if (typeof implementation === 'function') {
preprocessor.pluginOptions.implementation = implementation();
}
});
return Object.assign(preprocessor.pluginOptions, {
sourceMap:
preprocessor.type === 'sass' && Config.processCssUrls
? true
: Mix.isUsing('sourcemaps')
});
}
/**
* Register a generic CSS preprocessor.
*
* @param {string} type
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
* @param {Array} postCssPlugins
*/
preprocess(type, src, output, pluginOptions = {}, postCssPlugins = []) {
Assert.preprocessor(type, src, output);
src = new File(src);
output = this.normalizeOutput(
new File(output),
src.nameWithoutExtension() + '.css'
);
this.details = (this.details || []).concat({
type: this.constructor.name.toLowerCase(),
src,
output,
pluginOptions,
postCssPlugins
});
return this;
}
/**
* Generate a full output path, using a fallback
* file name, if a directory is provided.
*
* @param {Object} output
* @param {Object} fallbackName
*/
normalizeOutput(output, fallbackName) {
if (output.isDirectory()) {
output = new File(path.join(output.filePath, fallbackName));
}
return output;
}
}
module.exports = Preprocessor;
+58
View File
@@ -0,0 +1,58 @@
let AutomaticComponent = require('./AutomaticComponent');
let glob = require('glob');
class PurifyCss extends AutomaticComponent {
/**
* Required dependencies for the component.
*/
dependencies() {
if (Config.purifyCss) {
this.requiresReload = true;
return ['purifycss-webpack', 'purify-css'];
}
}
/**
* Override the generated webpack configuration.
*
* @param {Object} config
*/
webpackConfig(config) {
if (Config.purifyCss) {
Config.purifyCss = this.build(Config.purifyCss);
let CssPurifierPlugin = require('../webpackPlugins/CssPurifierPlugin');
config.plugins.push(CssPurifierPlugin.build());
}
}
/**
* Build the CSSPurifier plugin options.
*
* @param {Object} options
*/
build(options) {
if (typeof options === 'object') {
if (options.paths) {
let paths = options.paths;
paths.forEach(path => {
if (!path.includes('*')) return;
options.paths.splice(paths.indexOf(path), 1);
options.paths = paths.concat(glob.sync(path));
});
}
options.minimize = options.hasOwnProperty('minimize')
? options.minimize
: Mix.inProduction();
}
return options;
}
}
module.exports = PurifyCss;
+21
View File
@@ -0,0 +1,21 @@
let JavaScript = require('./JavaScript');
class React extends JavaScript {
/**
* Required dependencies for the component.
*/
dependencies() {
return ['@babel/preset-react'];
}
/**
* Babel config to be merged with Mix's defaults.
*/
babelConfig() {
return {
presets: ['@babel/preset-react']
};
}
}
module.exports = React;
+60
View File
@@ -0,0 +1,60 @@
let Preprocessor = require('./Preprocessor');
class Sass extends Preprocessor {
/**
* Required dependencies for the component.
*/
dependencies() {
this.requiresReload = true;
return tap(
[
'sass-loader@7.*',
Mix.seesNpmPackage('node-sass') ? 'node-sass' : 'sass'
],
dependencies => {
if (Config.processCssUrls) {
dependencies.push('resolve-url-loader@2.3.1');
}
}
);
}
/**
* Register the component.
*
* @param {*} src
* @param {string} output
* @param {Object} pluginOptions
* @param {Array} postCssPlugins
*/
register(src, output, pluginOptions = {}, postCssPlugins = []) {
return this.preprocess(
'sass',
src,
output,
this.pluginOptions(pluginOptions),
postCssPlugins
);
}
/**
* Build the plugin options for sass-loader.
*
* @param {Object} pluginOptions
* @returns {Object}
*/
pluginOptions(pluginOptions) {
return Object.assign(
{
precision: 8,
outputStyle: 'expanded',
implementation: () => require('sass')
},
pluginOptions,
{ sourceMap: true }
);
}
}
module.exports = Sass;
+37
View File
@@ -0,0 +1,37 @@
let Preprocessor = require('./Preprocessor');
class Stylus extends Preprocessor {
/**
* Required dependencies for the component.
*/
dependencies() {
return ['stylus', 'stylus-loader'];
}
/**
* Register the component.
*
* @param {*} src
* @param {string} output
* @param {Object} pluginOptions
* @param {Array} postCssPlugins
*/
register(src, output, pluginOptions = {}, postCssPlugins = []) {
pluginOptions = Object.assign(
{
preferPathResolver: 'webpack'
},
pluginOptions
);
return this.preprocess(
'stylus',
src,
output,
pluginOptions,
postCssPlugins
);
}
}
module.exports = Stylus;
+45
View File
@@ -0,0 +1,45 @@
let JavaScript = require('./JavaScript');
class TypeScript extends JavaScript {
/**
* The API name for the component.
*/
name() {
return ['typeScript', 'ts'];
}
/**
* Required dependencies for the component.
*/
dependencies() {
return ['ts-loader', 'typescript'].concat();
}
/**
* webpack rules to be appended to the master config.
*/
webpackRules() {
return [].concat(super.webpackRules(), {
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/]
}
});
}
/**
* Override the generated webpack configuration.
*
* @param {Object} config
*/
webpackConfig(config) {
super.webpackConfig(config);
config.resolve.extensions.push('.ts', '.tsx');
config.resolve.alias['vue$'] = 'vue/dist/vue.esm.js';
}
}
module.exports = TypeScript;
+41
View File
@@ -0,0 +1,41 @@
let glob = require('glob');
let path = require('path');
let webpack = require('webpack');
let VersionFilesTask = require('../tasks/VersionFilesTask');
class Version {
/**
* Register the component.
*
* @param {Array} files
*/
register(files = []) {
files = flatten(
[].concat(files).map(filePath => {
if (File.find(filePath).isDirectory()) {
filePath += path.sep + '**/*';
}
if (!filePath.includes('*')) return filePath;
return glob.sync(
new File(filePath).forceFromPublic().relativePath(),
{ nodir: true }
);
})
);
Mix.addTask(new VersionFilesTask({ files }));
}
/**
* webpack plugins to be appended to the master config.
*/
webpackPlugins() {
if (Mix.inProduction()) {
return [new webpack.HashedModuleIdsPlugin()];
}
}
}
module.exports = Version;
+181
View File
@@ -0,0 +1,181 @@
let { VueLoaderPlugin } = require('vue-loader');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
let { without } = require('lodash');
class Vue {
/**
* Required dependencies for the component.
*/
dependencies() {
let dependencies = ['vue-template-compiler'];
if (Config.extractVueStyles && Config.globalVueStyles) {
dependencies.push('sass-resources-loader');
}
return dependencies;
}
/**
* Override the generated webpack configuration.
*
* @param {Object} webpackConfig
*/
webpackConfig(webpackConfig) {
webpackConfig.module.rules.push({
test: /\.vue$/,
loader: 'vue-loader',
options: Config.vue || {}
});
webpackConfig.plugins.push(new VueLoaderPlugin());
this.updateCssLoaders(webpackConfig);
}
/**
* Update all preprocessor loaders to support CSS extraction.
*
* @param {Object} webpackConfig
*/
updateCssLoaders(webpackConfig) {
// Basic CSS and PostCSS
this.updateCssLoader('css', webpackConfig, rule => {
rule.loaders.find(
loader => loader.loader === 'postcss-loader'
).options = this.postCssOptions();
});
// LESS
this.updateCssLoader('less', webpackConfig);
// SASS
let sassCallback = rule => {
if (Mix.seesNpmPackage('sass')) {
rule.loaders.find(
loader => loader.loader === 'sass-loader'
).options.implementation = require('sass');
}
if (Config.globalVueStyles) {
rule.loaders.push({
loader: 'sass-resources-loader',
options: {
resources: Mix.paths.root(Config.globalVueStyles)
}
});
}
};
// SCSS
this.updateCssLoader('scss', webpackConfig, sassCallback);
// SASS
this.updateCssLoader('sass', webpackConfig, sassCallback);
// STYLUS
this.updateCssLoader('styl', webpackConfig);
}
/**
* Update a single CSS loader.
*
* @param {string} loader
* @param {Object} webpackConfig
* @param {Function} callback
*/
updateCssLoader(loader, webpackConfig, callback) {
let rule = webpackConfig.module.rules.find(rule => {
return rule.test instanceof RegExp && rule.test.test('.' + loader);
});
callback && callback(rule);
if (Config.extractVueStyles) {
let extractPlugin = this.extractPlugin();
rule.loaders = extractPlugin.extract({
fallback: 'style-loader',
use: without(rule.loaders, 'style-loader')
});
this.addExtractPluginToConfig(extractPlugin, webpackConfig);
}
}
/**
* Fetch the appropriate postcss plugins for the compile.
*/
postCssOptions() {
if (Mix.components.get('postCss')) {
return {
plugins: Mix.components.get('postCss').details[0].postCssPlugins
};
}
// If the user has a postcss.config.js file in their project root,
// postcss-loader will automatically read and fetch the plugins.
if (File.exists(Mix.paths.root('postcss.config.js'))) {
return {};
}
return { plugins: Config.postCss };
}
/**
* Add an extract text plugin to the webpack config plugins array.
*
* @param {Object} extractPlugin
* @param {Object} webpackConfig
*/
addExtractPluginToConfig(extractPlugin, webpackConfig) {
if (extractPlugin.isNew) {
webpackConfig.plugins.push(extractPlugin);
}
}
/**
* Fetch the appropriate extract plugin.
*/
extractPlugin() {
// If the user set extractVueStyles: true, we'll try
// to append the Vue styles to an existing CSS chunk.
if (typeof Config.extractVueStyles === 'boolean') {
let preprocessorName = Object.keys(Mix.components.all())
.reverse()
.find(componentName => {
return ['sass', 'less', 'stylus', 'postCss'].includes(
componentName
);
});
if (preprocessorName) {
return Mix.components
.get(preprocessorName)
.extractPlugins.slice(-1)[0];
}
}
// Otherwise, we'll need to whip up a fresh extract text instance.
return tap(
new ExtractTextPlugin(this.extractFileName()),
extractPlugin => {
extractPlugin.isNew = true;
}
);
}
/**
* Determine the extract file name.
*/
extractFileName() {
let fileName =
typeof Config.extractVueStyles === 'string'
? Config.extractVueStyles
: '/css/vue-styles.css';
return fileName.replace(Config.publicPath, '').replace(/^\//, '');
}
}
module.exports = Vue;
+233
View File
@@ -0,0 +1,233 @@
module.exports = function() {
return {
/**
* Determine if webpack should be triggered in a production environment.
*
* @type {Boolean}
*/
production:
process.env.NODE_ENV === 'production' ||
process.argv.includes('-p'),
/**
* Determine if we should enable hot reloading.
*
* @type {Boolean}
*/
hmr: process.argv.includes('--hot'),
/**
* Hostname and port used for the hot reload module
*
* @type {Object}
*/
hmrOptions: {
host: 'localhost',
port: '8080'
},
/**
* PostCSS plugins to be applied to compiled CSS.
*
* See: https://github.com/postcss/postcss/blob/master/docs/plugins.md
*
* @type {Array}
*/
postCss: [],
/**
* Determine if we should enable autoprefixer by default.
*
* @type {Boolean}
*/
autoprefixer: {
enabled: true,
options: {}
},
/**
* Determine if Mix should remove unused selectors from your CSS bundle.
* You may provide a boolean, or object for the Purify plugin.
*
* https://github.com/webpack-contrib/purifycss-webpack#options
*
* @type {Boolean|object}
*/
purifyCss: false,
/**
* The public path for the build.
*
* @type {String}
*/
publicPath: '',
/**
* Determine if error notifications should be displayed for each build.
*
* @type {Boolean}
*/
notifications: {
onSuccess: true,
onFailure: true
},
/**
* Determine if sourcemaps should be created for the build.
*
* @type {Boolean}
*/
sourcemaps: false,
/**
* The resource root for the build.
*
* @type {String}
*/
resourceRoot: '/',
/**
* Image Loader defaults.
* See: https://github.com/thetalecrafter/img-loader#options
*
* @type {Object}
*/
imgLoaderOptions: {
enabled: true,
gifsicle: {},
mozjpeg: {},
optipng: {},
svgo: {}
},
/**
* File Loader directory defaults.
*
* @type {Object}
*/
fileLoaderDirs: {
images: 'images',
fonts: 'fonts'
},
/**
* The default Babel configuration.
*
* @type {String} babelRcPath
*/
babel: function(babelRcPath) {
babelRcPath = babelRcPath || Mix.paths.root('.babelrc');
return require('./BabelConfig').generate(
this.babelConfig,
babelRcPath
);
},
/**
* Determine if CSS relative url()s should be calculated by Sass Webpack,
* using resolve-url-loader. Disabling this can improve performance
* greatly.
*
* @type {Boolean}
*/
processCssUrls: true,
/**
* Should we extract .vue component styles into a dedicated file?
* You may provide a boolean, or a dedicated path to extract to.
*
* Ex: extractVueStyles: '/css/vue.css'
*
* @type {Boolean|string}
*/
extractVueStyles: false,
/**
* A file path with global styles that shuold be imported into every Vue component.
*
* See: https://vue-loader.vuejs.org/en/configurations/pre-processors.html#loading-a-global-settings-file
*
* @type {string}
*/
globalVueStyles: '',
/**
* Terser-specific settings for Webpack.
*
* See: https://github.com/webpack-contrib/terser-webpack-plugin#options
*
* @type {Object}
*/
terser: {
cache: true,
parallel: true,
sourceMap: true,
terserOptions: {
compress: {
warnings: false
},
output: {
comments: false
}
}
},
/**
* cssnano-specific settings for Webpack.
*
* See: https://cssnano.co/optimisations/
*
* @type {Object}
*/
cssNano: {},
/**
* CleanCss-specific settings for Webpack.
*
* See: https://github.com/jakubpawlowicz/clean-css#constructor-options
*
* @type {Object}
*/
cleanCss: {},
/**
* Custom Webpack-specific configuration to merge/override Mix's.
*
* @type {Object}
*/
webpackConfig: {},
/**
* Custom Babel configuration to be merged with Mix's defaults.
*
* @type {Object}
*/
babelConfig: {},
/**
* Determine if Mix should ask the friendly errors plugin to
* clear the console before outputting the results or not.
*
* https://github.com/geowarin/friendly-errors-webpack-plugin#options
*
* @type {Boolean}
*/
clearConsole: true,
/**
* Merge the given options with the current defaults.
*
* @param {object} options
*/
merge(options) {
let mergeWith = require('lodash').mergeWith;
mergeWith(this, options, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return objValue.concat(srcValue);
}
});
}
};
};
+61
View File
@@ -0,0 +1,61 @@
let objectValues = require('lodash').values;
/**
* Generic tap function.
*
* @param {mixed} val
* @param {Function} callback
*/
global.tap = function(val, callback) {
callback(val);
return val;
};
/**
* Add tap to arrays.
*
* @param {mixed} val
* @param {Function} callback
*/
Object.defineProperty(Array.prototype, 'tap', {
value: function(callback) {
if (this.length) {
callback(this);
}
return this;
}
});
/**
* Reject items from an array.
*
* @param {mixed} val
* @param {Function} callback
*/
Object.defineProperty(Array.prototype, 'reject', {
value: function(callback) {
return this.filter(item => !callback(item));
}
});
/**
* Flatten the given array.
*
* @param {Array} arr
*/
global.flatten = function(arr) {
return [].concat.apply([], objectValues(arr));
};
/**
* Sort object by keys
*
* @param {Object} obj
*/
global.sortObjectKeys = obj => {
return Object.keys(obj)
.sort()
.reduce((r, k) => ((r[k] = obj[k]), r), {});
};
+68
View File
@@ -0,0 +1,68 @@
/*
|--------------------------------------------------------------------------
| Welcome to Laravel Mix!
|--------------------------------------------------------------------------
|
| Laravel Mix provides a clean, fluent API for defining basic webpack
| build steps for your Laravel application. Mix supports a variety
| of common CSS and JavaScript pre-processors out of the box.
|
*/
/**
* We'll begin by pulling in a few globals that Mix often uses.
*/
require('./helpers');
require('dotenv').config();
global.path = require('path');
global.File = require('./File');
/**
* This config object is what Mix will reference, when it's time
* to dynamically build up your Webpack configuration object.
*/
global.Config = require('./config')();
global.Mix = new (require('./Mix'))();
/**
* If we're working in a Laravel app, we'll explicitly
* set the default public path, as a convenience.
*/
if (Mix.sees('laravel')) {
Config.publicPath = 'public';
}
/**
* If the user activates hot reloading, with the --hot
* flag, we'll record it as a file, so that Laravel
* can detect it and update its mix() url paths.
*/
Mix.listen('init', () => {
if (Mix.shouldHotReload()) {
let http = process.argv.includes('--https') ? 'https' : 'http';
let port = process.argv.includes('--port')
? process.argv[process.argv.indexOf('--port') + 1]
: Config.hmrOptions.port;
new File(path.join(Config.publicPath, 'hot')).write(
http + '://' + Config.hmrOptions.host + ':' + port + '/'
);
}
});
/**
* Mix exposes a simple, fluent API for activating many common build
* steps that a typical project should require. Behind the scenes,
* all calls to this fluent API will update the above config.
*/
let Api = require('./Api');
let api = new Api();
module.exports = api;
module.exports.config = Config;
+29
View File
@@ -0,0 +1,29 @@
let Task = require('./Task');
let FileCollection = require('../FileCollection');
class ConcatenateFilesTask extends Task {
/**
* Run the task.
*/
run() {
this.files = new FileCollection(this.data.src);
this.merge();
}
/**
* Merge the files into one.
*/
merge() {
this.assets.push(this.files.merge(this.data.output, this.data.babel));
}
/**
* Handle when a relevant source file is changed.
*/
onChange(updatedFile) {
this.merge();
}
}
module.exports = ConcatenateFilesTask;
+47
View File
@@ -0,0 +1,47 @@
let Task = require('./Task');
let FileCollection = require('../FileCollection');
let Log = require('../Log');
const path = require('path');
class CopyFilesTask extends Task {
/**
* Run the task.
*/
run() {
let copy = this.data;
this.files = new FileCollection(copy.from);
this.files.copyTo(copy.to);
this.assets = this.files.assets;
}
/**
* Handle when a relevant source file is changed.
*
* @param {string} updatedFile
*/
onChange(updatedFile) {
let destination = this.data.to;
// If we're copying a src directory recursively, we have to calculate
// the correct destination path, based on the src directory tree.
if (
!Array.isArray(this.data.from) &&
new File(this.data.from).isDirectory()
) {
destination = destination.append(
path
.normalize(updatedFile)
.replace(path.normalize(this.data.from), '')
);
}
Log.feedback(`Copying ${updatedFile} to ${destination.path()}`);
this.files.copyTo(destination, new File(updatedFile));
}
}
module.exports = CopyFilesTask;
+43
View File
@@ -0,0 +1,43 @@
let chokidar = require('chokidar');
class Task {
/**
* Create a new task instance.
*
* @param {Object} data
*/
constructor(data) {
this.data = data;
this.assets = [];
this.isBeingWatched = false;
}
/**
* Watch all relevant files for changes.
*
* @param {boolean} usePolling
*/
watch(usePolling = false) {
if (this.isBeingWatched) return;
let files = this.files.get();
let watcher = chokidar
.watch(files, { usePolling, persistent: true })
.on('change', this.onChange.bind(this));
// Workaround for issue with atomic writes.
// See https://github.com/paulmillr/chokidar/issues/591
if (!usePolling) {
watcher.on('raw', (event, path, { watchedPath }) => {
if (event === 'rename') {
watcher.unwatch(files);
watcher.add(files);
}
});
}
this.isBeingWatched = true;
}
}
module.exports = Task;
+30
View File
@@ -0,0 +1,30 @@
let Task = require('./Task');
let FileCollection = require('../FileCollection');
class VersionFilesTask extends Task {
/**
* Run the task.
*/
run() {
this.files = new FileCollection(this.data.files);
this.assets = this.data.files.map(file => {
file = new File(file);
Mix.manifest.hash(file.pathFromPublic());
return file;
});
}
/**
* Handle when a relevant source file is changed.
*
* @param {string} updatedFile
*/
onChange(updatedFile) {
Mix.manifest.hash(new File(updatedFile).pathFromPublic()).refresh();
}
}
module.exports = VersionFilesTask;
+21
View File
@@ -0,0 +1,21 @@
class BuildCallbackPlugin {
/**
* Create a new plugin instance.
*
* @param {Function} callback
*/
constructor(callback) {
this.callback = callback;
}
/**
* Apply the plugin.
*
* @param {Object} compiler
*/
apply(compiler) {
compiler.plugin('done', this.callback);
}
}
module.exports = BuildCallbackPlugin;
+31
View File
@@ -0,0 +1,31 @@
let Purifier = require('purifycss-webpack');
let glob = require('glob');
class CssPurifierPlugin {
/**
* Build up the plugin.
*/
static build() {
let bladeFiles = glob.sync(
Mix.paths.root('resources/views/**/*.blade.php')
);
let vueFiles = glob.sync(
Mix.paths.root('resources/assets/js/**/*.vue')
);
let paths = bladeFiles.concat(vueFiles);
if (Config.purifyCss.paths) {
paths = paths.concat(Config.purifyCss.paths);
}
return new Purifier(
Object.assign({}, Config.purifyCss, {
paths,
minimize: Mix.inProduction()
})
);
}
}
module.exports = CssPurifierPlugin;
+85
View File
@@ -0,0 +1,85 @@
let Log = require('../Log');
class CustomTasksPlugin {
/**
* Apply the plugin.
*
* @param {Object} compiler
*/
apply(compiler) {
compiler.plugin('done', stats => {
Mix.tasks.forEach(task => this.runTask(task, stats));
if (Mix.components.get('version')) {
this.applyVersioning();
}
if (Mix.inProduction()) {
this.minifyAssets();
}
if (Mix.isWatching()) {
Mix.tasks.forEach(task => task.watch(Mix.isPolling()));
}
Mix.manifest.refresh();
});
}
/**
* Execute the task.
*
* @param {Task} task
*/
runTask(task, stats) {
task.run();
task.assets.forEach(asset => {
Mix.manifest.add(asset.pathFromPublic());
// Update the Webpack assets list for better terminal output.
stats.compilation.assets[asset.pathFromPublic()] = {
size: () => asset.size(),
emitted: true
};
});
}
/**
* Minify the given asset file.
*/
minifyAssets() {
let tasks = Mix.tasks.filter(task => {
return (
task.constructor.name !== 'VersionFilesTask' &&
task.constructor.name !== 'CopyFilesTask'
);
});
tasks.forEach(task => {
task.assets.forEach(asset => {
try {
asset.minify();
} catch (e) {
Log.error(
`Whoops! We had trouble minifying "${asset.relativePath()}". ` +
`Perhaps you need to use mix.babel() instead?`
);
throw e;
}
});
});
}
/**
* Version all files that are present in the manifest.
*/
applyVersioning() {
let manifest = Object.keys(Mix.manifest.get());
manifest.forEach(file => Mix.manifest.hash(file));
}
}
module.exports = CustomTasksPlugin;
+19
View File
@@ -0,0 +1,19 @@
class ManifestPlugin {
/**
* Apply the plugin.
*
* @param {Object} compiler
*/
apply(compiler) {
compiler.plugin('emit', (curCompiler, callback) => {
let stats = curCompiler.getStats().toJson();
// Handle the creation of the mix-manifest.json file.
Mix.manifest.transform(stats).refresh();
callback();
});
}
}
module.exports = ManifestPlugin;
+59
View File
@@ -0,0 +1,59 @@
let webpack = require('webpack');
let dotenv = require('dotenv');
let expand = require('dotenv-expand');
/**
* Create a new plugin instance.
*
* @param {string} envPath
*/
function MixDefinitionsPlugin(envPath) {
expand(
dotenv.config({
path: envPath || Mix.paths.root('.env')
})
);
}
/**
* Build up the necessary definitions and add them to the DefinePlugin.
*
* @param {Object|null} merge
*/
MixDefinitionsPlugin.build = function(merge = {}) {
return new webpack.DefinePlugin(
new MixDefinitionsPlugin().getDefinitions(merge)
);
};
/**
* Build all MIX_ definitions for Webpack's DefinePlugin.
*
* @param {object} merge
*/
MixDefinitionsPlugin.prototype.getDefinitions = function(merge) {
let regex = /^MIX_/i;
// Filter out env vars that don't begin with MIX_.
let env = Object.keys(process.env)
.filter(key => regex.test(key))
.reduce((value, key) => {
value[key] = process.env[key];
return value;
}, {});
let values = Object.assign(env, merge);
return {
'process.env': Object.keys(values)
// Stringify all values so they can be fed into Webpack's DefinePlugin.
.reduce((value, key) => {
value[key] = JSON.stringify(values[key]);
return value;
}, {})
};
};
module.exports = MixDefinitionsPlugin;
+29
View File
@@ -0,0 +1,29 @@
class MockEntryPlugin {
/**
* 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 {Object} compiler
*/
apply(compiler) {
compiler.plugin('done', stats => {
let temporaryOutputFile = stats
.toJson()
.assets.find(asset => asset.name === 'mix.js');
if (temporaryOutputFile) {
delete stats.compilation.assets[temporaryOutputFile.name];
File.find(
path.resolve(Config.publicPath, temporaryOutputFile.name)
).delete();
}
});
}
}
module.exports = MockEntryPlugin;