Initial commit

This commit is contained in:
Developer
2025-04-21 16:03:20 +02:00
commit 2832896157
22874 changed files with 3092801 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
docs/
test/
.github/
.nyc_output
.idea
coverage
mix.sublime-project
mix.sublime-workspace
notes.md

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Jeffrey Way <jeffrey@jeffrey-way.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,107 @@
{
"_from": "laravel-mix@^1.0",
"_id": "laravel-mix@1.7.2",
"_inBundle": false,
"_integrity": "sha512-La1eAsCkEdySc9J9MJ/g8Dj1EfGo7aXW92GZKoSbrSg4uQWNNoV824e6+o4f4Eo/YWYrYwZTkdnWJJ1uVqP+dw==",
"_location": "/laravel-mix",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "laravel-mix@^1.0",
"name": "laravel-mix",
"escapedName": "laravel-mix",
"rawSpec": "^1.0",
"saveSpec": null,
"fetchSpec": "^1.0"
},
"_requiredBy": [
"#DEV:/"
],
"_resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-1.7.2.tgz",
"_shasum": "4bf8444202948ce18f9cfd2e2508f82ea06106fe",
"_spec": "laravel-mix@^1.0",
"_where": "D:\\developments\\teaser-inertia\\nova-components\\NovaLeader",
"author": {
"name": "Jeffrey Way"
},
"bugs": {
"url": "https://github.com/JeffreyWay/laravel-mix/issues"
},
"bundleDependencies": false,
"dependencies": {
"autoprefixer": "^7.1.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.1.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.5.1",
"chokidar": "^1.7.0",
"clean-css": "^4.1.3",
"concatenate": "0.0.2",
"css-loader": "^0.28.3",
"dotenv": "^4.0.0",
"dotenv-expand": "^4.0.1",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^3.0.1",
"glob": "^7.1.2",
"html-loader": "^0.4.5",
"img-loader": "^2.0.0",
"lodash": "^4.17.4",
"md5": "^2.2.1",
"node-sass": "^4.5.3",
"postcss-loader": "^2.0.5",
"resolve-url-loader": "^2.0.2",
"sass-loader": "^6.0.5",
"style-loader": "^0.18.1",
"uglify-js": "^2.8.28",
"uglifyjs-webpack-plugin": "^1.0.0",
"vue-loader": "^13.0.5",
"vue-template-compiler": "^2.0.0",
"webpack": "^3.5.0",
"webpack-chunk-hash": "^0.4.0",
"webpack-dev-server": "^2.5.1",
"webpack-merge": "^4.1.0",
"webpack-notifier": "^1.5.0",
"yargs": "^8.0.1"
},
"deprecated": false,
"description": "Laravel Mix is an elegant wrapper around Webpack for the 80% use case.",
"devDependencies": {
"ava": "^0.19.1",
"mock-fs": "^4.3.0",
"mock-require": "^2.0.2",
"nyc": "^10.3.2",
"postcss-custom-properties": "^6.2.0",
"sinon": "^2.3.2",
"vue": "^2.5.0"
},
"engines": {
"node": ">=6.0.0"
},
"homepage": "https://github.com/JeffreyWay/laravel-mix#readme",
"keywords": [
"laravel",
"webpack",
"laravel elixir",
"laravel mix"
],
"license": "MIT",
"main": "src/index.js",
"name": "laravel-mix",
"repository": {
"type": "git",
"url": "git+https://github.com/JeffreyWay/laravel-mix.git"
},
"scripts": {
"dev": "NODE_ENV=development webpack --watch --progress --hide-modules",
"hmr": "NODE_ENV=development webpack-dev-server --inline --hot",
"posttest": "nyc report --reporter=html",
"production": "NODE_ENV=production webpack --progress --hide-modules",
"test": "sudo nyc ava --verbose",
"webpack": "NODE_ENV=development webpack --progress --hide-modules"
},
"version": "1.7.2"
}

View File

