This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
+8
View File
@@ -0,0 +1,8 @@
docs/
test/
.github/
.nyc_output
.idea
coverage
mix.sublime-project
mix.sublime-workspace
+21
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.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+143
View File
@@ -0,0 +1,143 @@
{
"_args": [
[
{
"raw": "laravel-mix@0.*",
"scope": null,
"escapedName": "laravel-mix",
"name": "laravel-mix",
"rawSpec": "0.*",
"spec": ">=0.0.0 <1.0.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel"
]
],
"_from": "laravel-mix@>=0.0.0 <1.0.0",
"_id": "laravel-mix@0.12.1",
"_inCache": true,
"_location": "/laravel-mix",
"_nodeVersion": "7.9.0",
"_npmOperationalInternal": {
"host": "s3://npm-registry-packages",
"tmp": "tmp/laravel-mix-0.12.1.tgz_1495976641182_0.37246236158534884"
},
"_npmUser": {
"name": "jeffreyway",
"email": "jeffrey@laracasts.com"
},
"_npmVersion": "4.2.0",
"_phantomChildren": {},
"_requested": {
"raw": "laravel-mix@0.*",
"scope": null,
"escapedName": "laravel-mix",
"name": "laravel-mix",
"rawSpec": "0.*",
"spec": ">=0.0.0 <1.0.0",
"type": "range"
},
"_requiredBy": [
"#DEV:/"
],
"_resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-0.12.1.tgz",
"_shasum": "2b4a5b123ce4bc9167eea49461b7567ae2921503",
"_shrinkwrap": null,
"_spec": "laravel-mix@0.*",
"_where": "c:\\xampp\\htdocs\\laravel",
"author": {
"name": "Jeffrey Way"
},
"bugs": {
"url": "https://github.com/JeffreyWay/laravel-mix/issues"
},
"dependencies": {
"autoprefixer": "^6.7.3",
"babel-core": "^6.20.0",
"babel-loader": "^6.2.9",
"babel-preset-env": "^1.3.0",
"browser-sync": "^2.18.7",
"browser-sync-webpack-plugin": "^1.1.4",
"chokidar": "^1.6.1",
"clean-css": "^4.0.7",
"concatenate": "0.0.2",
"cross-env": "^3.1.3",
"css-loader": "^0.14.5",
"dotenv": "^4.0.0",
"extract-text-webpack-plugin": "^2.0.0-rc.3",
"file-loader": "^0.10.1",
"friendly-errors-webpack-plugin": "^1.5.0",
"fs-extra": "^2.1.2",
"html-loader": "^0.4.4",
"img-loader": "^2.0.0",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"lodash": "^4.17.4",
"md5": "^2.2.1",
"mkdirp": "^0.5.1",
"node-sass": "^4.0.0",
"on-build-webpack": "^0.1.0",
"path": "^0.12.7",
"postcss-load-config": "^1.0.0",
"postcss-loader": "^1.2.1",
"resolve-url-loader": "^2.0.0",
"sass-loader": "^6.0.0",
"style-loader": "^0.13.1",
"uglify-js": "^2.8.27",
"vue-loader": "^11.1.3",
"vue-template-compiler": "^2.0.0",
"webpack": "~2.3.0",
"webpack-chunk-hash": "^0.4.0",
"webpack-dev-server": "^2.4.0",
"webpack-merge": "^4.0.0",
"webpack-notifier": "^1.4.1",
"webpack-stats-plugin": "^0.1.4",
"yargs": "^7.0.2"
},
"description": "Laravel Mix is an elegant wrapper around Webpack for the 80% use case.",
"devDependencies": {
"ava": "^0.19.1",
"nyc": "^10.0.0",
"sinon": "^1.17.7"
},
"directories": {},
"dist": {
"shasum": "2b4a5b123ce4bc9167eea49461b7567ae2921503",
"tarball": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-0.12.1.tgz"
},
"engines": {
"node": ">=6.0.0"
},
"gitHead": "712d4daf07468177eaef4d7f95672513550e914d",
"homepage": "https://github.com/JeffreyWay/laravel-mix#readme",
"keywords": [
"laravel",
"webpack",
"laravel elixir",
"laravel mix"
],
"license": "MIT",
"main": "src/index.js",
"maintainers": [
{
"name": "jeffreyway",
"email": "jeffrey@laracasts.com"
}
],
"name": "laravel-mix",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/JeffreyWay/laravel-mix.git"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --watch --progress --hide-modules",
"hmr": "cross-env NODE_ENV=development webpack-dev-server --inline --hot",
"posttest": "nyc report --reporter=html",
"production": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"test": "sudo nyc ava --verbose",
"webpack": "cross-env NODE_ENV=development webpack --progress --hide-modules"
},
"version": "0.12.1"
}
+19
View File
@@ -0,0 +1,19 @@
<p align="center"><img src="https://laravel.com/assets/img/components/logo-mix.svg" alt="Laravel Mix"></p>
[![npm](https://img.shields.io/npm/v/laravel-mix.svg)](https://www.npmjs.com/package/laravel-mix)
[![npm](https://img.shields.io/npm/dt/laravel-mix.svg)](https://www.npmjs.com/package/laravel-mix)
[![npm](https://img.shields.io/npm/l/laravel-mix.svg)](https://www.npmjs.com/package/laravel-mix)
## 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).
+480
View File
@@ -0,0 +1,480 @@
let path = require('path');
let glob = require('glob');
let webpack = require('webpack');
let Mix = require('laravel-mix').config;
let webpackPlugins = require('laravel-mix').plugins;
let dotenv = require('dotenv')
/*
|--------------------------------------------------------------------------
| Load Environment Variables
|--------------------------------------------------------------------------
|
| Load environment variables from .env file. dotenv will never modify
| any environment variables that have already been set.
|
*/
dotenv.config({
path: Mix.Paths.root('.env')
});
/*
|--------------------------------------------------------------------------
| Mix Initialization
|--------------------------------------------------------------------------
|
| As our first step, we'll require the project's Laravel Mix file
| and record the user's requested compilation and build steps.
| Once those steps have been recorded, we may get to work.
|
*/
Mix.initialize();
/*
|--------------------------------------------------------------------------
| Webpack Context
|--------------------------------------------------------------------------
|
| This prop will determine the appropriate context, when running Webpack.
| Since you have the option of publishing this webpack.config.js file
| to your project root, we will dynamically set the path for you.
|
*/
module.exports.context = Mix.Paths.root();
/*
|--------------------------------------------------------------------------
| Webpack Entry
|--------------------------------------------------------------------------
|
| We'll first specify the entry point for Webpack. By default, we'll
| assume a single bundled file, but you may call Mix.extract()
| to make a separate bundle specifically for vendor libraries.
|
*/
module.exports.entry = Mix.entry().get();
/*
|--------------------------------------------------------------------------
| Webpack Output
|--------------------------------------------------------------------------
|
| Webpack naturally requires us to specify our desired output path and
| file name. We'll simply echo what you passed to with Mix.js().
| Note that, for Mix.version(), we'll properly hash the file.
|
*/
module.exports.output = Mix.output();
/*
|--------------------------------------------------------------------------
| Rules
|--------------------------------------------------------------------------
|
| Webpack rules allow us to register any number of loaders and options.
| Out of the box, we'll provide a handful to get you up and running
| as quickly as possible, though feel free to add to this list.
|
*/
let plugins = [];
if (Mix.options.extractVueStyles) {
var vueExtractTextPlugin = Mix.vueExtractTextPlugin();
plugins.push(vueExtractTextPlugin);
}
let rules = [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: Mix.options.extractVueStyles ? {
js: 'babel-loader' + Mix.babelConfig(),
scss: vueExtractTextPlugin.extract({
use: 'css-loader!sass-loader',
fallback: 'vue-style-loader'
}),
sass: vueExtractTextPlugin.extract({
use: 'css-loader!sass-loader?indentedSyntax',
fallback: 'vue-style-loader'
}),
less: vueExtractTextPlugin.extract({
use: 'css-loader!less-loader',
fallback: 'vue-style-loader'
}),
stylus: vueExtractTextPlugin.extract({
use: 'css-loader!stylus-loader?paths[]=node_modules',
fallback: 'vue-style-loader'
}),
css: vueExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
})
}: {
js: 'babel-loader' + Mix.babelConfig(),
scss: 'vue-style-loader!css-loader!sass-loader',
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
less: 'vue-style-loader!css-loader!less-loader',
stylus: 'vue-style-loader!css-loader!stylus-loader?paths[]=node_modules'
},
postcss: Mix.options.postCss,
preLoaders: Mix.options.vue.preLoaders,
postLoaders: Mix.options.vue.postLoaders
}
},
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader' + Mix.babelConfig()
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader']
},
{
test: /\.html$/,
loaders: ['html-loader']
},
{
test: /\.(png|jpe?g|gif)$/,
loaders: [
{
loader: 'file-loader',
options: {
name: path => {
if (! /node_modules|bower_components/.test(path)) {
return 'images/[name].[ext]?[hash]';
}
return 'images/vendor/' + path
.replace(/\\/g, '/')
.replace(
/((.*(node_modules|bower_components))|images|image|img|assets)\//g, ''
) + '?[hash]';
},
publicPath: Mix.options.resourceRoot
}
},
{
loader: 'img-loader',
options: Mix.options.imgLoaderOptions
}
]
},
{
test: /\.(woff2?|ttf|eot|svg|otf)$/,
loader: 'file-loader',
options: {
name: path => {
if (! /node_modules|bower_components/.test(path)) {
return 'fonts/[name].[ext]?[hash]';
}
return 'fonts/vendor/' + path
.replace(/\\/g, '/')
.replace(
/((.*(node_modules|bower_components))|fonts|font|assets)\//g, ''
) + '?[hash]';
},
publicPath: Mix.options.resourceRoot
}
},
{
test: /\.(cur|ani)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]',
publicPath: Mix.options.resourceRoot
}
}
];
let extensions = ['*', '.js', '.jsx', '.vue'];
if (Mix.ts) {
rules.push({
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
});
extensions.push('.ts', '.tsx');
}
let sassRule = {
test: /\.s[ac]ss$/,
loaders: ['style-loader', 'css-loader', 'sass-loader']
};
if (Mix.preprocessors) {
sassRule.exclude = Mix.preprocessors.map(preprocessor => preprocessor.test());
}
rules.push(sassRule);
if (Mix.preprocessors) {
Mix.preprocessors.forEach(preprocessor => {
rules.push(preprocessor.rules());
plugins.push(preprocessor.extractPlugin);
});
}
module.exports.module = { rules };
/*
|--------------------------------------------------------------------------
| Resolve
|--------------------------------------------------------------------------
|
| Here, we may set any options/aliases that affect Webpack's resolving
| of modules. To begin, we will provide the necessary Vue alias to
| load the Vue common library. You may delete this, if needed.
|
*/
module.exports.resolve = {
extensions,
alias: {
'vue$': 'vue/dist/vue.common.js'
}
};
/*
|--------------------------------------------------------------------------
| Stats
|--------------------------------------------------------------------------
|
| By default, Webpack spits a lot of information out to the terminal,
| each you time you compile. Let's keep things a bit more minimal
| and hide a few of those bits and pieces. Adjust as you wish.
|
*/
module.exports.stats = {
hash: false,
version: false,
timings: false,
children: false,
errors: false
};
process.noDeprecation = true;
module.exports.performance = { hints: false };
/*
|--------------------------------------------------------------------------
| Devtool
|--------------------------------------------------------------------------
|
| Sourcemaps allow us to access our original source code within the
| browser, even if we're serving a bundled script or stylesheet.
| You may activate sourcemaps, by adding Mix.sourceMaps().
|
*/
module.exports.devtool = Mix.options.sourcemaps;
/*
|--------------------------------------------------------------------------
| Webpack Dev Server Configuration
|--------------------------------------------------------------------------
|
| If you want to use that flashy hot module replacement feature, then
| we've got you covered. Here, we'll set some basic initial config
| for the Node server. You very likely won't want to edit this.
|
*/
module.exports.devServer = {
headers: {
"Access-Control-Allow-Origin": "*"
},
historyApiFallback: true,
noInfo: true,
compress: true,
quiet: true
};
/*
|--------------------------------------------------------------------------
| Plugins
|--------------------------------------------------------------------------
|
| Lastly, we'll register a number of plugins to extend and configure
| Webpack. To get you started, we've included a handful of useful
| extensions, for versioning, OS notifications, and much more.
|
*/
plugins.push(
new webpack.ProvidePlugin(Mix.autoload || {}),
new webpackPlugins.FriendlyErrorsWebpackPlugin({ clearConsole: Mix.options.clearConsole }),
new webpackPlugins.StatsWriterPlugin({
filename: 'mix-manifest.json',
transform: Mix.manifest.transform.bind(Mix.manifest),
}),
new webpack.LoaderOptionsPlugin({
minimize: Mix.inProduction,
options: {
postcss: Mix.options.postCss,
context: __dirname,
output: { path: './' }
}
})
);
if (Mix.browserSync) {
plugins.push(
new webpackPlugins.BrowserSyncPlugin(
Object.assign({
host: 'localhost',
port: 3000,
proxy: 'app.dev',
files: [
'app/**/*.php',
'resources/views/**/*.php',
'public/js/**/*.js',
'public/css/**/*.css'
]
}, Mix.browserSync),
{
reload: false
}
)
);
}
if (Mix.options.notifications) {
plugins.push(
new webpackPlugins.WebpackNotifierPlugin({
title: 'Laravel Mix',
alwaysNotify: true,
contentImage: Mix.Paths.root('node_modules/laravel-mix/icons/laravel.png')
})
);
}
if (Mix.copy.length) {
new webpackPlugins.CopyWebpackPlugin(Mix.copy);
}
if (Mix.entry().hasExtractions()) {
plugins.push(
new webpack.optimize.CommonsChunkPlugin({
names: Mix.entry().getExtractions(),
minChunks: Infinity
})
);
}
if (Mix.options.versioning) {
plugins.push(
new webpack[Mix.inProduction ? 'HashedModuleIdsPlugin': 'NamedModulesPlugin'](),
new webpackPlugins.WebpackChunkHashPlugin()
);
} else if (Mix.options.hmr) {
plugins.push(
new webpack.NamedModulesPlugin()
);
}
if (Mix.options.purifyCss) {
let PurifyCSSPlugin = require('purifycss-webpack');
// By default, we'll scan all Blade and Vue files in our project.
let paths = glob.sync(Mix.Paths.root('resources/views/**/*.blade.php')).concat(
Mix.entry().scripts.reduce((carry, js) => {
return carry.concat(glob.sync(js.base + '/**/*.vue'));
}, [])
);
plugins.push(new PurifyCSSPlugin(
Object.assign({ paths }, Mix.options.purifyCss, { minimize: Mix.inProduction })
));
}
if (Mix.inProduction && Mix.options.uglify) {
plugins.push(
new webpack.optimize.UglifyJsPlugin(Mix.options.uglify)
);
}
plugins.push(
new webpack.DefinePlugin(
Mix.definitions({
NODE_ENV: Mix.inProduction
? 'production'
: ( process.env.NODE_ENV || 'development' )
})
),
new webpackPlugins.WebpackOnBuildPlugin(
stats => global.events.fire('build', stats)
)
);
if (! Mix.entry().hasScripts()) {
plugins.push(new webpackPlugins.MockEntryPlugin(Mix.output().path));
}
module.exports.plugins = plugins;
/*
|--------------------------------------------------------------------------
| Mix Finalizing
|--------------------------------------------------------------------------
|
| Now that we've declared the entirety of our Webpack configuration, the
| final step is to scan for any custom configuration in the Mix file.
| If mix.webpackConfig() is called, we'll merge it in, and build!
|
*/
if (Mix.webpackConfig) {
module.exports = require('webpack-merge').smart(
module.exports, Mix.webpackConfig
);
}
+45
View File
@@ -0,0 +1,45 @@
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.extract(vendorLibs);
// mix.sass(src, output);
// mix.standaloneSass('src', output); <-- Faster, but isolated from Webpack.
// mix.less(src, output);
// mix.stylus(src, output);
// 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.
// 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
// });
+397
View File
@@ -0,0 +1,397 @@
let Verify = require('./Verify');
class Api {
/**
* Create a new API instance.
*
* @param {Mix} Mix
*/
constructor(Mix) {
this.Mix = Mix;
}
/**
* Register the Webpack entry/output paths.
*
* @param {string|Array} entry
* @param {string} output
*/
js(entry, output) {
global.entry.addScript(entry, output);
return this;
};
/**
* Declare support for the React framework.
*/
react(entry, output) {
this.Mix.react = true;
Verify.dependency(
'babel-preset-react',
'npm install babel-preset-react --save-dev'
);
this.js(entry, output);
return this;
};
/**
* Declare support for the TypeScript.
*/
ts(entry, output) {
this.Mix.ts = true;
Verify.dependency(
'ts-loader',
'npm install ts-loader typescript --save-dev'
);
this.js(entry, output);
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) {
global.entry.addVendor(libs, output);
return this;
};
/**
* Register libraries to automatically "autoload" when
* the appropriate variable is references in js
*
* @param {object} libs
*/
autoload(libs) {
let aliases = {};
Object.keys(libs).forEach(library => {
[].concat(libs[library]).forEach(alias => {
aliases[alias] = library;
});
});
this.Mix.autoload = aliases;
return this;
};
/**
* Enable Browsersync support for the project.
*
* @param {object} config
*/
browserSync(config = {}) {
if (typeof config === 'string') {
config = { proxy: config };
}
this.Mix.browserSync = config;
return this;
};
/**
* Register Sass compilation.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
sass(src, output, pluginOptions = {}) {
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 = {}) {
let Preprocessor = require('./Preprocessors/StandaloneSass');
this.Mix.standaloneSass = (this.Mix.standaloneSass || []).concat(
new Preprocessor(src, output, pluginOptions)
);
return this;
};
/**
* Register Less compilation.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
*/
less(src, output, pluginOptions = {}) {
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',
'npm install stylus-loader stylus --save-dev'
);
return this.preprocess(
'Stylus', src, output, pluginOptions
);
};
/**
* 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);
global.entry.addStylesheet(src, output);
let Preprocessor = require('./Preprocessors/' + type);
this.Mix.preprocessors = (this.Mix.preprocessors || []).concat(
new Preprocessor(src, output, pluginOptions)
);
return this;
};
/**
* Combine a collection of files.
*
* @param {string|Array} src
* @param {string} output
*/
combine(src, output) {
this.Mix.concat.add({ src, output });
return this;
};
/**
* Alias for this.Mix.combine().
*
* @param {string|Array} src
* @param {string} output
*/
scripts(src, output) {
return this.combine(src, output);
};
/**
* Alias for this.Mix.combine().
*
* @param {string|Array} src
* @param {string} output
*/
styles(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) {
this.Mix.concat.add({ src, output, babel: true });
return this;
};
/**
* Copy one or more files to a new location.
*
* @param {string} from
* @param {string} to
*/
copy(from, to) {
this.Mix.copy.push({ from, to: global.Paths.root(to) });
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);
};
/**
* Minify the provided file.
*
* @param {string|Array} src
*/
minify(src) {
let output = src.replace(/\.([a-z]{2,})$/i, '.min.$1');
this.Mix.concat.add({ src, output });
return this;
};
/**
* Enable sourcemap support.
*
* @param {Boolean} productionToo
*/
sourceMaps(productionToo = true) {
let type = 'cheap-module-eval-source-map';
if (this.Mix.inProduction) {
type = productionToo ? 'cheap-source-map' : false;
}
global.options.sourcemaps = type;
return this;
};
/**
* Enable compiled file versioning.
*
* @param {string|Array} files
*/
version(files = []) {
global.options.versioning = true;
this.Mix.version = [].concat(files);
return this;
};
/**
* Disable all OS notifications.
*/
disableNotifications() {
global.options.notifications = false;
return this;
};
/**
* Set the path to your public folder.
*
* @param {string} path
*/
setPublicPath(path) {
global.options.publicPath = this.Mix.publicPath = new File(path)
.parsePath()
.pathWithoutExt;
return this;
};
/**
* Set prefix for generated asset paths
*
* @param {string} path
*/
setResourceRoot(path) {
global.options.resourceRoot = path;
return this;
};
/**
* Merge custom config with the provided webpack.config file.
*
* @param {object} config
*/
webpackConfig(config) {
this.Mix.webpackConfig = 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',
'npm install purifycss-webpack --save-dev',
true // abortOnComplete
);
}
global.options.merge(options);
return this;
};
/**
* Register a Webpack build event handler.
*
* @param {Function} callback
*/
then(callback) {
global.events.listen('build', callback);
return this;
}
}
module.exports = Api;
+55
View File
@@ -0,0 +1,55 @@
class Collection {
/**
* Create a new Collection instance.
*
* @param {object} items
*/
constructor(items = {}) {
this.items = items;
}
/**
* Add a new key-value pair to the collection.
*
* @param {string} name
* @param {string|Array} files
*/
add(name, files) {
if (! this.items[name]) {
this.items[name] = [];
}
this.items[name] = this.items[name].concat(files);
}
/**
* Get the underlying items for the collection.
*
* @return {Array}
*/
get() {
return this.items;
}
/**
* Determine if there are any items in the collection.
*/
any() {
return Object.keys(this.get()).length > 0;
}
/**
* Empty the collection.
*/
empty() {
this.items = {};
return this;
}
}
module.exports = Collection;
+129
View File
@@ -0,0 +1,129 @@
let md5 = require('md5');
let chokidar = require('chokidar');
let concatenate = require('concatenate');
let babel;
class Concat {
/**
* Create a new Concat instance.
*/
constructor() {
this.combinations = [];
global.events.listen('build', this.run.bind(this))
.listen('init', () => this.watch());
}
/**
* Add a set of files to be combined.
*
* @param {object} files
*/
add(files) {
this.combinations.push({
src: files.src,
output: files.output,
outputOriginal: files.output,
babel: !! files.babel
});
return this;
}
/**
* Watch all relevant files for changes.
*
* @param {object|null} watcher
*/
watch(watcher) {
watcher = watcher || chokidar;
if (! this.shouldWatch() || ! this.any()) return;
this.combinations.forEach(combination => {
watcher.watch(combination.src, { persistent: true })
.on('change', this.combine.bind(this, combination));
});
}
/**
* Determine if file watching should be enabled.
*
* @return {boolean}
*/
shouldWatch() {
return this.any() && process.argv.includes('--watch');
}
/**
* Process combination.
*
* @param {object} files
*/
combine(files) {
let output = File.find(files.output).makeDirectories();
let mergedFileContents = concatenate.sync(files.src, files.output);
if (files.babel && output.fileType === '.js') {
output.write(this.babelify(mergedFileContents));
}
// If file versioning is enabled, then we'll
// rename the output file to apply a hash.
if (global.options.versioning) {
let versionedPath = File.find(files.outputOriginal)
.versionedPath(md5(mergedFileContents));
files.output = output.rename(versionedPath).file;
}
if (process.env.NODE_ENV === 'production' || process.argv.includes('-p')) {
new File(files.output).minify();
}
// We'll now fire an event, so that the Manifest class
// can be refreshed to reflect these new files.
global.events.fire('combined', files);
}
/**
* Apply Babel to the given contents.
*
* @param {string} contents
*/
babelify(contents) {
if (! babel) babel = require('babel-core');
return babel.transform(
contents, { presets: ['env'] }
).code;
}
/**
* Perform all relevant combinations.
*/
run() {
this.combinations.forEach(files => this.combine(files));
return this;
}
/**
* Determine if there are any files to concatenate.
*
* @return {boolean}
*/
any() {
return this.combinations.length > 0;
}
}
module.exports = Concat;
+48
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;
+176
View File
@@ -0,0 +1,176 @@
let Collection = new require('./Collection');
let Verify = require('./Verify');
class Entry {
/**
* Create a new Entry instance.
*/
constructor() {
this.entry = new Collection;
this.scripts = [];
this.extractions = [];
}
/**
* Add a script to the entry.
*
* @param {string|array} entry
* @param {string} output
*/
addScript(entry, output) {
Verify.js(entry, output);
entry = [].concat(entry).map(file => {
return new File(path.resolve(file)).parsePath();
});
output = this.normalizeOutput(output, entry);
this.entry.add(
this.entryName(output),
entry.map(src => src.path)
);
this.scripts = this.scripts.concat(entry);
this.base = output.base.replace(global.options.publicPath, '');
return this;
}
/**
* Add a stylesheet to the entry.
*
* @param {string} src
* @param {string} output
*/
addStylesheet(src, output) {
let name = Object.keys(this.get())[0];
this.entry.add(name, path.resolve(src));
return this;
}
/**
* Add a set of vendor extractions to the entry.
*
* @param {array} libs
* @param {string|null} output
*/
addVendor(libs, output) {
if (! this.hasScripts() && ! output) {
throw new Error(
'Please provide an output path as the second argument to ' +
'mix.extract(), or call mix.js() first.'
);
}
let vendorPath = output
? output.replace(/\.js$/, '').replace(global.options.publicPath, '')
: path.join(this.base, 'vendor').replace(/\\/g, '/');
this.extractions.push(vendorPath);
this.extractionBase = new File(vendorPath).parsePath().base;
this.entry.add(vendorPath, libs);
return this;
}
/**
* Calculate the entry named from the output path.
*
* @param {object} output
*/
entryName(output) {
if (typeof output === 'string') {
output = new File(path.resolve(output)).parsePath();
}
return output.pathWithoutExt
.replace(/\\/g, '/')
.replace(/\.js$/, '')
.replace(global.options.publicPath + '/', '/');
}
/**
* Normalize the full output path.
*
* @param {string} output
* @param {string|array} entry
*/
normalizeOutput(output, entry) {
output = new File(output).parsePath();
if (output.isDir) {
output = new File(
path.join(output.path, entry[0].file)
).parsePath();
}
return output;
}
/**
* Fetch the Webpack-ready entry object.
*/
get() {
if (! this.entry.any()) {
let file = new File(path.resolve(__dirname, 'mock-entry.js'));
this.entry.add('mix', file.path());
}
return this.entry.get();
}
/**
* Determine if there are any registered vendor extractions.
*/
hasExtractions() {
return this.extractions.length > 0;
}
/**
* Fetch the vendor extractions list.
*/
getExtractions() {
// We also need to extract webpack's manifest file,
// so that it doesn't bust the cache.
return this.extractions.concat(
path.join(this.extractionBase, 'manifest').replace(/\\/g, '/')
);
}
/**
* Determine if the requested entry includes script compilation.
*/
hasScripts() {
return this.scripts.length > 0;
}
/**
* Fetch the user requested script compilations.
*/
scripts() {
return this.scripts;
}
/**
* Reset the entry object.
*/
reset() {
return new Entry;
}
}
module.exports = new Entry;
+184
View File
@@ -0,0 +1,184 @@
let fs = require('fs');
let md5 = require('md5');
let chokidar = require('chokidar');
let mkdirp = require('mkdirp');
let options = require('./Options');
let uglify = require('uglify-js');
let UglifyCss = require('clean-css');
class File {
/**
* Create a new File instance.
*
* @param {string} file
*/
constructor(file) {
this.file = file;
this.fileType = path.extname(file);
}
/**
* Static constructor.
*
* @param {string} file
*/
static find(file) {
return new File(file);
}
/**
* Make all nested directories in the current file path.
*/
makeDirectories() {
mkdirp.sync(this.parsePath().base);
return this;
}
/**
* Minify the file, if it is CSS or JS.
*/
minify() {
if (this.fileType === '.js') {
this.write(uglify.minify(this.file, options.uglify).code);
}
if (this.fileType === '.css') {
this.write(
new UglifyCss(options.cleanCss).minify(this.read()).styles
);
}
}
/**
* Determine if the given file exists.
*
* @param {string} file
*/
static exists(file) {
return fs.existsSync(file);
}
/**
* Read the file.
*/
read() {
return fs.readFileSync(this.file, {
encoding: 'utf-8'
});
}
/**
* Write the given contents to the file.
*
* @param {string} body
*/
write(body) {
if (typeof body === 'object') {
body = JSON.stringify(body, null, 2);
}
fs.writeFileSync(this.file, body);
return this;
}
/**
* Delete/Unlink the current file.
*/
delete() {
if (fs.existsSync(this.file)) {
fs.unlinkSync(this.file);
}
}
/**
* Watch the current file for changes.
*
* @param {Function} callback
*/
watch(callback) {
return chokidar.watch(
this.path(), { persistent: true }
).on('change', () => callback(this));
}
/**
* Fetch the full path to the file.
*
* @return {string}
*/
path() {
return path.resolve(this.file);
}
/**
* Version the current file.
*/
version() {
let contents = this.read();
let versionedPath = this.versionedPath(md5(contents));
return new File(versionedPath).write(contents);
}
/**
* Fetch a full, versioned path to the file.
*
* @param {string} hash
*/
versionedPath(hash) {
if (! hash) hash = md5(this.read());
return this.parsePath().hashedPath.replace('[hash]', hash);
}
/**
* Parse the file path into segments.
*/
parsePath() {
let outputSegments = path.parse(this.file);
return {
path: this.file,
pathWithoutExt: path.join(outputSegments.dir, `${outputSegments.name}`),
hashedPath: path.join(outputSegments.dir, `${outputSegments.name}.[hash]${outputSegments.ext}`),
base: outputSegments.dir,
file: outputSegments.base,
hashedFile: `${outputSegments.name}.[hash]${outputSegments.ext}`,
name: outputSegments.name,
isFile: !! outputSegments.ext,
isDir: ! outputSegments.ext,
ext: outputSegments.ext
};
}
/**
* Rename the file.
*
* @param {string} to
*/
rename(to) {
fs.renameSync(this.file, to);
this.file = to;
return this;
}
}
module.exports = File;
+84
View File
@@ -0,0 +1,84 @@
let fs = require('fs-extra');
let chokidar = require('chokidar');
let glob = require('glob');
class FileCollection {
/**
* Create a new FileCollection instance.
*
* @param {string|array} files
*/
constructor(files) {
this.files = files;
}
/**
* Copy the src files to the given destination.
*
* @param {string} destination
* @param {string|array|null} src
* @return {this}
*/
copyTo(destination, src) {
src = src || this.files;
this.destination = destination;
if (Array.isArray(src)) {
src.forEach(file => this.copyTo(this.destination, file));
return this;
}
if (src.includes('*')) {
return this.copyTo(this.destination, glob.sync(src));
}
src = new File(src).parsePath();
let output = this.outputPath(src);
console.log('Copying ' + src.path + ' to ' + output);
fs.copySync(src.path, output);
return this;
}
/**
* Construct the appropriate output path for the copy.
*
* @param {Object} src
* @return {string}
*/
outputPath(src) {
let output = new File(this.destination).parsePath();
// If the src path is a file, but the output is a directory,
// we have to append the src filename to the output path.
if (src.isFile && output.isDir) {
output = path.join(
output.path,
Array.isArray(this.files) ? src.file : src.path.replace(this.files, '')
);
if (new File(output).parsePath().isDir) {
output = path.join(output, src.file);
}
return output;
}
return output.path;
}
/**
* Watch all files in the collection for changes.
*/
watch() {
chokidar.watch(this.files, { persistent: true })
.on('change', updatedFile => this.copyTo(this.destination, updatedFile));
}
}
module.exports = FileCollection;
+174
View File
@@ -0,0 +1,174 @@
let objectValues = require('lodash').values;
let object = require('lodash/fp/object');
class Manifest {
/**
* Create a new Manifest instance.
*/
constructor() {
this.cache = this.exists() ? this.read() : {};
this.manifest = this.cache;
this.registerEvents();
}
/**
* Register any applicable event listeners.
*/
registerEvents() {
global.events.listen('combined', this.appendCombinedFiles.bind(this))
.listen('standalone-sass-compiled', compiledFile => {
this.add(compiledFile);
this.refresh();
});
return this;
}
/**
* Add a key-value pair to the manifest file.
*
* @param {File} file
*/
add(file) {
let original = this.preparePath(file.file);
this.manifest[original] = global.options.versioning ? this.preparePath(file.versionedPath()) : original;
return this;
}
/**
* Get the modified version of the given path.
*
* @param {string} original
*/
get(original) {
if (original) {
if (original instanceof File) original = original.file;
return this.manifest[this.preparePath(original)];
}
return this.manifest;
}
/**
* Transform the Webpack stats into the shape we need.
*
* @param {object} stats
* @param {object} options
*/
transform(stats, options) {
let flattenedPaths = [].concat.apply(
[], objectValues(stats.assetsByChunkName)
);
flattenedPaths.forEach(path => {
path = this.preparePath(path);
if (! path.startsWith('/')) path = ('/'+path);
let original = path.replace(/\.(\w{20}|\w{32})(\..+)/, '$2');
if (Object.keys(this.cache).length) {
let old = this.cache[original];
if(old && File.exists(old.replace(/^\//, ''))) {
File.find(old.replace(/^\//, '')).delete();
}
}
this.manifest[original] = path;
});
return JSON.stringify(this.manifest, null, 2);
}
/**
* Append any mix.combine()'d output paths to the manifest.
*
* @param {Array} toCombine
*/
appendCombinedFiles(toCombine) {
let output = this.preparePath(toCombine.output);
this.manifest[
output.replace(/\.(\w{32})(\..+)/, '$2')
] = output;
this.refresh();
}
/**
* Refresh the mix-manifest.js file.
*/
refresh() {
let manifest = {};
for (let key in this.manifest) {
let val = this.preparePath(this.manifest[key]);
key = this.preparePath(key);
manifest[key] = val;
}
manifest = object.merge(manifest, this.cache);
File.find(this.path()).write(manifest);
}
/**
* Get the path to the manifest file.
*/
path() {
return path.join(global.options.publicPath, 'mix-manifest.json');
}
/**
* Determine if the manifest file exists.
*/
exists() {
return File.exists(this.path());
}
/**
* Retrieve the JSON output from the manifest file.
*/
read() {
return JSON.parse(File.find(this.path()).read());
}
/**
* Prepare the provided path for processing.
*
* @param {string} path
*/
preparePath(path) {
return path.replace(new RegExp('^' + global.options.publicPath), '')
.replace(/\\/g, '/');
}
/**
* Delete the given file from the manifest.
*
* @param {string} file
*/
remove(file) {
File.find(file).delete();
}
}
module.exports = Manifest;
+155
View File
@@ -0,0 +1,155 @@
let Concat = require('./Concat');
let Manifest = require('./Manifest');
let Versioning = require('./Versioning');
class Mix {
/**
* Create a new Laravel Mix instance.
*/
constructor() {
this.concat = new Concat();
this.copy = [];
this.inProduction = options.production;
this.publicPath = options.publicPath;
this.options = global.options; // deprecated
this.Paths = global.Paths;
}
/**
* Initialize the user's webpack.mix.js configuration file.
*/
initialize() {
if (this.isUsingLaravel()) {
this.publicPath = options.publicPath = 'public';
}
this.manifest = new Manifest();
try { require(Paths.mix()); }
catch (e) {}
if (options.versioning) {
this.versioning = new Versioning(this.version, this.manifest).watch();
}
if (this.standaloneSass) {
this.standaloneSass.forEach(sass => sass.run());
}
this.detectHotReloading();
global.events.fire('init', this);
}
/**
* Prepare the Webpack entry object.
*/
entry() {
return global.entry;
}
/**
* Determine the Webpack output path.
*/
output() {
let filename = options.versioning ? '[name].[chunkhash].js' : '[name].js';
let chunkFilename = path.join(
global.entry.base || '', (options.versioning ? '[name].[chunkhash].js' : '[name].js')
);
let http = process.argv.includes('--https') ? 'https' : 'http';
return {
path: path.resolve(options.hmr ? '/' : options.publicPath),
filename: filename,
chunkFilename: chunkFilename.replace(/^\//, ''),
publicPath: options.hmr ? (http + '://localhost:8080/') : ''
};
}
/**
* Detect if the user desires hot reloading.
*
* @param {boolean} force
*/
detectHotReloading(force = false) {
let file = new File(options.publicPath + '/hot');
file.delete();
// If the user wants hot module replacement, we'll create
// a temporary file, so that Laravel can detect it, and
// reference the proper base URL for any assets.
if (options.hmr || force) {
options.hmr = true;
file.write('hot reloading');
}
}
/**
* Fetch the appropriate Babel config for babel-loader.
*/
babelConfig() {
if (File.exists(Paths.root('.babelrc'))) return '?cacheDirectory';
// If the user doesn't have a .babelrc, we'll use our config.
if (this.react) {
options.babel.presets.push('react');
}
return '?' + JSON.stringify(options.babel);
}
/**
* Fetch definitions for DefinePlugin
*
* @param {object} merge
*/
definitions(merge = {}) {
let regex = /^MIX_/i
// Filter out environment variables that doesn't pass regex
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 we can feed into Webpack DefinePlugin
.reduce((value, key) => {
value[key] = JSON.stringify(values[key])
return value
}, {})
};
}
/**
* Determine if we are working with a Laravel project.
*/
isUsingLaravel() {
return File.exists('./artisan');
}
/**
* Fetch the Vue-specific ExtractTextPlugin.
*/
vueExtractTextPlugin() {
let VueExtractTextPluginFactory = require('./Vue/ExtractTextPluginFactory');
return new VueExtractTextPluginFactory(this, options.extractVueStyles).build();
}
};
module.exports = Mix;
+191
View File
@@ -0,0 +1,191 @@
module.exports = {
/**
* Determine if webpack should be triggered in a production environment.
*
* @type {Booolean}
*/
production: (process.env.NODE_ENV === 'production' || process.argv.includes('-p')),
/**
* Determine if we should enable hot reloading.
*
* @type {Boolean}
*/
hmr: process.argv.includes('--hot'),
/**
* Determine if sourcemaps should be created for the build.
*
* @type {Boolean}
*/
sourcemaps: false,
/**
* Determine if notifications should be displayed for each build.
*
* @type {Boolean}
*/
notifications: true,
/**
* The public path for the build.
*
* @type {String}
*/
publicPath: '',
/**
* The resource root for the build.
*
* @type {String}
*/
resourceRoot: '/',
/**
* The default Babel configuration.
*
* @type {Object}
*/
babel: {
cacheDirectory: true,
presets: [
['env', {
'modules': false,
'targets': {
'browsers': ['> 2%'],
uglify: true
}
}]
]
},
/**
* Determine if the bundled assets should be versioned.
*
* @type {Boolean}
*/
versioning: false,
/**
* 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,
/**
* Determine if CSS url()s should be processed by Webpack.
*
* @type {Boolean}
*/
processCssUrls: 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,
/**
* Uglify-specific settings for Webpack.
*
* See: https://github.com/mishoo/UglifyJS2#compressor-options
*
* @type {Object}
*/
uglify: {
sourceMap: true,
compress: {
warnings: false,
drop_console: true
}
},
/**
* img-loader settings for Webpack.
* See: https://github.com/thetalecrafter/img-loader#options
* @type {Object}
*/
imgLoaderOptions: {
enabled: true,
gifsicle: {},
mozjpeg: {},
optipng: {},
svgo: {},
},
/**
* PostCSS plugins to be applied to compiled CSS.
*
* See: https://github.com/postcss/postcss/blob/master/docs/plugins.md
*
* @type {Array}
*/
postCss: [
require('autoprefixer')
],
/**
* vue-loader specific options.
*
* @type {Object}
*/
vue: {
preLoaders: {},
postLoaders: {}
},
/**
* 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,
/**
* CleanCss-specific settings for Webpack.
*
* See: https://github.com/jakubpawlowicz/clean-css#constructor-options
*
* @type {Object}
*/
cleanCss: {
},
/**
* 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);
}
});
}
};
+42
View File
@@ -0,0 +1,42 @@
let argv = require('yargs').argv;
class Paths {
/**
* Create a new Paths instance.
*/
constructor() {
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 argv.env && argv.env.mixfile !== undefined ? this.root(argv.env.mixfile) : this.root('webpack.mix');
}
/**
* Determine the project root.
*
* @param {string|null} append
*/
root(append = '') {
return path.resolve(this.rootPath, append);
}
}
module.exports = Paths;
+15
View File
@@ -0,0 +1,15 @@
let Preprocessor = require('./Preprocessor');
class Less extends Preprocessor {
/**
* Fetch the Webpack loaders for Less.
*/
loaders(sourceMaps) {
return [{
loader: 'less-loader' + (sourceMaps ? '?sourceMap' : ''),
options: this.pluginOptions
}];
}
}
module.exports = Less;
+99
View File
@@ -0,0 +1,99 @@
let ExtractTextPlugin = require('extract-text-webpack-plugin');
class Preprocessor {
/**
* Create a new Preprocessor instance.
*
* @param {string} src
* @param {string} output
* @param {object} pluginOptions
* @param {object} mixOptions
*/
constructor(src, output, pluginOptions) {
src = new File(path.resolve(src)).parsePath();
output = new File(output).parsePath();
if (output.isDir) {
output = new File(
path.join(output.path, src.name + '.css')
).parsePath();
}
this.src = src;
this.output = output;
this.pluginOptions = pluginOptions;
}
/**
* Get the Webpack extract text plugin instance.
*/
getExtractPlugin() {
if (! this.extractPlugin) {
this.extractPlugin = new ExtractTextPlugin(this.outputPath());
}
return this.extractPlugin;
}
/**
* Prepare the Webpack rules for the preprocessor.
*/
rules() {
return {
test: this.test(),
use: this.getExtractPlugin().extract({
fallback: 'style-loader',
use: this.defaultLoaders().concat(this.loaders(global.options.sourcemaps))
})
};
}
/**
* Get the regular expression test for the Extract plugin.
*/
test() {
return new RegExp(this.src.path.replace(/\\/g, '\\\\') + '$');
}
/**
* Fetch the default Webpack loaders.
*/
defaultLoaders() {
let sourceMap = !!global.options.sourcemaps;
return [
{
loader: 'css-loader',
options: {
url: global.options.processCssUrls,
sourceMap: sourceMap
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: sourceMap
}
}
];
}
/**
* Determine the appropriate CSS output path.
*
* @param {object} output
*/
outputPath() {
let regex = new RegExp('^(\.\/)?' + global.options.publicPath);
let pathVariant = global.options.versioning ? 'hashedPath' : 'path';
return this.output[pathVariant].replace(regex, '').replace(/\\/g, '/').replace('[hash]','[contenthash]');
}
}
module.exports = Preprocessor;
+33
View File
@@ -0,0 +1,33 @@
let Preprocessor = require('./Preprocessor');
class Sass extends Preprocessor {
/**
* Fetch the Webpack loaders for Sass.
*/
loaders(sourceMaps) {
let loaders = [
{ loader: 'sass-loader', options: this.sassPluginOptions() }
];
if (global.options.processCssUrls) {
loaders.unshift(
{ loader: 'resolve-url-loader' + (sourceMaps ? '?sourceMap' : '') }
);
}
return loaders;
}
/**
* Fetch the Node-Sass-specififc plugin options.
*/
sassPluginOptions() {
return Object.assign({
precision: 8,
outputStyle: 'expanded'
}, this.pluginOptions, { sourceMap: true })
}
}
module.exports = Sass;
+166
View File
@@ -0,0 +1,166 @@
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) {
src = new File(path.resolve(src)).parsePath();
output = new File(output).parsePath();
if (output.isDir) {
output = new File(
path.join(output.path, src.name + '.css')
).parsePath();
}
this.src = src;
this.output = output;
this.pluginOptions = pluginOptions;
this.shouldWatch = process.argv.includes('--watch');
}
/**
* Run the node-sass compiler.
*/
run() {
this.compile();
if (this.shouldWatch) this.watch();
}
/**
* Compile Sass.
*
* @param {Boolean} watch
*/
compile(watch = false) {
let output = this.output.path;
if (! output.startsWith(options.publicPath)) {
output = path.join(options.publicPath, output);
}
this.command = spawn(
'node-sass', [this.src.path, output].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=' + (global.options.production ? 'compressed' : 'expanded'),
];
if (watch) sassOptions.push('--watch');
if (this.pluginOptions.includePaths) {
this.pluginOptions.includePaths.forEach(
path => sassOptions.push('--include-path=' + path)
);
}
if (global.options.sourcemaps && ! global.options.production) {
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 (/Error/.test(output)) event = 'error';
if (/Wrote CSS/.test(output)) event = 'success';
callback(output, event);
});
}
/**
* Handle successful compilation.
*
* @param {string} output
*/
onSuccess(output) {
console.log("\n");
console.log(output);
if (global.options.notifications) {
notifier.notify({
title: 'Laravel Mix',
message: 'Sass Compilation Successful',
contentImage: 'node_modules/laravel-mix/icons/laravel.png'
});
}
global.events.fire(
'standalone-sass-compiled', File.find(this.output.path)
);
}
/**
* 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 (global.options.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;
+15
View File
@@ -0,0 +1,15 @@
let Preprocessor = require('./Preprocessor');
class Stylus extends Preprocessor {
/**
* Fetch the Webpack loaders for Stylus.
*/
loaders(sourceMaps) {
return [{
loader: 'stylus-loader' + (sourceMaps ? '?sourceMap' : ''),
options: this.pluginOptions
}];
}
}
module.exports = Stylus;
+26
View File
@@ -0,0 +1,26 @@
let glob = require('glob');
class Purify {
/**
* 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 = Purify;
+84
View File
@@ -0,0 +1,84 @@
let assert = require('assert');
let exec = require('child_process').execSync;
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 the 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 the call the 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} dependency
* @param {string} installCommand
* @param {Boolean} abortOnComplete
*/
static dependency(dependency, installCommand, abortOnComplete = false) {
try {
require.resolve(dependency);
} catch (e) {
console.log(
'Additional dependencies must be installed. ' +
'This will only take a moment.'
);
exec(installCommand);
if (abortOnComplete) {
console.log('Finished. Please run Mix again.');
process.exit();
}
}
}
}
module.exports = Verify;
+108
View File
@@ -0,0 +1,108 @@
let objectValues = require('lodash').values;
class Versioning {
/**
* Create a new Versioning instance.
*
* @param {Array} manualFiles
* @param {object} manifest
* @param {string} publicPath
*/
constructor(manualFiles = [], manifest) {
this.manualFiles = manualFiles.map(file => new File(file));
this.manifest = manifest;
this.registerEvents();
}
/**
* Register all relevant event listeners.
*/
registerEvents() {
global.events.listen('standalone-sass-compiled', compiledFile => {
compiledFile.rename(compiledFile.versionedPath());
this.prune();
});
global.events.listen(
['build', 'combined'], () => this.prune()
);
}
/**
* Register a watcher for any files that aren't
* included in Webpack's core bundle process.
*/
watch() {
if (! process.argv.includes('--watch')) return this;
this.manualFiles.forEach(file => {
file.watch(file => {
// Delete the old versioned file.
File.find(
path.join(global.options.publicPath, this.manifest.get(file))
).delete();
// And then whip up a new one.
file.version();
this.prune();
});
});
return this;
}
/**
* Create all hashed files requested by the user,
* when they called mix.version(['file']);
*/
writeHashedFiles() {
this.manualFiles.forEach(file => file.version());
return this;
}
/**
* The user may optionally add extra files to be
* versioned. Here, we'll manually add those to
* Mix's manifest file.
*/
addManualFilesToManifest() {
this.manualFiles.forEach(file => this.manifest.add(file));
}
/**
* Replace all old hashed files with the new versions.
*/
prune() {
this.writeHashedFiles().addManualFilesToManifest();
let cachedFiles = objectValues(this.manifest.cache);
let currentFiles = objectValues(this.manifest.get());
cachedFiles
.filter(file => ! currentFiles.includes(file))
.map(file => {
return file.startsWith(global.options.publicPath)
? file
: path.join(global.options.publicPath, file);
})
.forEach(file => {
this.manifest.remove(file)
});
this.manifest.refresh();
this.manifest.cache = this.manifest.get();
return currentFiles;
}
}
module.exports = Versioning;
+72
View File
@@ -0,0 +1,72 @@
let WebpackExtractPlugin = require('extract-text-webpack-plugin')
class ExtractTextPluginFactory {
/**
* Create a new class instance.
*
* @param {string|boolean} cssPath
*/
constructor(mix, cssPath) {
if (typeof cssPath === 'boolean') {
cssPath = path.join(global.entry.base || '', 'vue-styles.css');
this.useDefault = true;
}
this.mix = mix;
this.path = cssPath;
}
/**
* Build up the necessary ExtractTextPlugin instance.
*/
build() {
if (this.mix.preprocessors) {
// If no output path is provided, we can use the default plugin.
if (this.useDefault) return this.mix.preprocessors[0].getExtractPlugin();
// If what the user passed matches the output to mix.preprocessor(),
// then we can use that plugin instead and append to it.
if (this.pluginIsAlreadyBuilt()) return this.getPlugin();
}
// Otherwise, we'll setup a new plugin to toss the styles into it.
return new WebpackExtractPlugin(this.outputPath());
}
/**
* Check if the the provided path is already registered as an extract instance.
*/
pluginIsAlreadyBuilt() {
return this.mix.preprocessors.find(
preprocessor => preprocessor.output.path === this.path
);
}
/**
* Fetch the Extract plugin instance that matches the current output path.
*/
getPlugin() {
return this.mix.preprocessors.find(
preprocessor => preprocessor.getExtractPlugin().filename === this.outputPath()
).getExtractPlugin();
}
/**
* Prepare the appropriate output path.
*/
outputPath() {
let segments = new File(this.path).parsePath();
let regex = new RegExp('^(\.\/)?' + global.options.publicPath);
let pathVariant = global.options.versioning ? 'hashedPath' : 'path';
return segments[pathVariant].replace(regex, '').replace(/\\/g, '/');
}
}
module.exports = ExtractTextPluginFactory;
+16
View File
@@ -0,0 +1,16 @@
let FileCollection = require('../FileCollection');
/**
* Create a new CopyWebpackPlugin instance.
*
* @param {array} copy
*/
module.exports = function CopyWebpackPlugin(copy) {
copy.forEach(copy => {
let filesToCopy = new FileCollection(copy.from).copyTo(copy.to);
if (process.argv.includes('--watch') || process.argv.includes('--hot')) {
filesToCopy.watch();
}
});
}
+24
View File
@@ -0,0 +1,24 @@
function MockEntryPlugin(outputPath) {
this.outputPath = outputPath;
}
MockEntryPlugin.prototype.apply = function (compiler) {
compiler.plugin('done', stats => {
// If no mix.js() call was requested, we'll also need
// to delete the output script for the user. Since we
// won't know the exact name, we'll hunt it down.
let temporaryOutputFile = stats.toJson()
.assets
.find(asset => asset.chunkNames.includes('mix'));
if (temporaryOutputFile) {
File.find(
path.resolve(this.outputPath, temporaryOutputFile.name)
).delete();
}
delete stats.compilation.assets['mix.js'];
});
};
module.exports = MockEntryPlugin;
+29
View File
@@ -0,0 +1,29 @@
global.options = require('./Options');
global.entry = require('./Entry');
global.path = require('path');
global.Paths = new (require('./Paths'));
global.events = new (require('./Dispatcher'));
global.File = require('./File');
let mix = new (require('./Mix'));
// The default export for this module will in fact
// be the fluent API for your webpack.mix.js file.
module.exports = api = new (require('./Api'))(mix);
module.exports.mix = api; // Deprecated.
// However, you can access the Mix instance like this:
module.exports.config = mix;
// We'll export a handful of common plugins for a cleaner config file.
module.exports.plugins = {
WebpackNotifierPlugin: require('webpack-notifier'),
WebpackOnBuildPlugin: require('on-build-webpack'),
ExtractTextPlugin: require('extract-text-webpack-plugin'),
FriendlyErrorsWebpackPlugin: require('friendly-errors-webpack-plugin'),
StatsWriterPlugin: require('webpack-stats-plugin').StatsWriterPlugin,
WebpackChunkHashPlugin: require('webpack-chunk-hash'),
BrowserSyncPlugin: require('browser-sync-webpack-plugin'),
CopyWebpackPlugin: require('./WebpackPlugins/CopyWebpackPlugin'),
MockEntryPlugin: require('./WebpackPlugins/MockEntryPlugin')
};
View File