@@ -0,0 +1,21 @@
<p align="center"><img src="https://laravel.com/assets/img/components/logo-mix.svg" alt="Laravel Mix"></p>
<p align="center">
<a href="https://www.npmjs.com/package/laravel-mix"><img src="https://img.shields.io/npm/v/laravel-mix.svg" alt="NPM"></a>
<a href="https://npmcharts.com/compare/laravel-mix?minimal=true"><img src="https://img.shields.io/npm/dt/laravel-mix.svg" alt="NPM"></a>
<a href="https://www.npmjs.com/package/laravel-mix"><img src="https://img.shields.io/npm/l/laravel-mix.svg" alt="NPM"></a>
</p>
## Introduction
Laravel Mix provides a clean, fluent API for defining basic [webpack](http://github.com/webpack/webpack) build steps for your Laravel application. Mix supports several common CSS and JavaScript pre-processors.
If you've ever been confused about how to get started with module bundling and asset compilation, you will love Laravel Mix!
## Documentation
You may review the initial documentation here [on GitHub](https://github.com/JeffreyWay/laravel-mix/tree/master/docs#readme).
## License
Laravel Mix is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).

View File

@@ -0,0 +1,26 @@
/**
* As our first step, we'll pull in the user's webpack.mix.js
* file. Based on what the user requests in that file,
* a generic config object will be constructed for us.
*/
require('../src/index');
require(Mix.paths.mix());
/**
* Just in case the user needs to hook into this point
* in the build process, we'll make an announcement.
*/
Mix.dispatch('init', Mix);
/**
* Now that we know which build tasks are required by the
* user, we can dynamically create a configuration object
* for Webpack. And that's all there is to it. Simple!
*/
let WebpackConfig = require('../src/builder/WebpackConfig');
module.exports = new WebpackConfig().build();

View File

@@ -0,0 +1,49 @@
let mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for your application, as well as bundling up your JS files.
|
*/
mix.js('src/app.js', 'dist/')
.sass('src/app.scss', 'dist/');
// Full API
// mix.js(src, output);
// mix.react(src, output); <-- Identical to mix.js(), but registers React Babel compilation.
// mix.ts(src, output); <-- Requires tsconfig.json to exist in the same folder as webpack.mix.js
// mix.extract(vendorLibs);
// mix.sass(src, output);
// mix.standaloneSass('src', output); <-- Faster, but isolated from Webpack.
// mix.fastSass('src', output); <-- Alias for mix.standaloneSass().
// mix.less(src, output);
// mix.stylus(src, output);
// mix.postCss(src, output, [require('postcss-some-plugin')()]);
// mix.browserSync('my-site.dev');
// mix.combine(files, destination);
// mix.babel(files, destination); <-- Identical to mix.combine(), but also includes Babel compilation.
// mix.copy(from, to);
// mix.copyDirectory(fromDir, toDir);
// mix.minify(file);
// mix.sourceMaps(); // Enable sourcemaps
// mix.version(); // Enable versioning.
// mix.disableNotifications();
// mix.setPublicPath('path/to/public');
// mix.setResourceRoot('prefix/for/resource/locators');
// mix.autoload({}); <-- Will be passed to Webpack's ProvidePlugin.
// mix.webpackConfig({}); <-- Override webpack.config.js, without editing the file directly.
// mix.then(function () {}) <-- Will be triggered each time Webpack finishes building.
// mix.options({
// extractVueStyles: false, // Extract .vue component styling to file, rather than inline.
// globalVueStyles: file, // Variables file to be imported in every component.
// processCssUrls: true, // Process/optimize relative stylesheet url()'s. Set to false, if you don't want them touched.
// purifyCss: false, // Remove unused CSS selectors.
// uglify: {}, // Uglify-specific options. https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
// postCss: [] // Post-CSS options: https://github.com/postcss/postcss/blob/master/docs/plugins.md
// });

View File

@@ -0,0 +1,525 @@
let Verify = require('./Verify');
let CopyFilesTask = require('./tasks/CopyFilesTask');
let ConcatFilesTask = require('./tasks/ConcatenateFilesTask');
let VersionFilesTask = require('./tasks/VersionFilesTask');
let webpack = require('webpack');
let glob = require('glob');
let _ = require('lodash');
let path = require('path');
class Api {
/**
* Register the Webpack entry/output paths.
*
* @param {string|Array} entry
* @param {string} output
*/
js(entry, output) {
Verify.js(entry, output);
entry = [].concat(entry).map(file => new File(file));
output = new File(output);
Config.js.push({ entry, output });
return this;
}
/**
* Register support for the React framework.
*
* @param {string|Array} entry
* @param {string} output
*/
react(entry, output) {
Config.react = true;
Verify.dependency('babel-preset-react', ['babel-preset-react']);
return this.js(entry, output);
};
/**
* Register support for the Preact framework.
*
* @param {string|Array} entry
* @param {string} output
*/
preact(entry, output) {
Config.preact = true;
Verify.dependency('babel-preset-preact', ['babel-preset-preact']);
return this.js(entry, output);
};
/**
* Register support for the TypeScript.
*/
ts(entry, output) {
Config.typeScript = true;
Verify.dependency('ts-loader', ['ts-loader', 'typescript']);
return this.js(entry, output);
};
/**
* Register support for the TypeScript.
*/
typeScript(entry, output) {
return this.ts(entry, output);
}
/**
* Register Sass compilation.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
sass(src, output, pluginOptions = {}) {
pluginOptions = Object.assign({
precision: 8,
outputStyle: 'expanded'
}, pluginOptions, { sourceMap: true });
return this.preprocess('sass', src, output, pluginOptions);
}
/**
* Register standalone-Sass compilation that will not run through Webpack.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
standaloneSass(src, output, pluginOptions = {}) {
Verify.exists(src);
return this.preprocess('fastSass', src, output, pluginOptions);
};
/**
* Alias for standaloneSass.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
fastSass(...args) {
return this.standaloneSass(...args);
}
/**
* Register Less compilation.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
less(src, output, pluginOptions) {
Verify.dependency('less-loader', ['less-loader', 'less']);
return this.preprocess('less', src, output, pluginOptions);
}
/**
* Register Stylus compilation.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
stylus(src, output, pluginOptions = {}) {
Verify.dependency('stylus-loader', ['stylus-loader', 'stylus']);
return this.preprocess('stylus', src, output, pluginOptions);
};
/**
* Register postcss compilation.
*
* @param {string} src
* @param {string} output
* @param {array} postCssPlugins
*/
postCss(src, output, postCssPlugins = []) {
Verify.preprocessor('postCss', src, output);
src = new File(src);
output = this._normalizeOutput(new File(output), src.nameWithoutExtension() + '.css');
Config.preprocessors['postCss'] = (Config.preprocessors['postCss'] || []).concat({
src, output, postCssPlugins
});
return this;
};
/**
* Register a generic CSS preprocessor.
*
* @param {string} type
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
preprocess(type, src, output, pluginOptions = {}) {
Verify.preprocessor(type, src, output);
src = new File(src);
output = this._normalizeOutput(new File(output), src.nameWithoutExtension() + '.css');
Config.preprocessors[type] = (Config.preprocessors[type] || []).concat({
src, output, pluginOptions
});
if (type === 'fastSass') {
Mix.addAsset(output);
}
return this;
}
/**
* Combine a collection of files.
*
* @param {string|Array} src
* @param {string} output
* @param {Boolean} babel
*/
combine(src, output = '', babel = false) {
output = new File(output);
Verify.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);
return this;
};
/**
* Alias for this.Mix.combine().
*
* @param {string|Array} src
* @param {string} output
*/
scripts(src, output) {
return this.combine(src, output);
};
/**
* Identical to this.Mix.combine(), but includes Babel compilation.
*
* @param {string|Array} src
* @param {string} output
*/
babel(src, output) {
return this.combine(src, output, true);
return this;
};
/**
* Alias for this.Mix.combine().
*
* @param {string|Array} src
* @param {string} output
*/
styles(src, output) {
return this.combine(src, output);
};
/**
* Minify the provided file.
*
* @param {string|Array} src
*/
minify(src) {
if (Array.isArray(src)) {
src.forEach(file => this.minify(file));
return this;
}
let output = src.replace(/\.([a-z]{2,})$/i, '.min.$1');
return this.combine(src, output);
};
/**
* Copy one or more files to a new location.
*
* @param {string} from
* @param {string} to
*/
copy(from, to) {
let task = new CopyFilesTask({
from, to: new File(to)
});
Mix.addTask(task);
return this;
};
/**
* Copy a directory to a new location. This is identical
* to mix.copy().
*
* @param {string} from
* @param {string} to
*/
copyDirectory(from, to) {
return this.copy(from, to);
};
/**
* Enable Browsersync support for the project.
*
* @param {object} config
*/
browserSync(config = {}) {
Verify.dependency(
'browser-sync-webpack-plugin',
['browser-sync-webpack-plugin', 'browser-sync'],
true
);
if (typeof config === 'string') {
config = { proxy: config };
}
Config.browserSync = config;
return this;
};
/**
* Enable automatic file versioning.
*
* @param {Array} files
*/
version(files = []) {
Config.versioning = true;
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 })
);
return this;
}
/**
* Register vendor libs that should be extracted.
* This helps drastically with long-term caching.
*
* @param {Array} libs
* @param {string} output
*/
extract(libs, output) {
Config.extractions.push({ libs, output });
return this;
};
/**
* Enable sourcemap support.
*
* @param {Boolean} productionToo
* @param {string} type
*/
sourceMaps(productionToo = true, type = 'inline-source-map') {
if (Mix.inProduction()) {
type = productionToo ? 'cheap-source-map' : 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;
};
/**
* Disable all OS notifications.
*/
disableNotifications() {
Config.notifications = false;
return this;
};
/**
* Disable success notifications.
*/
disableSuccessNotifications() {
Config.notifications = {
onSuccess: false,
onFailure: true
};
return this;
};
/**
* Register libraries to automatically "autoload" when
* the appropriate variable is references in your JS.
*
* @param {Object} libs
*/
autoload(libs) {
let aliases = {};
Object.keys(libs).forEach(library => {
[].concat(libs[library]).forEach(alias => {
aliases[alias] = library;
});
});
Config.autoload = aliases;
return this;
};
/**
* Merge custom config with the provided webpack.config file.
*
* @param {object} config
*/
webpackConfig(config) {
Config.webpackConfig = (typeof config == 'function') ? config(webpack) : config;
return this;
}
/* Set Mix-specific options.
*
* @param {object} options
*/
options(options) {
if (options.purifyCss) {
options.purifyCss = require('./PurifyPaths').build(options.purifyCss);
Verify.dependency(
'purifycss-webpack',
['purifycss-webpack', 'purify-css'],
true // abortOnComplete
);
}
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();
}
/**
* 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 = Api;

View File

@@ -0,0 +1,48 @@
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;

View File

@@ -0,0 +1,288 @@
let md5 = require('md5');
let path = require('path');
let fs = require('fs-extra');
let uglify = require('uglify-js');
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);
}
fs.writeFileSync(this.absolutePath, body);
return this;
}
/**
* Read the file's contents.
*/
read() {
return fs.readFileSync(this.path(), {
encoding: 'utf-8'
});
}
/**
* 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(uglify.minify(this.path(), Config.uglify).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;

View File

@@ -0,0 +1,113 @@
let concatenate = require('concatenate');
let babel = require('babel-core');
let glob = require('glob');
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) {
console.log(`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;

View File

@@ -0,0 +1,136 @@
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.join(
Config.publicPath,
this.manifest[this.normalizePath(file)]
);
}
return 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();
this.manifest[file] = file + '?id=' + hash;
return this;
}
/**
* Transform the Webpack stats into the shape we need.
*
* @param {object} stats
*/
transform(stats) {
let customAssets = Config.customAssets.map(asset => asset.pathFromPublic());
this.flattenAssets(stats).concat(customAssets).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) {
filePath = filePath.replace(
new RegExp('^' + Config.publicPath), ''
).replace(/\\/g, '/');
if (! filePath.startsWith('/')) {
filePath = '/' + filePath;
}
return filePath;
}
}
module.exports = Manifest;

View File

@@ -0,0 +1,122 @@
let Paths = require('./Paths');
let Manifest = require('./Manifest');
let Dispatcher = require('./Dispatcher');
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 = [];
}
/**
* 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 Mix should activate hot reloading.
*/
shouldHotReload() {
new File(path.join(Config.publicPath, 'hot')).delete();
return this.isUsing('hmr');
}
/**
* Add a custom file to the webpack assets collection.
*
* @param {string} asset
*/
addAsset(asset) {
Config.customAssets.push(asset);
}
/**
* 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;

View File

@@ -0,0 +1,48 @@
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 = path.resolve(__dirname, '../../../');
}
}
/**
* 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;

View File

@@ -0,0 +1,26 @@
let glob = require('glob');
class PurifyPaths {
/**
* Build up the proper Purify file paths.
*
* @param {Boolean|object} options
*/
static build(options) {
if (typeof options === 'object' && 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));
});
}
return options;
}
}
module.exports = PurifyPaths;

View File

@@ -0,0 +1,158 @@
let File = require('./File');
let path = require('path');
let spawn = require('child_process').spawn;
let notifier = require('node-notifier');
class StandaloneSass {
/**
* Create a new StandaloneSass instance.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
constructor(src, output, pluginOptions) {
this.src = src;
this.output = output;
this.pluginOptions = pluginOptions;
this.shouldWatch = process.argv.includes('--watch');
Mix.addAsset(this.output);
}
/**
* Run the node-sass compiler.
*/
run() {
this.compile();
if (this.shouldWatch) this.watch();
}
/**
* Compile Sass.
*
* @param {Boolean} watch
*/
compile(watch = false) {
this.command = spawn(
path.resolve('./node_modules/.bin/node-sass'), [
this.src.path(),
this.output.path()
].concat(this.options(watch)), { shell: true }
);
this.whenOutputIsAvailable((output, event) => {
if (event === 'error') this.onFail(output);
if (event === 'success') this.onSuccess(output);
});
return this;
}
/**
* Fetch the node-sass options.
*
* @param {Boolean} watch
*/
options(watch) {
let sassOptions = [
'--precision=8',
'--output-style=' + (Mix.inProduction() ? 'compressed' : 'expanded'),
];
if (watch) sassOptions.push('--watch');
if (this.pluginOptions.includePaths) {
this.pluginOptions.includePaths.forEach(
path => sassOptions.push('--include-path=' + path)
);
}
if(this.pluginOptions.importer) {
sassOptions.push('--importer ' + this.pluginOptions.importer)
}
if (Mix.isUsing('sourcemaps') && ! Mix.inProduction()) {
sassOptions.push('--source-map-embed');
}
return sassOptions;
}
/**
* Compile Sass, while registering a watcher.
*/
watch() {
return this.compile(true);
}
/**
* Register a callback for when output is available.
*
* @param {Function} callback
*/
whenOutputIsAvailable(callback) {
this.command.stderr.on('data', output => {
output = output.toString();
let event = 'change';
if (output.includes('Error')) event = 'error';
if (output.includes('Wrote CSS')) event = 'success';
callback(output, event);
});
}
/**
* Handle successful compilation.
*
* @param {string} output
*/
onSuccess(output) {
console.log("\n");
console.log(output);
if (Config.notifications.onSuccess) {
notifier.notify({
title: 'Laravel Mix',
message: 'Sass Compilation Successful',
contentImage: 'node_modules/laravel-mix/icons/laravel.png'
});
}
}
/**
* Handle failed compilation.
*
* @param {string} output
*/
onFail(output) {
output = output.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
console.log("\n");
console.log('Sass Compilation Failed!');
console.log();
console.log(output);
if (Mix.isUsing('notifications')) {
notifier.notify({
title: 'Laravel Mix',
subtitle: 'Sass Compilation Failed',
message: JSON.parse(output).message,
contentImage: 'node_modules/laravel-mix/icons/laravel.png'
});
}
if (! this.shouldWatch) process.exit();
}
}
module.exports = StandaloneSass;

View File

@@ -0,0 +1,130 @@
let assert = require('assert');
let exec = require('child_process').execSync;
let argv = require('yargs').argv;
class Verify {
/**
* Verify 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'
);
}
/**
* Verify 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`
);
}
/**
* Verify 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.`
);
}
// /**
// * Verify that the call to mix.extract() is valid.
// *
// * @param {Array} libs
// */
// static extract(libs) {
// assert(
// libs && Array.isArray(libs),
// 'mix.extract() requires an array as its first parameter.'
// );
// }
/**
* Verify that the necessary dependency is available.
*
* @param {string} name
* @param {array} dependencies
* @param {Boolean} abortOnComplete
*/
static dependency(name, dependencies, abortOnComplete = false) {
if (argv['$0'].includes('ava')) return;
try {
require.resolve(name);
} catch (e) {
console.log(
'Additional dependencies must be installed. ' +
'This will only take a moment.'
);
installDependencies(dependencies.join(' '));
if (abortOnComplete) {
console.log('Finished. Please run Mix again.');
process.exit();
}
}
}
}
/**
* Install the given dependencies using npm or yarn.
*
* @param {array} dependencies
*/
let installDependencies = dependencies => {
let command = `npm install ${dependencies} --save-dev`;
if (File.exists('yarn.lock')) {
command = `yarn add ${dependencies} --dev`;
}
exec(command);
};
module.exports = Verify;

View File

@@ -0,0 +1,119 @@
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 (! Config.js.length && ! extraction.output) {
throw new Error(
'Please provide an output path as the second argument to mix.extract().'
);
}
let vendorPath = extraction.output
? new File(extraction.output).pathFromPublic(Config.publicPath).replace(/\.js$/, '')
: 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$/, '');
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.
if (! output.pathFromPublic().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;

View File

@@ -0,0 +1,140 @@
let webpack = require('webpack');
let webpackDefaultConfig = require('./webpack-default');
let webpackEntry = require('./webpack-entry');
let webpackRules = require('./webpack-rules');
let webpackPlugins = require('./webpack-plugins');
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, extractions } = webpackEntry();
this.webpackConfig.entry = entry;
// If we're extracting any vendor libraries, then we
// need to add the CommonChunksPlugin to strip out
// all relevant code into its own file.
if (extractions.length) {
this.webpackConfig.plugins.push(
new webpack.optimize.CommonsChunkPlugin({
names: extractions,
minChunks: Infinity
})
);
}
return this;
}
/**
* Build the output object.
*/
buildOutput() {
let http = process.argv.includes('--https') ? 'https' : 'http';
this.webpackConfig.output = {
path: path.resolve(Mix.isUsing('hmr') ? '/' : Config.publicPath),
filename: '[name].js',
chunkFilename: '[name].js',
publicPath: Mix.isUsing('hmr') ? (http + '://localhost:8080/') : ''
};
return this;
}
/**
* Build the rules array.
*/
buildRules() {
let { rules, extractPlugins } = webpackRules();
this.webpackConfig.module.rules = this.webpackConfig.module.rules.concat(rules);
this.webpackConfig.plugins = this.webpackConfig.plugins.concat(extractPlugins);
return this;
}
/**
* Build the plugins array.
*/
buildPlugins() {
this.webpackConfig.plugins = this.webpackConfig.plugins.concat(
webpackPlugins()
);
return this;
}
/**
* Build the resolve object.
*/
buildResolving() {
let extensions = ['*', '.js', '.jsx', '.vue'];
let buildFile = 'vue/dist/vue.common.js';
if (Config.typeScript) {
extensions.push('.ts', '.tsx');
buildFile = 'vue/dist/vue.esm.js';
}
this.webpackConfig.resolve = {
extensions,
alias: {
'vue$': buildFile
}
};
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

@@ -0,0 +1,43 @@
module.exports = function () {
return {
context: Mix.paths.root(),
entry: {},
output: {},
module: { rules: [] },
plugins: [],
stats: {
hash: false,
version: false,
timings: false,
children: false,
errorDetails: false,
chunks: false,
modules: false,
reasons: false,
source: false,
publicPath: false
},
performance: {
hints: false
},
devtool: Config.sourcemaps,
devServer: {
headers: {
"Access-Control-Allow-Origin": "*"
},
contentBase: path.resolve(Config.publicPath),
historyApiFallback: true,
noInfo: true,
compress: true,
quiet: true
}
};
};

View File

@@ -0,0 +1,59 @@
let Entry = require('./Entry');
let entry;
// We'll start by filtering through all the JS compilation
// requests from the user, and building up the main entry
// object for Webpack.
function addScripts() {
if (Config.js.length) {
Config.js.forEach(js => {
entry.addFromOutput(
js.entry.map(file => file.path()),
js.output,
js.entry[0]
);
});
} else {
entry.addDefault();
}
}
// Next, we'll append any requested vendor library extractions.
function addVendors() {
let extractions = Config.extractions.map(entry.addExtraction.bind(entry));
// If we are extracting vendor libraries, then we also need
// to extract Webpack's manifest file to assist with caching.
if (extractions.length) {
extractions.push(path.join(entry.base, 'manifest'));
}
return extractions;
}
// Finally, we'll append all Sass/Less/Stylus references,
// and they'll neatly be extracted to their own files.
function addStylesheets() {
Object.keys(Config.preprocessors).filter(p => p !== 'fastSass').forEach(type => {
Config.preprocessors[type].forEach(preprocessor => {
entry.add(entry.keys()[0], preprocessor.src.path());
});
});
}
module.exports = function () {
entry = new Entry();
addScripts();
let extractions = addVendors();
addStylesheets();
return {
entry: entry.get(),
extractions
};
};

View File

@@ -0,0 +1,162 @@
let webpack = require('webpack');
let FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
let MockEntryPlugin = require('../plugins/MockEntryPlugin');
let MixDefinitionsPlugin = require('../plugins/MixDefinitionsPlugin');
let BuildCallbackPlugin = require('../plugins/BuildCallbackPlugin');
let CustomTasksPlugin = require('../plugins/CustomTasksPlugin');
let ManifestPlugin = require('../plugins/ManifestPlugin');
let WebpackChunkHashPlugin = require('webpack-chunk-hash');
let UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = function () {
let plugins = [];
// Activate better error feedback in the console.
plugins.push(
new FriendlyErrorsWebpackPlugin({ clearConsole: Config.clearConsole })
);
// Activate Webpack autoloading support.
plugins.push(
new webpack.ProvidePlugin(Config.autoload)
);
// Add support for webpack 3 scope hoisting.
if (Mix.inProduction()) {
plugins.push(
new webpack.optimize.ModuleConcatenationPlugin()
);
}
// Activate support for Mix_ .env definitions.
plugins.push(
MixDefinitionsPlugin.build({
NODE_ENV: Mix.inProduction()
? 'production'
: (process.env.NODE_ENV || 'development')
})
);
// Add automatic CSS Purification support.
if (Mix.isUsing('purifyCss')) {
let CssPurifierPlugin = require('../plugins/CssPurifierPlugin');
plugins.push(CssPurifierPlugin.build());
}
// Activate OS notifications for each compile.
if (Mix.isUsing('notifications')) {
let WebpackNotifierPlugin = require('webpack-notifier');
plugins.push(
new WebpackNotifierPlugin({
title: 'Laravel Mix',
alwaysNotify: Config.notifications.onSuccess,
contentImage: Mix.paths.root('node_modules/laravel-mix/icons/laravel.png')
})
);
}
// Add support for browser reloading with BrowserSync.
if (Mix.isUsing('browserSync')) {
let BrowserSyncPlugin = require('browser-sync-webpack-plugin');
plugins.push(
new BrowserSyncPlugin(
Object.assign({
host: 'localhost',
port: 3000,
proxy: 'app.dev',
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;
}
}
}
}, Config.browserSync),
{ reload: false }
)
);
}
// 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 (! Config.js.length) {
plugins.push(new MockEntryPlugin);
}
// Activate the appropriate Webpack versioning plugin, based on the environment.
if (Mix.isUsing('versioning')) {
plugins.push(
new webpack[Mix.inProduction() ? 'HashedModuleIdsPlugin': 'NamedModulesPlugin'](),
new WebpackChunkHashPlugin()
);
} else if (Mix.isUsing('hmr')) {
plugins.push(
new webpack.NamedModulesPlugin()
);
}
// Add some general Webpack loader options.
plugins.push(new webpack.LoaderOptionsPlugin({
minimize: Mix.inProduction(),
options: {
context: __dirname,
output: { path: './' }
}
}));
// If we're in production environment, with Uglification turned on, we'll
// clean up and minify all of the user's JS and CSS automatically.
if (Mix.inProduction() && Config.uglify) {
plugins.push(
new UglifyJSPlugin(Config.uglify)
);
}
if (Config.preprocessors.fastSass && Config.preprocessors.fastSass.length) {
plugins.push(
new (require('../plugins/FastSassPlugin'))(Config.preprocessors.fastSass)
);
}
// 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;
};

View File

@@ -0,0 +1,285 @@
let webpack = require('webpack');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = function () {
let rules = [];
let extractPlugins = [];
// Babel Compilation.
rules.push({
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: Config.babel()
}
]
});
// TypeScript Compilation.
if (Mix.isUsing('typeScript')) {
rules.push({
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
});
}
// CSS Compilation.
rules.push({
test: /\.css$/,
exclude: Config.preprocessors.postCss ? Config.preprocessors.postCss.map(postCss => postCss.src.path()) : [],
loaders: ['style-loader', 'css-loader']
});
// Recognize .scss Imports.
rules.push({
test: /\.s[ac]ss$/,
exclude: Config.preprocessors.sass ? Config.preprocessors.sass.map(sass => sass.src.path()) : [],
loaders: ['style-loader', 'css-loader', 'sass-loader']
});
// Recognize .less Imports.
rules.push({
test: /\.less$/,
exclude: Config.preprocessors.less ? Config.preprocessors.less.map(less => less.src.path()) : [],
loaders: ['style-loader', 'css-loader', 'less-loader']
});
// Add support for loading HTML files.
rules.push({
test: /\.html$/,
loaders: ['html-loader']
});
// Add support for loading images.
rules.push({
test: /\.(png|jpe?g|gif)$/,
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|svg|otf)$/,
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
}
});
// Here, we'll filter through all CSS preprocessors that the user has requested.
// For each one, we'll add a new Webpack rule and then prepare the necessary
// extract plugin to extract the CSS into its file.
Object.keys(Config.preprocessors).forEach(type => {
if (type === 'fastSass') return;
Config.preprocessors[type].forEach(preprocessor => {
let outputPath = preprocessor.output.filePath.replace(Config.publicPath + path.sep, path.sep);
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: (type === 'sass' && Config.processCssUrls) ? true : Mix.isUsing('sourcemaps'),
ident: 'postcss',
plugins: (function () {
let plugins = Config.postCss;
if (preprocessor.postCssPlugins && preprocessor.postCssPlugins.length) {
plugins = preprocessor.postCssPlugins;
}
if (Config.autoprefixer) {
plugins.push(require('autoprefixer'));
}
return plugins;
})()
}
},
];
if (type === 'sass' && Config.processCssUrls) {
loaders.push({
loader: 'resolve-url-loader',
options: {
sourceMap: true,
root: Mix.paths.root('node_modules')
}
});
}
if (type !== 'postCss') {
loaders.push({
loader: `${type}-loader`,
options: Object.assign(
preprocessor.pluginOptions,
{ sourceMap: (type === 'sass' && Config.processCssUrls) ? true : Mix.isUsing('sourcemaps') }
)
});
}
rules.push({
test: preprocessor.src.path(),
use: extractPlugin.extract({ fallback: 'style-loader', use: loaders })
});
extractPlugins.push(extractPlugin);
});
});
});
// Vue Compilation.
let vueExtractPlugin;
if (Config.extractVueStyles) {
let fileName = typeof(Config.extractVueStyles) === "string" ? Config.extractVueStyles : 'vue-styles.css';
let filePath = fileName.replace(Config.publicPath, '').replace(/^\//, "");
vueExtractPlugin = extractPlugins.length ? extractPlugins[0] : new ExtractTextPlugin(filePath);
}
rules.push({
test: /\.vue$/,
loader: 'vue-loader',
exclude: /bower_components/,
options: {
// extractCSS: Config.extractVueStyles,
loaders: Config.extractVueStyles ? {
js: {
loader: 'babel-loader',
options: Config.babel()
},
scss: vueExtractPlugin.extract({
use: 'css-loader!sass-loader',
fallback: 'vue-style-loader'
}),
sass: vueExtractPlugin.extract({
use: 'css-loader!sass-loader?indentedSyntax',
fallback: 'vue-style-loader'
}),
css: vueExtractPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
}),
stylus: vueExtractPlugin.extract({
use: 'css-loader!stylus-loader?paths[]=node_modules',
fallback: 'vue-style-loader'
}),
less: vueExtractPlugin.extract({
use: 'css-loader!less-loader',
fallback: 'vue-style-loader'
}),
} : {
js: {
loader: 'babel-loader',
options: Config.babel()
}
},
postcss: Config.postCss,
preLoaders: Config.vue.preLoaders,
postLoaders: Config.vue.postLoaders,
esModule: Config.vue.esModule
}
});
// If there were no existing extract text plugins to add our
// Vue styles extraction too, we'll push a new one in.
if (Config.extractVueStyles && ! extractPlugins.length) {
extractPlugins.push(vueExtractPlugin);
}
// If we want to import a global styles file in every component,
// use sass resources loader
if (Config.extractVueStyles && Config.globalVueStyles) {
tap(rules[rules.length - 1].options.loaders, vueLoaders => {
vueLoaders.scss.push({
loader: 'sass-resources-loader',
options: {
resources: Mix.paths.root(Config.globalVueStyles)
}
});
vueLoaders.sass.push({
loader: 'sass-resources-loader',
options: {
resources: Mix.paths.root(Config.globalVueStyles)
}
});
});
}
return { rules, extractPlugins };
}

View File

@@ -0,0 +1,339 @@
let paths = new (require('./Paths'));
let webpackMerge = require('webpack-merge');
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')),
/**
* The list of scripts to bundle.
*
* @type {Array}
*/
js: [],
/**
* A list of custom assets that are being compiled outside of Webpack.
*
* @type {Array}
*/
customAssets: [],
/**
* The list of vendor chunks to extract.
*
* @type {Array}
*/
extractions: [],
/**
* A list of CSS preprocessing to be performed.
*
* @type {Object}
*/
preprocessors: {},
/**
* Does the project require React support?
*
* @type {Boolean}
*/
react: false,
/**
* Does the project require Preact support?
*
* @type {Boolean}
*/
preact: false,
/**
* Does the project require TypeScript support?
*
* @type {Boolean}
*/
typeScript: false,
/**
* A list of variables that should be autoloaded by webpack.
*
* @type {Object}
*/
autoload: {},
/**
* Does the project require BrowserSync support?
*
* @type {Boolean}
*/
browserSync: false,
/**
* Determine if we should enable hot reloading.
*
* @type {Boolean}
*/
hmr: process.argv.includes('--hot'),
/**
* 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: true,
/**
* 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 we should enable cache busting.
*
* @type {Boolean}
*/
versioning: false,
/**
* 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: '/',
/**
* vue-loader specific options.
*
* @type {Object}
*/
vue: {
preLoaders: {},
postLoaders: {},
esModule: false
},
/**
* 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 {Object}
*/
babel: function () {
let options = {};
tap(Mix.paths.root('.babelrc'), babelrc => {
if (File.exists(babelrc)) {
options = JSON.parse(File.find(babelrc).read());
}
});
let defaultOptions = {
cacheDirectory: true,
presets: [
['env', {
'modules': false,
'targets': {
'browsers': ['> 2%'],
uglify: true
}
}]
],
plugins: [
'transform-object-rest-spread',
['transform-runtime', {
'polyfill': false,
'helpers': false
}]
]
};
if (this.react) {
defaultOptions.presets.push('react');
}
if (this.preact) {
defaultOptions.presets.push('preact');
}
return webpackMerge.smart(defaultOptions, options);
},
/**
* Determine if CSS url()s should be processed by Webpack.
*
* @type {Boolean}
*/
processCssUrls: true,
/**
* Whether to extract .vue component styles into a dedicated file.
* You may provide a boolean, or a dedicated path to extract to.
*
* @type {Boolean|string}
*/
extractVueStyles: false,
/**
* File with global styles to be imported in every component.
*
* See: https://vue-loader.vuejs.org/en/configurations/pre-processors.html#loading-a-global-settings-file
*
* @type {string}
*/
globalVueStyles: '',
/**
* Uglify-specific settings for Webpack.
*
* See: https://github.com/mishoo/UglifyJS2#compressor-options
*
* @type {Object}
*/
uglify: {
sourceMap: true,
uglifyOptions: {
compress: {
warnings: false
},
output: {
comments: false
}
}
},
/**
* 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: {},
/**
* 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);
}
});
}
};
};

View File

@@ -0,0 +1,25 @@
let objectValues = require('lodash').values;
/**
* Generic tap function.
*
* @param {mixed} val
* @param {Function} callback
*/
global.tap = function (val, callback) {
callback(val);
return val;
};
/**
* Flatten the given array.
*
* @param {Array} arr
*/
global.flatten = function (arr) {
return [].concat.apply(
[], objectValues(arr)
);
};

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');
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()) {
new File(
path.join(Config.publicPath, 'hot')
).write('hot reloading');
}
});
/**
* 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.mix = api; // Deprecated.
module.exports.config = Config;

View File

@@ -0,0 +1,22 @@
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;

View File

@@ -0,0 +1,24 @@
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;

View File

@@ -0,0 +1,83 @@
class CustomTasksPlugin {
/**
* Apply the plugin.
*
* @param {Object} compiler
*/
apply(compiler) {
compiler.plugin('done', stats => {
Mix.tasks.forEach(task => this.runTask(task, stats));
if (Mix.isUsing('versioning')) {
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.
*
* @param {File} asset
*/
minifyAssets(asset) {
let tasks = Mix.tasks.filter(task => task.constructor.name !== 'VersionFilesTask');
tasks.forEach(task => {
task.assets.forEach(asset => {
try {
asset.minify();
} catch (e) {
console.log(
`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;

View File

@@ -0,0 +1,26 @@
let StandaloneSass = require('../StandaloneSass');
class FastSassPlugin {
/**
* Create a new plugin instance.
*
* @param {Array} files
*/
constructor(files = []) {
this.files = files;
}
/**
* Apply the plugin.
*/
apply() {
this.files.forEach(sass => {
new StandaloneSass(
sass.src, sass.output.forceFromPublic(), sass.pluginOptions
).run();
});
}
}
module.exports = FastSassPlugin;

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;

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;

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.chunkNames.includes('mix'));
if (temporaryOutputFile) {
delete stats.compilation.assets[temporaryOutputFile.name];
File.find(
path.resolve(Config.publicPath, temporaryOutputFile.name)
).delete();
}
});
}
}
module.exports = MockEntryPlugin;

View File

@@ -0,0 +1,34 @@
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;

View File

@@ -0,0 +1,40 @@
let Task = require('./Task');
let FileCollection = require('../FileCollection');
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), ''));
}
console.log(`Copying ${updatedFile} to ${destination.path()}`);
this.files.copyTo(destination, new File(updatedFile));
}
}
module.exports = CopyFilesTask;

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;

View File

@@ -0,0 +1,34 @@
let Task = require('./Task');
let chokidar = require('chokidar');
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;

File diff suppressed because it is too large Load Diff