ui
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 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.
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// @ts-check
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const pkg = require('../package.json');
|
||||
const { assertSupportedNodeVersion } = require('../src/Engine.js');
|
||||
|
||||
run().catch(err => {
|
||||
console.error(err);
|
||||
|
||||
process.exitCode = process.exitCode || 1;
|
||||
process.exit();
|
||||
});
|
||||
|
||||
/**
|
||||
* Run the program.
|
||||
*/
|
||||
async function run() {
|
||||
const program = new Command();
|
||||
|
||||
program.name('mix');
|
||||
program.version(pkg.version);
|
||||
program.option(
|
||||
'--mix-config <path>',
|
||||
'The path to your Mix configuration file.',
|
||||
'webpack.mix'
|
||||
);
|
||||
program.option('--no-progress', 'Disable progress reporting', false);
|
||||
|
||||
program
|
||||
.command('watch')
|
||||
.description('Build and watch files for changes.')
|
||||
.option('--hot', 'Enable hot reloading.', false)
|
||||
.option('--https', 'Enable https.', false)
|
||||
.action((opts, cmd) =>
|
||||
executeScript('watch', { ...program.opts(), ...opts }, cmd.args)
|
||||
);
|
||||
|
||||
program
|
||||
.command('build', { isDefault: true })
|
||||
.description('Compile Mix.')
|
||||
.option('-p, --production', 'Run Mix in production mode.', false)
|
||||
.action((opts, cmd) =>
|
||||
executeScript('build', { ...program.opts(), ...opts }, cmd.args)
|
||||
);
|
||||
|
||||
await program.parseAsync(process.argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the script.
|
||||
*
|
||||
* @param {"build"|"watch"} cmd
|
||||
* @param {{[key: string]: any}} opts
|
||||
* @param {string[]} args
|
||||
*/
|
||||
async function executeScript(cmd, opts, args = []) {
|
||||
assertSupportedNodeVersion();
|
||||
|
||||
const env = getEffectiveEnv(opts);
|
||||
|
||||
// We MUST use a relative path because the files
|
||||
// created by npm dont correctly handle paths
|
||||
// containg spaces on Windows (yarn does)
|
||||
const configPath = path.relative(
|
||||
process.cwd(),
|
||||
require.resolve('../setup/webpack.config.js')
|
||||
);
|
||||
|
||||
const script = [
|
||||
commandScript(cmd, opts),
|
||||
`--config="${configPath}"`,
|
||||
...quoteArgs(args)
|
||||
].join(' ');
|
||||
|
||||
const scriptEnv = {
|
||||
NODE_ENV: env,
|
||||
MIX_FILE: opts.mixConfig
|
||||
};
|
||||
|
||||
const nodeEnv = requiresLegacyOpenSSLProvider()
|
||||
? { NODE_OPTIONS: process.env.NODE_OPTIONS || `--openssl-legacy-provider` }
|
||||
: {};
|
||||
|
||||
if (isTesting()) {
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
script,
|
||||
env: scriptEnv
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function restart() {
|
||||
let child = spawn(script, {
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: {
|
||||
...process.env,
|
||||
...nodeEnv,
|
||||
...scriptEnv
|
||||
}
|
||||
});
|
||||
|
||||
let shouldOverwriteExitCode = true;
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
// Note adapted from cross-env:
|
||||
// https://github.com/kentcdodds/cross-env/blob/3edefc7b450fe273655664f902fd03d9712177fe/src/index.js#L30-L31
|
||||
|
||||
// The process exit code can be null when killed by the OS (like an out of memory error) or sometimes by node
|
||||
// SIGINT means the _user_ pressed Ctrl-C to interrupt the process execution
|
||||
// Return the appropriate error code in that case
|
||||
if (code === null) {
|
||||
code = signal === 'SIGINT' ? 130 : 1;
|
||||
}
|
||||
|
||||
if (shouldOverwriteExitCode) {
|
||||
process.exitCode = code;
|
||||
}
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
shouldOverwriteExitCode = false;
|
||||
child.kill('SIGINT');
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
shouldOverwriteExitCode = false;
|
||||
child.kill('SIGTERM');
|
||||
});
|
||||
}
|
||||
|
||||
restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command-specific portion of the script.
|
||||
*
|
||||
* @param {"build"|"watch"} cmd
|
||||
* @param {{[key: string]: any}} opts
|
||||
*/
|
||||
function commandScript(cmd, opts) {
|
||||
const showProgress = isTTY() && opts.progress;
|
||||
const script = ['webpack'];
|
||||
|
||||
if (cmd === 'build' && showProgress) {
|
||||
script.push('--progress');
|
||||
} else if (cmd === 'watch' && !opts.hot) {
|
||||
script.push('--watch');
|
||||
|
||||
if (showProgress) {
|
||||
script.push('--progress');
|
||||
}
|
||||
} else if (cmd === 'watch' && opts.hot) {
|
||||
script.push('serve', '--hot');
|
||||
|
||||
if (opts.https) {
|
||||
script.push('--https');
|
||||
}
|
||||
}
|
||||
|
||||
return script.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command arguments with quoted values.
|
||||
*
|
||||
* @param {string[]} args
|
||||
*/
|
||||
function quoteArgs(args) {
|
||||
return args.map(arg => {
|
||||
// Split string at first = only
|
||||
const pattern = /^([^=]+)=(.*)$/;
|
||||
const keyValue = arg.includes('=') ? pattern.exec(arg).slice(1) : [];
|
||||
|
||||
if (keyValue.length === 2) {
|
||||
return `${keyValue[0]}="${keyValue[1]}"`;
|
||||
}
|
||||
|
||||
return arg;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective envirnoment to run in
|
||||
*
|
||||
** @param {{[key: string]: any}} opts
|
||||
*/
|
||||
function getEffectiveEnv(opts) {
|
||||
// If we've requested a production compile we enforce use of the production env
|
||||
// If we don't a user's global NODE_ENV may override and prevent minification of assets
|
||||
if (opts.production) {
|
||||
return 'production';
|
||||
}
|
||||
|
||||
// We use `development` by default or under certain specific conditions when testing
|
||||
if (!process.env.NODE_ENV || (isTesting() && process.env.NODE_ENV === 'test')) {
|
||||
return 'development';
|
||||
}
|
||||
|
||||
// Otherwsise defer to the current value of NODE_ENV
|
||||
return process.env.NODE_ENV;
|
||||
}
|
||||
|
||||
function isTesting() {
|
||||
return process.env.TESTING;
|
||||
}
|
||||
|
||||
function isTTY() {
|
||||
if (isTesting() && process.env.IS_TTY !== undefined) {
|
||||
return process.env.IS_TTY === 'true';
|
||||
}
|
||||
|
||||
if (isTesting() && process.stdout.isTTY === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return process.stdout.isTTY;
|
||||
}
|
||||
|
||||
function requiresLegacyOpenSSLProvider() {
|
||||
if (!process.version.startsWith('v17.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
require('crypto').createHash('md4').update('test').digest('hex');
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
+165
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"name": "laravel-mix",
|
||||
"version": "6.0.43",
|
||||
"description": "The power of webpack, distilled for the rest of us.",
|
||||
"main": "src/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"mix": "./bin/cli.js",
|
||||
"test": "cd test && ava --verbose --timeout=30s --no-worker-threads",
|
||||
"eslint": "eslint setup/ src/ test/ --max-warnings=0"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged"
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
"mix": "./bin/cli.js",
|
||||
"laravel-mix": "./bin/cli.js"
|
||||
},
|
||||
"repository": "JeffreyWay/laravel-mix",
|
||||
"bugs": {
|
||||
"url": "https://github.com/JeffreyWay/laravel-mix/issues"
|
||||
},
|
||||
"homepage": "https://github.com/JeffreyWay/laravel-mix#readme",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"webpack",
|
||||
"laravel elixir",
|
||||
"laravel mix"
|
||||
],
|
||||
"author": "Jeffrey Way",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"icons",
|
||||
"setup",
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.15.8",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.15.8",
|
||||
"@babel/preset-env": "^7.15.8",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/babel__core": "^7.1.16",
|
||||
"@types/clean-css": "^4.2.5",
|
||||
"@types/imagemin-gifsicle": "^7.0.1",
|
||||
"@types/imagemin-mozjpeg": "^8.0.1",
|
||||
"@types/imagemin-optipng": "^5.2.1",
|
||||
"@types/imagemin-svgo": "^8.0.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"babel-loader": "^8.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"chokidar": "^3.5.2",
|
||||
"clean-css": "^5.2.4",
|
||||
"cli-table3": "^0.6.0",
|
||||
"collect.js": "^4.28.5",
|
||||
"commander": "^7.2.0",
|
||||
"concat": "^1.0.3",
|
||||
"css-loader": "^5.2.6",
|
||||
"cssnano": "^5.0.8",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"glob": "^7.2.0",
|
||||
"html-loader": "^1.3.2",
|
||||
"imagemin": "^7.0.1",
|
||||
"img-loader": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"mini-css-extract-plugin": "^1.6.2",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"postcss-load-config": "^3.1.0",
|
||||
"postcss-loader": "^6.2.0",
|
||||
"semver": "^7.3.5",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"terser": "^5.9.0",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"webpack": "^5.60.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.7.3",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"webpack-notifier": "^1.14.1",
|
||||
"webpackbar": "^5.0.0-3",
|
||||
"yargs": "^17.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.15.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.0",
|
||||
"@types/cssnano": "^4.0.1",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/koa": "^2.13.4",
|
||||
"@types/koa-static": "^4.0.2",
|
||||
"@types/lodash": "^4.14.176",
|
||||
"@types/mock-require": "^2.0.0",
|
||||
"@types/react": "^17.0.33",
|
||||
"@types/react-dom": "^17.0.10",
|
||||
"@types/semver": "^7.3.9",
|
||||
"@types/sinon": "^10.0.6",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/yargs": "^17.0.2",
|
||||
"@vue/compiler-sfc": "^3.2.20",
|
||||
"ava": "^4.0.0-rc.1",
|
||||
"browserslist": "^4.17.5",
|
||||
"buffer": "^6.0.3",
|
||||
"coffee-loader": "^3.0.0",
|
||||
"coffeescript": "^2.6.1",
|
||||
"core-js": "^3.19.0",
|
||||
"eol": "^0.9.1",
|
||||
"eslint": "^7.28.0",
|
||||
"husky": "^4.3.0",
|
||||
"koa": "^2.13.4",
|
||||
"koa-static": "^5.0.0",
|
||||
"less": "^3.12.2 || ^4.1.2",
|
||||
"less-loader": "^9.1.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"normalize.css": "^8.0.1",
|
||||
"playwright": "^1.16.2",
|
||||
"postcss": "^8.3.11",
|
||||
"postcss-custom-properties": "^11.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"pretty-quick": "^3.1.1",
|
||||
"process": "^0.11.10",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-refresh": "^0.10.0",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.43.4",
|
||||
"sass-loader": "^12.3.0",
|
||||
"sass-resources-loader": "^2.2.4",
|
||||
"sinon": "^11.1.2",
|
||||
"stylus": "^0.55.0",
|
||||
"stylus-loader": "^6.2.0",
|
||||
"supertest": "^6.1.6",
|
||||
"temp-sandbox": "^4.0.1",
|
||||
"ts-loader": "^9.2.6",
|
||||
"tsc": "^2.0.3",
|
||||
"typescript": "^4.4.4",
|
||||
"vue-loader15": "npm:vue-loader@^15.9.8",
|
||||
"vue-loader16": "npm:vue-loader@^16.8.2",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vue2": "npm:vue@^2.6.14",
|
||||
"vue3": "npm:vue@^3.2.20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.15.8",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.15.8",
|
||||
"@babel/preset-env": "^7.15.8",
|
||||
"postcss": "^8.3.11",
|
||||
"webpack": "^5.60.0",
|
||||
"webpack-cli": "^4.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.14.0"
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<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/node/v/laravel-mix.svg" alt="Node"></a>
|
||||
<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 applications. 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](docs/).
|
||||
|
||||
## License
|
||||
|
||||
Laravel Mix is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
const { assertSupportedNodeVersion } = require('../src/Engine');
|
||||
|
||||
module.exports = async () => {
|
||||
// @ts-ignore
|
||||
process.noDeprecation = true;
|
||||
|
||||
assertSupportedNodeVersion();
|
||||
|
||||
const mix = require('../src/Mix').primary;
|
||||
|
||||
require(mix.paths.mix());
|
||||
|
||||
await mix.installDependencies();
|
||||
await mix.init();
|
||||
|
||||
return mix.build();
|
||||
};
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
let assert = require('assert');
|
||||
let File = require('./File');
|
||||
|
||||
class Assert {
|
||||
/**
|
||||
* Assert that the call the mix.js() is valid.
|
||||
*
|
||||
* @param {any} entry
|
||||
* @param {any} output
|
||||
*/
|
||||
static js(entry, output) {
|
||||
assert(
|
||||
typeof entry === 'string' || Array.isArray(entry),
|
||||
'mix.js() is missing required parameter 1: entry'
|
||||
);
|
||||
|
||||
assert(
|
||||
typeof output === 'string',
|
||||
'mix.js() is missing required parameter 2: output'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the calls to mix.sass() and mix.less() are valid.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} src
|
||||
* @param {string} output
|
||||
*/
|
||||
static preprocessor(type, src, output) {
|
||||
assert(
|
||||
typeof src === 'string',
|
||||
`mix.${type}() is missing required parameter 1: src`
|
||||
);
|
||||
|
||||
assert(
|
||||
typeof output === 'string',
|
||||
`mix.${type}() is missing required parameter 2: output`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that calls to mix.combine() are valid.
|
||||
*
|
||||
* @param {string|string[]} src
|
||||
* @param {File} output
|
||||
*/
|
||||
static combine(src, output) {
|
||||
assert(
|
||||
typeof src === 'string' || Array.isArray(src),
|
||||
`mix.combine() requires a valid src string or array.`
|
||||
);
|
||||
|
||||
assert(
|
||||
output.isFile(),
|
||||
'mix.combine() requires a full output file path as the second argument. Got ' +
|
||||
output.path()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Assert;
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
const babel = require('@babel/core');
|
||||
|
||||
/**
|
||||
* @typedef {string|string[]} BabelTargetsBrowserslist
|
||||
* @typedef {Record<string,string> & {browsers: BabelTargetsBrowserslist}} BabelTargetsPerEngine
|
||||
* @typedef {BabelTargetsBrowserslist | BabelTargetsPerEngine} BabelTargets
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} AdditionalBabelOptions
|
||||
* @property {boolean} [cacheDirectory]
|
||||
* @property {BabelTargets} [targets]
|
||||
*/
|
||||
|
||||
/** @typedef {import("@babel/core").TransformOptions & AdditionalBabelOptions} BabelOptions */
|
||||
|
||||
class BabelConfig {
|
||||
/**
|
||||
*
|
||||
* @param {import("./Mix")} [mix]
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the appropriate Babel configuration for the build.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static generate() {
|
||||
return new BabelConfig().generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the appropriate Babel configuration for the build.
|
||||
*/
|
||||
generate() {
|
||||
return this.mergeAll([
|
||||
this.default(),
|
||||
this.getCustomConfig(this.mix.config.babelConfig),
|
||||
{
|
||||
root: this.mix.paths.root(),
|
||||
babelrc: true,
|
||||
configFile: true,
|
||||
babelrcRoots: ['.', this.mix.paths.root()]
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user's .babelrc config file, if any.
|
||||
*
|
||||
* @param {string} path
|
||||
* @deprecated
|
||||
*/
|
||||
fetchBabelRc(path) {
|
||||
const File = require('./File');
|
||||
|
||||
return File.exists(path) ? JSON.parse(File.find(path).read()) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the babel config setup when using mix.babelConfig()
|
||||
*
|
||||
* @internal
|
||||
* @param {import('@babel/core').TransformOptions} customOptions
|
||||
*/
|
||||
getCustomConfig(customOptions) {
|
||||
const config = babel.loadPartialConfig(customOptions);
|
||||
|
||||
return config ? config.options : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge babel configs
|
||||
*
|
||||
* @param {BabelOptions[]} configs
|
||||
*/
|
||||
mergeAll(configs) {
|
||||
const options = configs.reduce((prev, current) => {
|
||||
const presets = [...(prev.presets || []), ...(current.presets || [])].map(
|
||||
preset => babel.createConfigItem(preset, { type: 'preset' })
|
||||
);
|
||||
|
||||
const plugins = [...(prev.plugins || []), ...(current.plugins || [])].map(
|
||||
preset => babel.createConfigItem(preset, { type: 'plugin' })
|
||||
);
|
||||
|
||||
return Object.assign(prev, current, { presets, plugins });
|
||||
});
|
||||
|
||||
options.plugins = this.filterConfigItems(options.plugins || []);
|
||||
options.presets = this.filterConfigItems(options.presets || []);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter merged presets or plugins
|
||||
*
|
||||
* @internal
|
||||
* @param {import("@babel/core").PluginItem[]} items
|
||||
* @returns {import("@babel/core").PluginItem[]}
|
||||
*/
|
||||
filterConfigItems(items) {
|
||||
/**
|
||||
*
|
||||
* @param {import("@babel/core").PluginItem[]} unique
|
||||
* @param {import("@babel/core").PluginItem} item
|
||||
* @returns
|
||||
*/
|
||||
function dedupe(unique, item) {
|
||||
if (item.file != null) {
|
||||
const toDeleteIndex = unique.findIndex(
|
||||
element =>
|
||||
element.file && element.file.resolved === item.file.resolved
|
||||
);
|
||||
|
||||
if (toDeleteIndex >= 0) {
|
||||
unique.splice(toDeleteIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return [...unique, item];
|
||||
}
|
||||
|
||||
return items.reduce((unique, configItem) => dedupe(unique, configItem), []);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
static default() {
|
||||
return new BabelConfig().default();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the default Babel configuration.
|
||||
*
|
||||
* @internal
|
||||
* @returns {BabelOptions}
|
||||
*/
|
||||
default() {
|
||||
return {
|
||||
cacheDirectory: true,
|
||||
presets: [
|
||||
['@babel/preset-env', { modules: false, forceAllTransforms: true }]
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
[
|
||||
'@babel/plugin-transform-runtime',
|
||||
{
|
||||
helpers: false
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BabelConfig;
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
let path = require('path');
|
||||
|
||||
/** @typedef {import("webpack").Module} Module */
|
||||
/** @typedef {object} OptimizationSplitChunksCacheGroup */
|
||||
|
||||
/**
|
||||
* @typedef {Object} CacheGroupsContext
|
||||
* @property {import("webpack").ModuleGraph} moduleGraph
|
||||
* @property {import("webpack").ChunkGraph} chunkGraph
|
||||
*/
|
||||
|
||||
/** @typedef {any} CacheGroup */
|
||||
|
||||
/**
|
||||
* @typedef {(module: import("webpack").Module, context: CacheGroupsContext) => boolean} ChunkTestCallback
|
||||
* @typedef {undefined|boolean|string|RegExp|ChunkTestCallback} ChunkTest
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(chunk: CacheGroup, id: string) => boolean} ChunkFindCallback
|
||||
*/
|
||||
|
||||
class Chunks {
|
||||
/** @type {Chunks|null} */
|
||||
static _instance = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("./Mix")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
// TODO: Simplify in Mix 7 -- Here for backwards compat if a plugin creates this class directly
|
||||
this.mix = mix || global.Mix;
|
||||
|
||||
/** @type {{[key: string]: CacheGroup}} */
|
||||
this.chunks = {};
|
||||
|
||||
/** @type {import("./builder/Entry.js")|null} */
|
||||
this.entry = null;
|
||||
this.runtime = false;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
makeCurrent() {
|
||||
Chunks._instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return {Chunks}
|
||||
*/
|
||||
static instance() {
|
||||
return Chunks._instance || this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return {Chunks}
|
||||
*/
|
||||
static reset() {
|
||||
return (Chunks._instance = new Chunks(global.Mix));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique identifier for this chunk. Multiple chunks with the same ID are merged.
|
||||
* @param {string} path The output path for this chunk
|
||||
* @param {ChunkTest|ChunkTest[]} test A test that tells webpack how to determine what to put in this chunk
|
||||
* @param {Partial<CacheGroup>} attrs
|
||||
*/
|
||||
add(id, path, test, attrs = {}) {
|
||||
this.create(id, path, attrs).addTo(id, test);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique identifier for this chunk. Multiple chunks with the same ID are merged.
|
||||
* @param {string} path The output path for this chunk
|
||||
* @param {Partial<OptimizationSplitChunksCacheGroup>} attrs
|
||||
*/
|
||||
create(id, path, attrs = {}) {
|
||||
this.chunks[id] = this.chunks[id] || {
|
||||
name: path,
|
||||
...attrs
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} idOrPath
|
||||
* @param {ChunkTest|ChunkTest[]} test
|
||||
*/
|
||||
addTo(idOrPath, test) {
|
||||
const chunk = this.find(idOrPath);
|
||||
|
||||
if (Array.isArray(test)) {
|
||||
test = this._checkAllTests(test);
|
||||
}
|
||||
|
||||
if (chunk.test) {
|
||||
test = this._checkAnyTests([chunk.test, test]);
|
||||
}
|
||||
|
||||
chunk.test = test;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|ChunkFindCallback} idOrPath
|
||||
* @returns {CacheGroup|null}
|
||||
*/
|
||||
find(idOrPath) {
|
||||
if (typeof idOrPath === 'string') {
|
||||
if (this.chunks[idOrPath]) {
|
||||
return this.chunks[idOrPath];
|
||||
}
|
||||
|
||||
return this.find((_, id) => id === idOrPath);
|
||||
}
|
||||
|
||||
const item = Object.entries(this.chunks).find(([id, chunk]) =>
|
||||
idOrPath(chunk, id)
|
||||
);
|
||||
|
||||
return item ? item[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {import('webpack').Configuration}
|
||||
*/
|
||||
config() {
|
||||
return {
|
||||
optimization: {
|
||||
...this.runtimeChunk(),
|
||||
...this.splitChunks()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
runtimeChunk() {
|
||||
if (!this.runtime || !this.entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeChunk: {
|
||||
name: path
|
||||
.join(this.mix.config.runtimeChunkPath || this.entry.base, 'manifest')
|
||||
.replace(/\\/g, '/')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
splitChunks() {
|
||||
return {
|
||||
splitChunks: {
|
||||
...this.cacheGroups()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cacheGroups() {
|
||||
return {
|
||||
cacheGroups: {
|
||||
/** @type {false} */
|
||||
default: false,
|
||||
/** @type {false} */
|
||||
defaultVendors: false,
|
||||
...this.chunks
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a chunk should be included based on multiple tests
|
||||
*
|
||||
* This is for internal use only and may be changed or removed at any time
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param {(undefined|boolean|string|RegExp|Function)[]} tests
|
||||
* @returns {ChunkTestCallback}
|
||||
*/
|
||||
_checkAllTests(tests) {
|
||||
return (module, context) =>
|
||||
tests.every(test => this._checkTest(test, module, context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a chunk should be included based on multiple tests
|
||||
*
|
||||
* This is for internal use only and may be changed or removed at any time
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param {(undefined|boolean|string|RegExp|Function)[]} tests
|
||||
* @returns {ChunkTestCallback}
|
||||
*/
|
||||
_checkAnyTests(tests) {
|
||||
return (module, context) =>
|
||||
tests.some(test => this._checkTest(test, module, context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a chunk should be included
|
||||
*
|
||||
* NOTE: This repeats the code from the SplitChunksPlugin checkTest function
|
||||
* This is for internal use only and may be changed or removed at any time
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param {undefined|boolean|string|RegExp|Function} test test option
|
||||
* @param {import("webpack").Module} module the module
|
||||
* @param {CacheGroupsContext} context context object
|
||||
* @returns {boolean} true, if the module should be selected
|
||||
*/
|
||||
_checkTest(test, module, context) {
|
||||
if (this._checkModuleTest(test, module, context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const issuer = context.moduleGraph.getIssuer(module);
|
||||
|
||||
if (issuer) {
|
||||
return this._checkTest(test, issuer, context);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a chunk should be included
|
||||
*
|
||||
* NOTE: This repeats the code from the SplitChunksPlugin checkTest function
|
||||
* This is for internal use only and may be changed or removed at any time
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param {undefined|boolean|string|RegExp|Function} test test option
|
||||
* @param {import("webpack").Module} module the module
|
||||
* @param {any} context context object
|
||||
* @returns {boolean} true, if the module should be selected
|
||||
*/
|
||||
_checkModuleTest(test, module, context) {
|
||||
if (test === undefined) return true;
|
||||
if (typeof test === 'function') {
|
||||
return test(module, context);
|
||||
}
|
||||
if (typeof test === 'boolean') return test;
|
||||
if (typeof test === 'string') {
|
||||
const name = module.nameForCondition();
|
||||
|
||||
return (name && name.startsWith(test)) || false;
|
||||
}
|
||||
if (test instanceof RegExp) {
|
||||
const name = module.nameForCondition();
|
||||
|
||||
return (name && test.test(name)) || false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Chunks = Chunks;
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
let Log = require('./Log');
|
||||
let PackageManager = require('./PackageManager');
|
||||
let { PackageDependency } = require('./PackageDependency');
|
||||
|
||||
class Dependencies {
|
||||
/** @type {PackageDependency[]} */
|
||||
items = [];
|
||||
|
||||
requiresReload = false;
|
||||
|
||||
/**
|
||||
* Create a new Dependencies instance.
|
||||
*
|
||||
* @param {import('./PackageDependency').Dependency[]} deps
|
||||
*/
|
||||
enqueue(deps, requiresReload = false) {
|
||||
this.items.push(...deps.filter(dep => dep).map(PackageDependency.from));
|
||||
this.requiresReload = this.requiresReload || requiresReload;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install all dependencies that aren't available.
|
||||
*/
|
||||
async install() {
|
||||
const packages = await this.installablePackages();
|
||||
|
||||
if (!packages.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.execute(this.buildInstallCommand(packages));
|
||||
}
|
||||
|
||||
async installablePackages() {
|
||||
const results = await Promise.all(
|
||||
this.items.map(async dep => {
|
||||
if (await dep.needsInstallation()) {
|
||||
return dep;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
return /** @type {PackageDependency[]} */ (results.filter(Boolean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the provided console command.
|
||||
*
|
||||
* @param {string} command
|
||||
*/
|
||||
async execute(command) {
|
||||
Log.feedback(
|
||||
'Additional dependencies must be installed. This will only take a moment.'
|
||||
);
|
||||
|
||||
Log.feedback(`Running: ${command}`);
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
|
||||
await promisify(childProcess.exec)(command);
|
||||
|
||||
this.respond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the dependency install command.
|
||||
*
|
||||
* @param {PackageDependency[]} deps
|
||||
*/
|
||||
buildInstallCommand(deps) {
|
||||
const list = deps.map(dep => dep.package).join(' ');
|
||||
|
||||
switch (PackageManager.detect()) {
|
||||
case 'npm':
|
||||
return `npm install ${list} --save-dev --legacy-peer-deps`;
|
||||
|
||||
case 'yarn':
|
||||
return `yarn add ${list} --dev`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the install process.
|
||||
*/
|
||||
respond() {
|
||||
if (!this.requiresReload) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.feedback('Finished. Please run Mix again.');
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Dependencies;
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/** @typedef {(...args: any) => void|Promise<void>} Handler */
|
||||
|
||||
const { concat } = require('lodash');
|
||||
|
||||
class Dispatcher {
|
||||
/**
|
||||
* Create a new Dispatcher instance.
|
||||
*/
|
||||
constructor() {
|
||||
/** @type {Record<string, Handler[]>} */
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for the given event.
|
||||
*
|
||||
* @param {string|string[]} events
|
||||
* @param {Handler} 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 {any} [data]
|
||||
*/
|
||||
async fire(event, data) {
|
||||
if (!this.events[event]) return false;
|
||||
|
||||
return Promise.all(this.events[event].map(handler => handler(data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all registered event listeners.
|
||||
*/
|
||||
all() {
|
||||
return this.events;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Dispatcher;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
const semver = require('semver');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
module.exports.assertSupportedNodeVersion = function assertSupportedNodeVersion() {
|
||||
if (!semver.satisfies(process.versions.node, pkg.engines.node)) {
|
||||
throw new Error(
|
||||
`You are using an unsupported version of Node. Please update to at least Node v12.14`
|
||||
);
|
||||
}
|
||||
};
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
let os = require('os');
|
||||
let md5 = require('md5');
|
||||
let path = require('path');
|
||||
let fs = require('fs-extra');
|
||||
let Terser = require('terser');
|
||||
let UglifyCss = require('clean-css');
|
||||
const { escapeRegExp } = require('lodash');
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given file exists.
|
||||
*/
|
||||
exists() {
|
||||
return fs.existsSync(this.path());
|
||||
}
|
||||
|
||||
normalizedOutputPath() {
|
||||
let path = this.pathFromPublic(this.mix.config.publicPath);
|
||||
|
||||
path = File.stripPublicDir(path);
|
||||
|
||||
return path
|
||||
.replace(/\.(js|css)$/, '')
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^\//, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this.mix.paths.root(), this.path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative path to the file, from the project root.
|
||||
*/
|
||||
relativePathWithoutExtension() {
|
||||
return path.relative(this.mix.paths.root(), this.pathWithoutExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = null) {
|
||||
publicPath = publicPath || this.mix.config.publicPath;
|
||||
|
||||
if (!this.relativePath().startsWith(publicPath)) {
|
||||
return new File(path.join(publicPath, this.relativePath()));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @param {string|null} publicPath
|
||||
*/
|
||||
static stripPublicDir(filePath, publicPath = null) {
|
||||
let publicDir = path.basename(publicPath || this.mix.config.publicPath);
|
||||
|
||||
publicDir = escapeRegExp(publicDir);
|
||||
|
||||
return filePath.replace(new RegExp(`^[/\\\\]?${publicDir}[/\\\\]`), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the file, starting at the project's public dir.
|
||||
*
|
||||
* @param {string|null} [publicPath]
|
||||
*/
|
||||
pathFromPublic(publicPath) {
|
||||
publicPath = publicPath || this.mix.config.publicPath;
|
||||
|
||||
let extra = this.filePath.startsWith(publicPath) ? publicPath : '';
|
||||
|
||||
// If the path starts with the public folder remove it
|
||||
if (
|
||||
this.filePath.startsWith(
|
||||
path.normalize(`${publicPath}/${path.basename(publicPath)}`)
|
||||
)
|
||||
) {
|
||||
extra += `/${path.basename(publicPath)}`;
|
||||
}
|
||||
|
||||
return path.normalize(
|
||||
'/' + path.relative(this.mix.paths.root(extra), this.path())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the file without query string.
|
||||
*/
|
||||
pathWithoutQueryString() {
|
||||
const queryStringIndex = this.path().indexOf('?');
|
||||
|
||||
return queryStringIndex < 0
|
||||
? this.path()
|
||||
: this.path().substr(0, queryStringIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base directory of the file.
|
||||
*/
|
||||
base() {
|
||||
return this.segments.base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file is a directory.
|
||||
*/
|
||||
isDirectory() {
|
||||
return this.segments.isDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the path is a file, and not a directory.
|
||||
*/
|
||||
isFile() {
|
||||
return this.segments.isFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given contents to the file.
|
||||
*
|
||||
* @param {string} body
|
||||
*/
|
||||
write(body) {
|
||||
if (typeof body === 'object') {
|
||||
body = JSON.stringify(body, null, 4);
|
||||
}
|
||||
|
||||
body = body + os.EOL;
|
||||
|
||||
fs.writeFileSync(this.absolutePath, body);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the file's contents.
|
||||
*/
|
||||
read() {
|
||||
return fs.readFileSync(this.pathWithoutQueryString(), 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the proper version hash for the file.
|
||||
*/
|
||||
version() {
|
||||
return md5(this.read());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all nested directories.
|
||||
*/
|
||||
makeDirectories() {
|
||||
fs.mkdirSync(this.base(), { mode: 0o777, recursive: true });
|
||||
|
||||
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.
|
||||
*/
|
||||
async minify() {
|
||||
if (this.extension() === '.js') {
|
||||
const output = await Terser.minify(
|
||||
this.read(),
|
||||
this.mix.config.terser.terserOptions
|
||||
);
|
||||
|
||||
if (output.code !== undefined) {
|
||||
this.write(output.code);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.extension() === '.css') {
|
||||
const output = await new UglifyCss(this.mix.config.cleanCss).minify(
|
||||
this.read()
|
||||
);
|
||||
|
||||
this.write(output.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);
|
||||
|
||||
let isDir = this.checkIsDir(parsed);
|
||||
|
||||
return {
|
||||
path: this.filePath,
|
||||
absolutePath: this.absolutePath,
|
||||
pathWithoutExt: path.join(parsed.dir, `${parsed.name}`),
|
||||
isDir,
|
||||
isFile: !isDir,
|
||||
name: parsed.name,
|
||||
ext: parsed.ext,
|
||||
file: parsed.base,
|
||||
base: parsed.dir
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a bit more involved check to verify if a file is a directory
|
||||
*
|
||||
* @param {path.ParsedPath} parsed
|
||||
* @private
|
||||
*/
|
||||
checkIsDir(parsed) {
|
||||
// 1. If the file exists and is a directory
|
||||
try {
|
||||
return fs.lstatSync(this.absolutePath).isDirectory();
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
// 2. If the path ends in a slash
|
||||
if (this.absolutePath.endsWith('/')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Pevious logic: No extension & does not end in a wildcard
|
||||
return !parsed.ext && !parsed.name.endsWith('*');
|
||||
}
|
||||
|
||||
// TODO: Can we refactor this to remove the need for implicit global? Or does this one make sense to leave as is?
|
||||
get mix() {
|
||||
// TODO: Use warning-less version here
|
||||
return global.Mix;
|
||||
}
|
||||
|
||||
static get mix() {
|
||||
// TODO: Use warning-less version here
|
||||
return global.Mix;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = File;
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
let concat = require('concat');
|
||||
let path = require('path');
|
||||
let fs = require('fs');
|
||||
let babel = require('@babel/core');
|
||||
let glob = require('glob');
|
||||
let _ = require('lodash');
|
||||
let Log = require('./Log');
|
||||
let File = require('./File');
|
||||
|
||||
class FileCollection {
|
||||
/**
|
||||
* Create a new FileCollection instance.
|
||||
*
|
||||
* @param {string|string[]} files
|
||||
*/
|
||||
constructor(files = []) {
|
||||
/** @type {string[]} */
|
||||
this.files = _.concat([], files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the underlying files.
|
||||
*/
|
||||
get() {
|
||||
return this.files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge all files in the collection into one.
|
||||
*
|
||||
* @param {File} output
|
||||
* @param {boolean} wantsBabel
|
||||
*/
|
||||
async merge(output, wantsBabel = false) {
|
||||
/** @type {string} */
|
||||
// @ts-ignore
|
||||
const contents = await concat(this.files, output.makeDirectories().path());
|
||||
|
||||
if (this.shouldCompileWithBabel(wantsBabel, output)) {
|
||||
const code = this.babelify(contents);
|
||||
|
||||
if (code) {
|
||||
output.write(code);
|
||||
}
|
||||
}
|
||||
|
||||
return new File(output.makeDirectories().path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should add a Babel pass to the concatenated file.
|
||||
*
|
||||
* @param {Boolean} wantsBabel
|
||||
* @param {File} output
|
||||
*/
|
||||
shouldCompileWithBabel(wantsBabel, output) {
|
||||
return wantsBabel && output.extension() === '.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Babel to the given contents.
|
||||
*
|
||||
* @param {string} contents
|
||||
*/
|
||||
babelify(contents) {
|
||||
let babelConfig = this.mix.config.babel();
|
||||
|
||||
delete babelConfig.cacheDirectory;
|
||||
|
||||
const result = babel.transform(contents, babelConfig);
|
||||
|
||||
return result && result.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the src files to the given destination.
|
||||
*
|
||||
* @param {File} destination
|
||||
* @param {string[]|File} [src]
|
||||
* @return {void|string}
|
||||
*/
|
||||
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()) {
|
||||
src.copyTo(destination.path());
|
||||
|
||||
this.assets = fs.readdirSync(src.path()).map(file => {
|
||||
return new File(path.resolve(destination.path(), file));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (src.contains('*')) {
|
||||
let files = glob.sync(src.path(), { nodir: true });
|
||||
|
||||
if (!files.length) {
|
||||
Log.feedback(`Notice: The ${src.path()} search produced no matches.`);
|
||||
}
|
||||
|
||||
return this.copyTo(destination, files);
|
||||
}
|
||||
|
||||
if (destination.isDirectory()) {
|
||||
destination = destination.append(src.name());
|
||||
}
|
||||
|
||||
src.copyTo(destination.path());
|
||||
|
||||
this.assets = this.assets.concat(destination);
|
||||
|
||||
return destination.path();
|
||||
}
|
||||
|
||||
get mix() {
|
||||
return global.Mix;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileCollection;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
let path = require('path');
|
||||
let File = require('./File');
|
||||
|
||||
class HotReloading {
|
||||
/**
|
||||
*
|
||||
* @param {import('./Mix')} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix;
|
||||
}
|
||||
|
||||
record() {
|
||||
this.clean();
|
||||
|
||||
if (!this.mix.config.hmr) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hotFile().write(
|
||||
`${this.http()}://${this.mix.config.hmrOptions.host}:${this.port()}`
|
||||
);
|
||||
|
||||
process.on('exit', () => this.clean());
|
||||
process.on('SIGINT', () => this.clean());
|
||||
process.on('SIGHUP', () => this.clean());
|
||||
}
|
||||
|
||||
hotFile() {
|
||||
return new File(path.join(this.mix.config.publicPath, 'hot'));
|
||||
}
|
||||
|
||||
http() {
|
||||
return process.argv.includes('--https') ? 'https' : 'http';
|
||||
}
|
||||
|
||||
port() {
|
||||
return process.argv.includes('--port')
|
||||
? process.argv[process.argv.indexOf('--port') + 1]
|
||||
: this.mix.config.hmrOptions.port;
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.hotFile().delete();
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
static record() {
|
||||
return new HotReloading(global.Mix).record();
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
static hotFile() {
|
||||
return new HotReloading(global.Mix).hotFile();
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
static http() {
|
||||
return new HotReloading(global.Mix).http();
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
static port() {
|
||||
return new HotReloading(global.Mix).port();
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
static clean() {
|
||||
return new HotReloading(global.Mix).clean();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HotReloading;
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
const chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* @typedef {object} LogMessage
|
||||
* @property {string} text
|
||||
* @property {'info' | 'warn' | 'error'} type
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef {'default' | 'green' | 'red'} LogColor
|
||||
**/
|
||||
|
||||
class Log {
|
||||
/**
|
||||
* Determine if we are in test mode.
|
||||
*/
|
||||
static testing = false;
|
||||
|
||||
/**
|
||||
* All logged messages.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
static fakedLogs = [];
|
||||
|
||||
/**
|
||||
* Log basic info to the console.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {LogColor} color
|
||||
*/
|
||||
static info(message, color = 'default') {
|
||||
if (Log.testing) {
|
||||
Log.fakedLogs.push(message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(Log.colors()[color], message);
|
||||
|
||||
Log.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {LogMessage} message
|
||||
*/
|
||||
static message(message) {
|
||||
if (Log.testing) {
|
||||
Log.fakedLogs.push(message.text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
let prefix = '';
|
||||
|
||||
if (message.type === 'info') {
|
||||
prefix = ' INFO ';
|
||||
} else if (message.type === 'warn') {
|
||||
prefix = ' WARN ';
|
||||
} else if (message.type === 'error') {
|
||||
prefix = ' ERR ';
|
||||
}
|
||||
|
||||
const line = message.text.replace(/\n/g, '\n' + ' '.repeat(prefix.length + 1));
|
||||
|
||||
if (message.type === 'info') {
|
||||
console.warn(`${chalk.bgBlue.white(prefix)} ${chalk.white(line)}`);
|
||||
} else if (message.type === 'warn') {
|
||||
console.warn(`${chalk.bgYellow.black(prefix)} ${chalk.yellow(line)}`);
|
||||
} else if (message.type === 'error') {
|
||||
console.warn(`${chalk.bgRed.white(prefix)} ${chalk.red(line)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log feedback info to the console.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {LogColor} color
|
||||
*/
|
||||
static feedback(message, color = 'green') {
|
||||
Log.line('\t' + message, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error info to the console.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {LogColor} color
|
||||
*/
|
||||
static error(message, color = 'red') {
|
||||
Log.line(message, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a new line of info to the console.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {LogColor} color
|
||||
*/
|
||||
static line(message, color = 'default') {
|
||||
Log.info(message, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the default color for future console.logs.
|
||||
*/
|
||||
static reset() {
|
||||
console.log(Log.colors()['default'], '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter testing mode.
|
||||
*/
|
||||
static fake() {
|
||||
Log.testing = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit testing mode.
|
||||
*/
|
||||
static restore() {
|
||||
Log.testing = false;
|
||||
Log.fakedLogs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given message was logged.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
static received(message) {
|
||||
let result = Log.fakedLogs.some(log => log.includes(message));
|
||||
|
||||
Log.restore();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The colors lookup table.
|
||||
*/
|
||||
static colors() {
|
||||
return {
|
||||
default: '\x1b[0m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Log;
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
let collect = require('collect.js');
|
||||
let path = require('path');
|
||||
let File = require('./File');
|
||||
|
||||
let hasWarnedAboutManifestChange = false;
|
||||
|
||||
class Manifest {
|
||||
/**
|
||||
* Create a new Manifest instance.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {import('./Mix.js')} [mix]
|
||||
*/
|
||||
constructor(name = 'mix-manifest.json', mix = undefined) {
|
||||
this.mix = mix || global.Mix;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
this.manifest = {};
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying manifest collection.
|
||||
* @param {string|null} [file]
|
||||
* @returns {string | Record<string, string>}
|
||||
*/
|
||||
get(file = null) {
|
||||
if (file) {
|
||||
return path.posix.join(
|
||||
this.mix.config.publicPath,
|
||||
this.manifest[this.normalizePath(file)]
|
||||
);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.manifest).sort((a, b) => a[0].localeCompare(b[0]))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given path to the manifest file.
|
||||
*
|
||||
* @param {string} filePath
|
||||
*/
|
||||
add(filePath) {
|
||||
filePath = this.normalizePath(filePath);
|
||||
|
||||
let original = filePath.replace(/\?id=\w+/, '');
|
||||
|
||||
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(this.mix.config.publicPath, file)).version();
|
||||
|
||||
let filePath = this.normalizePath(file);
|
||||
|
||||
this.manifest[filePath] = filePath + '?id=' + hash;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the Webpack stats into the shape we need.
|
||||
*
|
||||
* @param {object} stats
|
||||
*/
|
||||
transform(stats) {
|
||||
this.flattenAssets(stats).forEach(this.add.bind(this));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the mix-manifest.js file.
|
||||
*/
|
||||
refresh() {
|
||||
if (this.path() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
File.find(this.path()).makeDirectories().write(this.manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JSON output from the manifest file.
|
||||
*/
|
||||
read() {
|
||||
if (this.path() === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(File.find(this.path()).read());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the manifest file.
|
||||
*/
|
||||
path() {
|
||||
// Backwards compat for plugis that manually swapped out the manifest plugin with a different name
|
||||
if (this.name !== 'mix-manifest.json') {
|
||||
if (!hasWarnedAboutManifestChange) {
|
||||
hasWarnedAboutManifestChange = true;
|
||||
console.warn(
|
||||
`You can now customize the name of the manifest using mix.options(${JSON.stringify(
|
||||
{ manifest: this.name }
|
||||
)}) instead of changing the manifest name directly.`
|
||||
);
|
||||
}
|
||||
|
||||
return path.join(this.mix.config.publicPath, this.name);
|
||||
}
|
||||
|
||||
if (!this.mix.config.manifest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path.join(this.mix.config.publicPath, this.mix.config.manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = collect(assets.mix).except('mix.js').all();
|
||||
}
|
||||
|
||||
return (
|
||||
collect(assets)
|
||||
.flatten()
|
||||
// Don't add hot updates to manifest
|
||||
.filter(name => name.indexOf('hot-update') === -1)
|
||||
.all()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the provided path for processing.
|
||||
*
|
||||
* @param {string} filePath
|
||||
*/
|
||||
normalizePath(filePath) {
|
||||
if (
|
||||
this.mix.config.publicPath &&
|
||||
filePath.startsWith(this.mix.config.publicPath)
|
||||
) {
|
||||
filePath = filePath.substring(this.mix.config.publicPath.length);
|
||||
}
|
||||
filePath = filePath.replace(/\\/g, '/');
|
||||
|
||||
if (!filePath.startsWith('/')) {
|
||||
filePath = '/' + filePath;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Manifest;
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
let { Chunks } = require('./Chunks');
|
||||
let buildConfig = require('./config');
|
||||
let ComponentRegistrar = require('./components/ComponentRegistrar');
|
||||
let Components = require('./components/Components');
|
||||
let Dependencies = require('./Dependencies');
|
||||
let Dispatcher = require('./Dispatcher');
|
||||
let Dotenv = require('dotenv');
|
||||
let File = require('./File');
|
||||
let HotReloading = require('./HotReloading');
|
||||
let Manifest = require('./Manifest');
|
||||
let Paths = require('./Paths');
|
||||
let WebpackConfig = require('./builder/WebpackConfig');
|
||||
let { Resolver } = require('./Resolver');
|
||||
const Log = require('./Log');
|
||||
|
||||
/** @typedef {import("./tasks/Task")} Task */
|
||||
|
||||
class Mix {
|
||||
/** @type {Mix|null} */
|
||||
static _primary = null;
|
||||
|
||||
/** @type {Record<string, boolean>} */
|
||||
static _hasWarned = {};
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*/
|
||||
constructor() {
|
||||
/** @type {ReturnType<buildConfig>} */
|
||||
this.config = buildConfig(this);
|
||||
|
||||
this.chunks = new Chunks(this);
|
||||
this.components = new Components();
|
||||
this.dispatcher = new Dispatcher();
|
||||
this.manifest = new Manifest('mix-manifest.json', this);
|
||||
this.paths = new Paths();
|
||||
this.registrar = new ComponentRegistrar(this);
|
||||
this.webpackConfig = new WebpackConfig(this);
|
||||
this.hot = new HotReloading(this);
|
||||
this.resolver = new Resolver();
|
||||
this.dependencies = new Dependencies();
|
||||
this.logger = Log;
|
||||
|
||||
/** @type {Task[]} */
|
||||
this.tasks = [];
|
||||
|
||||
this.booted = false;
|
||||
|
||||
this.bundlingJavaScript = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @type {boolean}
|
||||
**/
|
||||
this.initialized = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @type {false | null | string | string[] | Record<string, string | string[]>}
|
||||
*/
|
||||
this.globalStyles = null;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @type {boolean|string}
|
||||
**/
|
||||
this.extractingStyles = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
static get primary() {
|
||||
return Mix._primary || (Mix._primary = new Mix());
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async build() {
|
||||
if (!this.booted) {
|
||||
console.warn(
|
||||
'Mix was not set up correctly. Please ensure you import or require laravel-mix in your mix config.'
|
||||
);
|
||||
|
||||
this.boot();
|
||||
}
|
||||
|
||||
return this.webpackConfig.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
boot() {
|
||||
if (this.booted) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.booted = true;
|
||||
|
||||
// Load .env
|
||||
Dotenv.config();
|
||||
|
||||
// If we're using Laravel set the public path by default
|
||||
if (this.sees('laravel')) {
|
||||
this.config.publicPath = 'public';
|
||||
}
|
||||
|
||||
this.listen('init', () => this.hot.record());
|
||||
this.makeCurrent();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async installDependencies() {
|
||||
await this.dispatch('internal:gather-dependencies');
|
||||
await this.dependencies.install();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
return this.dispatch('init', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import("laravel-mix").Api}
|
||||
*/
|
||||
get api() {
|
||||
if (!this._api) {
|
||||
this._api = this.registrar.installAll();
|
||||
|
||||
// @ts-ignore
|
||||
this._api.inProduction = () => this.config.production;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return this._api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given config item is truthy.
|
||||
*
|
||||
* @param {string} tool
|
||||
*/
|
||||
isUsing(tool) {
|
||||
// @ts-ignore
|
||||
return !!this.config[tool];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Mix is executing in a production environment.
|
||||
*/
|
||||
inProduction() {
|
||||
return this.config.production;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Mix should use HMR.
|
||||
*/
|
||||
isHot() {
|
||||
return process.argv.includes('--hot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Mix should watch files for changes.
|
||||
*/
|
||||
isWatching() {
|
||||
return this.isHot() || process.argv.includes('--watch');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if polling is used for file watching
|
||||
*/
|
||||
isPolling() {
|
||||
const hasPollingOption = process.argv.some(arg =>
|
||||
arg.includes('--watch-options-poll')
|
||||
);
|
||||
|
||||
return this.isWatching() && hasPollingOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Mix sees a particular tool or framework.
|
||||
*
|
||||
* @param {string} tool
|
||||
* @deprecated
|
||||
*/
|
||||
sees(tool) {
|
||||
if (tool === 'laravel') {
|
||||
return File.exists('./artisan');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given npm package is installed.
|
||||
*
|
||||
* @param {string} npmPackage
|
||||
* @deprecated
|
||||
*/
|
||||
seesNpmPackage(npmPackage) {
|
||||
return this.resolver.has(npmPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue up a new task.
|
||||
*
|
||||
* @param {Task} task
|
||||
*/
|
||||
addTask(task) {
|
||||
this.tasks.push(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for the given event.
|
||||
*
|
||||
* @param {string|string} event
|
||||
* @param {import('./Dispatcher').Handler} callback
|
||||
*/
|
||||
listen(event, callback) {
|
||||
this.dispatcher.listen(event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given event.
|
||||
*
|
||||
* @param {string} event
|
||||
* @param {any | (() => any)} [data]
|
||||
*/
|
||||
async dispatch(event, data) {
|
||||
if (typeof data === 'function') {
|
||||
data = data();
|
||||
}
|
||||
|
||||
return this.dispatcher.fire(event, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @internal
|
||||
*/
|
||||
resolve(name) {
|
||||
return this.resolver.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
makeCurrent() {
|
||||
// Set up some globals
|
||||
|
||||
// @ts-ignore
|
||||
global.Config = this.config;
|
||||
|
||||
// @ts-ignore
|
||||
global.Mix = this;
|
||||
|
||||
// @ts-ignore
|
||||
global.webpackConfig = this.webpackConfig;
|
||||
|
||||
this.chunks.makeCurrent();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Mix;
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @typedef {object} DependencyObject
|
||||
* @property {string} package
|
||||
* @property {(name: string) => boolean} [check]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string | DependencyObject} Dependency
|
||||
*/
|
||||
|
||||
exports.PackageDependency = class PackageDependency {
|
||||
/** @type {string} */
|
||||
package;
|
||||
|
||||
/** @type {string} */
|
||||
name;
|
||||
|
||||
/** @type {(name: string) => boolean | Promise<boolean>} */
|
||||
checkFn;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Dependency} dep
|
||||
*/
|
||||
static from(dep) {
|
||||
const depObj = typeof dep === 'string' ? { package: dep } : dep;
|
||||
|
||||
return new PackageDependency(depObj.package, depObj.check);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pkg
|
||||
* @param {(name: string) => boolean} [checkFn]
|
||||
*/
|
||||
constructor(pkg, checkFn) {
|
||||
this.package = pkg;
|
||||
this.name = pkg.replace(/(?!^@)@.+$/, '');
|
||||
this.checkFn = checkFn || (() => true);
|
||||
}
|
||||
|
||||
async needsInstallation() {
|
||||
return !(await this.isInstalled()) || !(await this.isValid());
|
||||
}
|
||||
|
||||
async isInstalled() {
|
||||
try {
|
||||
require.resolve(this.name);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async isValid() {
|
||||
return this.checkFn(this.name);
|
||||
}
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const File = require('./File');
|
||||
const Paths = require('./Paths');
|
||||
|
||||
class PackageManager {
|
||||
static detect() {
|
||||
const execPath = process.env.npm_execpath || '';
|
||||
|
||||
if (
|
||||
execPath.endsWith('yarn.js') ||
|
||||
execPath.endsWith('yarn') ||
|
||||
PackageManager.hasYarnLockFile()
|
||||
) {
|
||||
return 'yarn';
|
||||
}
|
||||
|
||||
return 'npm';
|
||||
}
|
||||
|
||||
static hasYarnLockFile() {
|
||||
const paths = new Paths();
|
||||
|
||||
return File.exists(paths.root('yarn.lock'));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PackageManager;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
let path = require('path');
|
||||
|
||||
class Paths {
|
||||
/**
|
||||
* Create a new Paths instance.
|
||||
*/
|
||||
constructor() {
|
||||
// TODO: Refactor setup to allow removing this check
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
this.rootPath = path.resolve(__dirname, '../');
|
||||
} else {
|
||||
this.rootPath = process.cwd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root path to resolve webpack.mix.js.
|
||||
*
|
||||
* @param {string} path
|
||||
*/
|
||||
setRootPath(path) {
|
||||
this.rootPath = path;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the path to the user's webpack.mix.js file.
|
||||
*/
|
||||
mix() {
|
||||
const path = this.root(
|
||||
process.env && process.env.MIX_FILE ? process.env.MIX_FILE : 'webpack.mix'
|
||||
);
|
||||
|
||||
try {
|
||||
require.resolve(`${path}.cjs`);
|
||||
|
||||
return `${path}.cjs`;
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the project root.
|
||||
*
|
||||
* @param {string} [append]
|
||||
*/
|
||||
root(append = '') {
|
||||
return path.resolve(this.rootPath, append);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Paths;
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
module.exports = class PostCssPluginsFactory {
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param {import('./Mix.js')} context
|
||||
*/
|
||||
constructor(context) {
|
||||
this.context = context;
|
||||
|
||||
/** @type {import('postcss').AcceptedPlugin[]} */
|
||||
this.plugins = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all relevant PostCSS plugins.
|
||||
*
|
||||
* @param {import('postcss').AcceptedPlugin[] | undefined} plugins
|
||||
*/
|
||||
load(plugins = []) {
|
||||
this.loadGlobalPlugins();
|
||||
this.loadLocalPlugins(plugins);
|
||||
this.loadAutoprefixer();
|
||||
this.loadCssNano();
|
||||
|
||||
return this.plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the user's postcss.config.js file, if any.
|
||||
*
|
||||
* @deprecated postcss-loader already does this on its own
|
||||
*/
|
||||
loadConfigFile() {
|
||||
let postcssrc = require('postcss-load-config');
|
||||
|
||||
try {
|
||||
this.plugins = [...this.plugins, ...postcssrc.sync().plugins];
|
||||
} catch (e) {
|
||||
// No postcss.config.js file exists.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load any global postcss plugins that have been passed to Mix.
|
||||
*/
|
||||
loadGlobalPlugins() {
|
||||
if (this.context.config.postCss && this.context.config.postCss.length) {
|
||||
this.plugins = [...this.plugins, ...this.context.config.postCss];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load any postcss plugins that were passed to the Mix command.
|
||||
*
|
||||
* @param {import('postcss').AcceptedPlugin[] | undefined} plugins
|
||||
*/
|
||||
loadLocalPlugins(plugins) {
|
||||
if (plugins && plugins.length) {
|
||||
this.plugins = [...this.plugins, ...plugins];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add autoprefixer to the plugins list.
|
||||
*/
|
||||
loadAutoprefixer() {
|
||||
if (this.context.config.autoprefixer) {
|
||||
this.plugins.push(require('autoprefixer')(this.context.config.autoprefixer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CSSNano to the plugins list.
|
||||
*/
|
||||
loadCssNano() {
|
||||
if (this.context.config.production && this.context.config.cssNano !== false) {
|
||||
this.plugins.push(
|
||||
require('cssnano')({
|
||||
preset: ['default', this.context.config.cssNano]
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
class Resolver {
|
||||
constructor() {
|
||||
/** @type {Record<string, string>} */
|
||||
this.aliases = {};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
*/
|
||||
get(name) {
|
||||
if (this.aliases[name] !== undefined) {
|
||||
return this.aliases[name];
|
||||
}
|
||||
|
||||
try {
|
||||
return require.resolve(name);
|
||||
} catch (err) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(name) {
|
||||
if (this.aliases[name] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
require.resolve(name);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} newName
|
||||
* @internal
|
||||
*/
|
||||
alias(name, newName) {
|
||||
this.aliases[name] = require.resolve(newName);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.aliases = {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Resolver = Resolver;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
class VueVersion {
|
||||
/**
|
||||
*
|
||||
* @param {import("./Mix")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue versions that are supported by Mix.
|
||||
*
|
||||
* @returns {number[]}
|
||||
*/
|
||||
supported() {
|
||||
return [2, 3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and validate the current version of Vue for the build.
|
||||
*
|
||||
* @param {string|number|null|undefined} [version]
|
||||
* @returns {number}
|
||||
*/
|
||||
detect(version) {
|
||||
version = parseInt(`${version}`);
|
||||
|
||||
if (!version || isNaN(version)) {
|
||||
try {
|
||||
return this.detect(require(this.mix.resolve('vue')).version);
|
||||
} catch (e) {
|
||||
this.fail();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.supported().includes(version)) {
|
||||
return version;
|
||||
}
|
||||
|
||||
this.fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort and log that a supported version of Vue wasn't found.
|
||||
* @returns {never}
|
||||
*/
|
||||
fail() {
|
||||
this.mix.logger.error(
|
||||
`We couldn't find a supported version of Vue in your project. ` +
|
||||
`Please ensure that it's installed (npm install vue).`
|
||||
);
|
||||
|
||||
throw new Error('Unable to detect vue version');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VueVersion;
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
let path = require('path');
|
||||
let File = require('../File');
|
||||
|
||||
class Entry {
|
||||
/**
|
||||
* Create a new Entry instance.
|
||||
* @param {import("../Mix")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
// TODO: Simplify in Mix 7 -- Here for backwards compat if a plugin creates this class directly
|
||||
this.mix = mix || global.Mix;
|
||||
|
||||
/** @type {Record<string, string[]>} */
|
||||
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 {any} 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 {any} val
|
||||
* @param {File} output
|
||||
* @param {File} fallback
|
||||
*/
|
||||
addFromOutput(val, output, fallback) {
|
||||
output = this.normalizePath(output, fallback);
|
||||
|
||||
return this.add(this.createName(output), val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default entry script to the structure.
|
||||
*/
|
||||
addDefault() {
|
||||
this.add('mix', new File(path.resolve(__dirname, 'mock-entry.js')).path());
|
||||
}
|
||||
|
||||
hasDefault() {
|
||||
return (this.structure.mix || []).some(path => path.includes('mock-entry.js'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the proper entry name, based on a given output.
|
||||
*
|
||||
* @param {File} output
|
||||
*/
|
||||
createName(output) {
|
||||
let name = output
|
||||
.pathFromPublic(this.mix.config.publicPath)
|
||||
.replace(/\.js$/, '')
|
||||
.replace(/\\/g, '/');
|
||||
|
||||
this.base = path.parse(name).dir;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given output path.
|
||||
*
|
||||
* @param {File} output
|
||||
* @param {File} fallback
|
||||
*/
|
||||
normalizePath(output, fallback) {
|
||||
// All output paths need to start at the project's public dir.
|
||||
let pathFromPublicDir = output.pathFromPublic();
|
||||
if (
|
||||
!pathFromPublicDir.startsWith('/' + this.mix.config.publicPath) &&
|
||||
!pathFromPublicDir.startsWith('\\' + this.mix.config.publicPath)
|
||||
) {
|
||||
output = new File(
|
||||
path.join(this.mix.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;
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// This code was taken from v4.2.2 of webpack-merge for backwards compatability.
|
||||
// With minor modifications since Mix is not transpiled before running in Node.
|
||||
// Sources:
|
||||
// 1. https://github.com/survivejs/webpack-merge/blob/v4.2.2/src/index.js
|
||||
// 2. https://github.com/survivejs/webpack-merge/blob/v4.2.2/src/join-arrays-smart.js
|
||||
|
||||
const { isEqual, mergeWith, differenceWith, unionWith } = require('lodash');
|
||||
|
||||
function isRule(key) {
|
||||
return ['preLoaders', 'loaders', 'postLoaders', 'rules'].indexOf(key) >= 0;
|
||||
}
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
function uniteRules(rules, key, newRule, rule) {
|
||||
if (
|
||||
String(rule.test) !== String(newRule.test) ||
|
||||
((newRule.enforce || rule.enforce) && rule.enforce !== newRule.enforce) ||
|
||||
(newRule.include && !isSameValue(rule.include, newRule.include)) ||
|
||||
(newRule.exclude && !isSameValue(rule.exclude, newRule.exclude))
|
||||
) {
|
||||
return false;
|
||||
} else if (
|
||||
!rule.test &&
|
||||
!rule.include &&
|
||||
!rule.exclude &&
|
||||
(rule.loader && rule.loader.split('?')[0]) !==
|
||||
(newRule.loader && newRule.loader.split('?')[0])
|
||||
) {
|
||||
// Don't merge the rule if there isn't any identifying fields and the loaders don't match
|
||||
return false;
|
||||
} else if ((rule.include || rule.exclude) && !newRule.include && !newRule.exclude) {
|
||||
// Don't merge child without include/exclude to parent that has either
|
||||
return false;
|
||||
}
|
||||
|
||||
// apply the same logic for oneOf
|
||||
if (rule.oneOf && newRule.oneOf) {
|
||||
rule.oneOf = unionWith(
|
||||
rule.oneOf,
|
||||
newRule.oneOf,
|
||||
uniteRules.bind(null, {}, 'oneOf')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// newRule.loader should always override use, loaders and oneOf
|
||||
if (newRule.loader) {
|
||||
const optionsKey = newRule.options ? 'options' : newRule.query && 'query';
|
||||
|
||||
delete rule.use;
|
||||
delete rule.loaders;
|
||||
delete rule.oneOf;
|
||||
|
||||
rule.loader = newRule.loader;
|
||||
|
||||
if (optionsKey) {
|
||||
rule[optionsKey] = newRule[optionsKey];
|
||||
}
|
||||
} else if (newRule.oneOf) {
|
||||
delete rule.use;
|
||||
delete rule.loaders;
|
||||
delete rule.loader;
|
||||
|
||||
rule.oneOf = newRule.oneOf;
|
||||
} else if (
|
||||
(rule.use || rule.loaders || rule.loader) &&
|
||||
(newRule.use || newRule.loaders)
|
||||
) {
|
||||
const expandEntry = loader => (typeof loader === 'string' ? { loader } : loader);
|
||||
// this is only here to avoid breaking existing tests
|
||||
const unwrapEntry = entry =>
|
||||
!entry.options && !entry.query ? entry.loader : entry;
|
||||
|
||||
let entries;
|
||||
if (rule.loader) {
|
||||
const optionsKey = rule.options ? 'options' : rule.query && 'query';
|
||||
entries = [{ loader: rule.loader }];
|
||||
|
||||
if (optionsKey) {
|
||||
entries[0][optionsKey] = rule[optionsKey];
|
||||
}
|
||||
|
||||
delete rule.loader;
|
||||
|
||||
if (optionsKey) {
|
||||
delete rule[optionsKey];
|
||||
}
|
||||
} else {
|
||||
entries = [].concat(rule.use || rule.loaders).map(expandEntry);
|
||||
}
|
||||
const newEntries = [].concat(newRule.use || newRule.loaders).map(expandEntry);
|
||||
|
||||
const loadersKey = rule.use || newRule.use ? 'use' : 'loaders';
|
||||
const resolvedKey = `${key}.${loadersKey}`;
|
||||
|
||||
switch (rules[resolvedKey]) {
|
||||
case 'prepend':
|
||||
rule[loadersKey] = [
|
||||
...differenceWith(newEntries, entries, uniteEntries),
|
||||
...entries
|
||||
].map(unwrapEntry);
|
||||
break;
|
||||
case 'replace':
|
||||
rule[loadersKey] = newRule.use || newRule.loaders;
|
||||
break;
|
||||
default:
|
||||
rule[loadersKey] = combineEntries(newEntries, entries).map(unwrapEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (newRule.include) {
|
||||
rule.include = newRule.include;
|
||||
}
|
||||
|
||||
if (newRule.exclude) {
|
||||
rule.exclude = newRule.exclude;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check equality of two values using lodash's isEqual
|
||||
* Arrays need to be sorted for equality checking
|
||||
* but clone them first so as not to disrupt the sort order in tests
|
||||
*/
|
||||
function isSameValue(a, b) {
|
||||
const [propA, propB] = [a, b].map(value =>
|
||||
isArray(value) ? [...value].sort() : value
|
||||
);
|
||||
|
||||
return isEqual(propA, propB);
|
||||
}
|
||||
|
||||
function areEqualEntries(newEntry, entry) {
|
||||
const loaderNameRe = /^([^?]+)/gi;
|
||||
|
||||
const [loaderName] = entry.loader.match(loaderNameRe);
|
||||
const [newLoaderName] = newEntry.loader.match(loaderNameRe);
|
||||
|
||||
return loaderName === newLoaderName;
|
||||
}
|
||||
|
||||
function uniteEntries(newEntry, entry) {
|
||||
if (areEqualEntries(newEntry, entry)) {
|
||||
// Replace query values with newer ones
|
||||
mergeWith(entry, newEntry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Combines entries and newEntries, while respecting the order of loaders in each.
|
||||
|
||||
Iterates through new entries. If the new entry also exists in existing entries,
|
||||
we'll put in all of the loaders from existing entries that come before it (in case
|
||||
those are pre-requisites). Any remaining existing entries are added at the end.
|
||||
|
||||
Since webpack processes right-to-left, we're working backwards through the arrays
|
||||
*/
|
||||
function combineEntries(newEntries, existingEntries) {
|
||||
const resultSet = [];
|
||||
|
||||
// We're iterating through newEntries, this keeps track of where we are in the existingEntries
|
||||
let existingEntriesIteratorIndex = existingEntries.length - 1;
|
||||
|
||||
for (let i = newEntries.length - 1; i >= 0; i -= 1) {
|
||||
const currentEntry = newEntries[i];
|
||||
const indexInExistingEntries = findLastIndexUsingComparinator(
|
||||
existingEntries,
|
||||
currentEntry,
|
||||
areEqualEntries,
|
||||
existingEntriesIteratorIndex
|
||||
);
|
||||
const hasEquivalentEntryInExistingEntries = indexInExistingEntries !== -1;
|
||||
|
||||
if (hasEquivalentEntryInExistingEntries) {
|
||||
// If the same entry exists in existing entries, we should add all of the entries that
|
||||
// come before to maintain order
|
||||
for (
|
||||
let j = existingEntriesIteratorIndex;
|
||||
j > indexInExistingEntries;
|
||||
j -= 1
|
||||
) {
|
||||
const existingEntry = existingEntries[j];
|
||||
|
||||
// If this entry also exists in new entries, we'll add as part of iterating through
|
||||
// new entries so that if there's a conflict between existing entries and new entries,
|
||||
// new entries order wins
|
||||
const hasMatchingEntryInNewEntries =
|
||||
findLastIndexUsingComparinator(
|
||||
newEntries,
|
||||
existingEntry,
|
||||
areEqualEntries,
|
||||
i
|
||||
) !== -1;
|
||||
|
||||
if (!hasMatchingEntryInNewEntries) {
|
||||
resultSet.unshift(existingEntry);
|
||||
}
|
||||
existingEntriesIteratorIndex -= 1;
|
||||
}
|
||||
|
||||
uniteEntries(currentEntry, existingEntries[existingEntriesIteratorIndex]);
|
||||
// uniteEntries mutates the second parameter to be a merged version, so that's what's pushed
|
||||
resultSet.unshift(existingEntries[existingEntriesIteratorIndex]);
|
||||
|
||||
existingEntriesIteratorIndex -= 1;
|
||||
} else {
|
||||
const alreadyHasMatchingEntryInResultSet =
|
||||
findLastIndexUsingComparinator(
|
||||
resultSet,
|
||||
currentEntry,
|
||||
areEqualEntries
|
||||
) !== -1;
|
||||
|
||||
if (!alreadyHasMatchingEntryInResultSet) {
|
||||
resultSet.unshift(currentEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining existing entries
|
||||
for (
|
||||
existingEntriesIteratorIndex;
|
||||
existingEntriesIteratorIndex >= 0;
|
||||
existingEntriesIteratorIndex -= 1
|
||||
) {
|
||||
const existingEntry = existingEntries[existingEntriesIteratorIndex];
|
||||
const alreadyHasMatchingEntryInResultSet =
|
||||
findLastIndexUsingComparinator(resultSet, existingEntry, areEqualEntries) !==
|
||||
-1;
|
||||
|
||||
if (!alreadyHasMatchingEntryInResultSet) {
|
||||
resultSet.unshift(existingEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
function findLastIndexUsingComparinator(
|
||||
entries,
|
||||
entryToFind,
|
||||
comparinator,
|
||||
startingIndex
|
||||
) {
|
||||
startingIndex = startingIndex || entries.length - 1;
|
||||
for (let i = startingIndex; i >= 0; i -= 1) {
|
||||
if (areEqualEntries(entryToFind, entries[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const { mergeWithCustomize } = require('webpack-merge');
|
||||
|
||||
module.exports.mergeSmart = mergeWithCustomize({
|
||||
customizeArray: (a, b, key) => {
|
||||
if (isRule(key.split('.').slice(-1)[0])) {
|
||||
return unionWith(a, b, uniteRules.bind(null, {}, key));
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Added by Mix to fix a regex merge bug
|
||||
customizeObject: (a, b, key) => {
|
||||
if (a instanceof RegExp) {
|
||||
if (b instanceof RegExp) {
|
||||
return b;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
if (b instanceof RegExp) {
|
||||
return b;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
let { mergeSmart } = require('./MergeSmart');
|
||||
|
||||
/** @typedef {import('webpack').Configuration} Configuration */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Configuration} configA
|
||||
* @param {Configuration} configB
|
||||
*/
|
||||
module.exports = (configA, configB) => {
|
||||
return mergeSmart(configA, configB);
|
||||
};
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
let path = require('path');
|
||||
let Entry = require('./Entry');
|
||||
let webpackRules = require('./webpack-rules');
|
||||
let webpackPlugins = require('./webpack-plugins');
|
||||
let webpackDefaultConfig = require('./webpack-default');
|
||||
|
||||
class WebpackConfig {
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param {import("../Mix.js")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix;
|
||||
this.chunks = mix.chunks;
|
||||
|
||||
/** @type {ReturnType<webpackDefaultConfig>} */
|
||||
this.webpackConfig = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Webpack configuration object.
|
||||
*/
|
||||
async build() {
|
||||
this.webpackConfig = webpackDefaultConfig(this.mix);
|
||||
|
||||
await this.buildEntry();
|
||||
this.buildOutput();
|
||||
this.configureHMR();
|
||||
await this.buildRules();
|
||||
await this.buildPlugins();
|
||||
this.buildChunks();
|
||||
|
||||
// We'll announce that the core config object has been
|
||||
// generated by Mix. At this point, any plugins may
|
||||
// hook in and modify the config as necessary.
|
||||
await this.mix.dispatch('configReady', this.webpackConfig);
|
||||
|
||||
// Rebuild the chunks as plugins may have added new ones
|
||||
this.buildChunks();
|
||||
|
||||
// Finally, we'll make one last announcement for the user
|
||||
// to hook into - using mix.override().
|
||||
await this.mix.dispatch('configReadyForUser', this.webpackConfig);
|
||||
|
||||
// Rebuild the chunks as the user may have changed things
|
||||
this.buildChunks();
|
||||
|
||||
return this.webpackConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the entry object.
|
||||
*/
|
||||
async buildEntry() {
|
||||
let entry = new Entry(this.mix);
|
||||
|
||||
if (!this.mix.bundlingJavaScript) {
|
||||
entry.addDefault();
|
||||
}
|
||||
|
||||
await this.mix.dispatch('loading-entry', entry);
|
||||
|
||||
this.webpackConfig.entry = entry.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the output object.
|
||||
*/
|
||||
buildOutput() {
|
||||
this.webpackConfig.output = {
|
||||
hashFunction: 'xxhash64',
|
||||
path: path.resolve(this.mix.config.publicPath),
|
||||
filename: '[name].js',
|
||||
|
||||
chunkFilename: pathData => {
|
||||
let hasAbsolutePathChunkName =
|
||||
pathData.chunk &&
|
||||
pathData.chunk.name &&
|
||||
pathData.chunk.name.startsWith('/');
|
||||
|
||||
if (
|
||||
(this.mix.components.get('js') || this.mix.components.get('ts')) &&
|
||||
!hasAbsolutePathChunkName
|
||||
) {
|
||||
let output = this.mix.components.get('ts')
|
||||
? this.mix.components.get('ts').toCompile[0].output
|
||||
: this.mix.components.get('js').toCompile[0].output;
|
||||
|
||||
return `${output.normalizedOutputPath()}/[name].js`;
|
||||
}
|
||||
|
||||
return '[name].js';
|
||||
},
|
||||
|
||||
publicPath: '/'
|
||||
};
|
||||
}
|
||||
|
||||
configureHMR() {
|
||||
if (!this.mix.isUsing('hmr')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { https, host, port } = this.mix.config.hmrOptions;
|
||||
const protocol = https ? 'https' : 'http';
|
||||
const url = `${protocol}://${host}:${port}/`;
|
||||
|
||||
this.webpackConfig.output = {
|
||||
...this.webpackConfig.output,
|
||||
|
||||
publicPath: url
|
||||
};
|
||||
|
||||
this.webpackConfig.devServer = {
|
||||
host,
|
||||
port,
|
||||
|
||||
client: {
|
||||
webSocketURL: {
|
||||
hostname: host,
|
||||
pathname: '/ws',
|
||||
port
|
||||
}
|
||||
},
|
||||
|
||||
liveReload: false,
|
||||
|
||||
https,
|
||||
|
||||
devMiddleware: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
|
||||
'Access-Control-Allow-Headers':
|
||||
'X-Requested-With, Content-Type, Authorization'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{app: import("express").Application}} param0
|
||||
*/
|
||||
onBeforeSetupMiddleware({ app }) {
|
||||
app.use(function (req, _, next) {
|
||||
// Something causes hot update chunks (except for the JSON payload)
|
||||
// to start with a double slash
|
||||
// e.g. GET http://localhost:8080//js/app.[hash].hot-update.js
|
||||
|
||||
// This causes loading those chunks to fail so we patch it up here
|
||||
// This is super hacky and a proper solution should be found eventually
|
||||
req.url = req.url.replace(/^\/\//, '/');
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
...this.webpackConfig.devServer
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the rules array.
|
||||
*/
|
||||
async buildRules() {
|
||||
this.webpackConfig.module = this.webpackConfig.module || {};
|
||||
this.webpackConfig.module.rules = this.webpackConfig.module.rules || [];
|
||||
|
||||
this.webpackConfig.module.rules.push(...webpackRules(this.mix));
|
||||
|
||||
await this.mix.dispatch('loading-rules', this.webpackConfig.module.rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the plugins array.
|
||||
*/
|
||||
async buildPlugins() {
|
||||
this.webpackConfig.plugins = this.webpackConfig.plugins || [];
|
||||
this.webpackConfig.plugins.push(...webpackPlugins(this.mix));
|
||||
|
||||
await this.mix.dispatch('loading-plugins', this.webpackConfig.plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the resolve object.
|
||||
*/
|
||||
buildChunks() {
|
||||
this.webpackConfig = require('./MergeWebpackConfig')(
|
||||
this.webpackConfig,
|
||||
this.mix.chunks.config()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebpackConfig;
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
let path = require('path');
|
||||
let TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../Mix")} mix
|
||||
* @returns {import("webpack").Configuration & {devServer?: import("webpack").WebpackOptionsNormalized["devServer"]}}
|
||||
*/
|
||||
module.exports = function (mix) {
|
||||
// TODO: Remove in Mix 7 -- Here for backwards compat if a plugin requires this file
|
||||
mix = mix || global.Mix;
|
||||
|
||||
return {
|
||||
context: mix.paths.root(),
|
||||
|
||||
mode: mix.inProduction() ? 'production' : 'development',
|
||||
|
||||
infrastructureLogging: mix.isWatching() ? { level: 'none' } : {},
|
||||
|
||||
entry: {},
|
||||
|
||||
output: {
|
||||
chunkFilename: '[name].[hash:5].js'
|
||||
},
|
||||
|
||||
module: { rules: [] },
|
||||
|
||||
plugins: [],
|
||||
|
||||
resolve: {
|
||||
extensions: ['*', '.wasm', '.mjs', '.js', '.jsx', '.json'],
|
||||
roots: [path.resolve(mix.config.publicPath)]
|
||||
},
|
||||
|
||||
stats: {
|
||||
preset: 'errors-warnings',
|
||||
performance: mix.inProduction()
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
optimization: mix.inProduction()
|
||||
? {
|
||||
providedExports: true,
|
||||
sideEffects: true,
|
||||
usedExports: true,
|
||||
// @ts-ignore
|
||||
minimizer: [new TerserPlugin(mix.config.terser)]
|
||||
}
|
||||
: {},
|
||||
|
||||
devtool: mix.config.sourcemaps,
|
||||
|
||||
// @ts-ignore
|
||||
devServer: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
},
|
||||
static: path.resolve(mix.config.publicPath),
|
||||
historyApiFallback: true,
|
||||
compress: true,
|
||||
allowedHosts: 'all'
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
}
|
||||
};
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
let MixDefinitionsPlugin = require('../webpackPlugins/MixDefinitionsPlugin');
|
||||
let BuildCallbackPlugin = require('../webpackPlugins/BuildCallbackPlugin');
|
||||
let CustomTasksPlugin = require('../webpackPlugins/CustomTasksPlugin');
|
||||
let ManifestPlugin = require('../webpackPlugins/ManifestPlugin');
|
||||
let MockEntryPlugin = require('../webpackPlugins/MockEntryPlugin');
|
||||
let BuildOutputPlugin = require('../webpackPlugins/BuildOutputPlugin');
|
||||
let WebpackBar = require('webpackbar');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../Mix")} mix
|
||||
*/
|
||||
module.exports = function (mix) {
|
||||
// TODO: Remove in Mix 7 -- Here for backwards compat if a plugin requires this file
|
||||
mix = mix || global.Mix;
|
||||
|
||||
let plugins = [];
|
||||
|
||||
// If the user didn't declare any JS compilation, we still need to
|
||||
// use a temporary script to force a compile. This plugin will
|
||||
// handle the process of deleting the compiled script.
|
||||
if (!mix.bundlingJavaScript) {
|
||||
plugins.push(new MockEntryPlugin(mix));
|
||||
}
|
||||
|
||||
// Activate support for Mix_ .env definitions.
|
||||
plugins.push(
|
||||
new MixDefinitionsPlugin(mix.paths.root('.env'), {
|
||||
NODE_ENV: mix.inProduction()
|
||||
? 'production'
|
||||
: process.env.NODE_ENV || 'development'
|
||||
})
|
||||
);
|
||||
|
||||
// Handle the creation of the mix-manifest.json file.
|
||||
plugins.push(new ManifestPlugin(mix));
|
||||
|
||||
// Handle all custom, non-webpack tasks.
|
||||
plugins.push(new CustomTasksPlugin(mix));
|
||||
|
||||
// Notify the rest of our app when Webpack has finished its build.
|
||||
plugins.push(new BuildCallbackPlugin(stats => mix.dispatch('build', stats)));
|
||||
|
||||
// Enable custom output when the Webpack build completes.
|
||||
plugins.push(
|
||||
new BuildOutputPlugin({
|
||||
clearConsole: mix.config.clearConsole,
|
||||
showRelated: true
|
||||
})
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
plugins.push(new WebpackBar({ name: 'Mix' }));
|
||||
}
|
||||
|
||||
return plugins;
|
||||
};
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
*
|
||||
* @param {import("../Mix")} mix
|
||||
*/
|
||||
module.exports = function (mix) {
|
||||
// TODO: Remove in Mix 7 -- Here for backwards compat if a plugin requires this file
|
||||
mix = mix || global.Mix;
|
||||
|
||||
/** @type {import("webpack").RuleSetRule[]} */
|
||||
let rules = [];
|
||||
|
||||
// Add support for loading HTML files.
|
||||
rules.push({
|
||||
test: /\.html$/,
|
||||
resourceQuery: { not: [/\?vue/i] },
|
||||
use: [{ loader: mix.resolve('html-loader') }]
|
||||
});
|
||||
|
||||
if (mix.config.imgLoaderOptions) {
|
||||
// Add support for loading images.
|
||||
rules.push({
|
||||
// only include svg that doesn't have font in the path or file name by using negative lookahead
|
||||
test: /(\.(png|jpe?g|gif|webp|avif)$|^((?!font).)*\.svg$)/,
|
||||
use: [
|
||||
{
|
||||
loader: mix.resolve('file-loader'),
|
||||
options: {
|
||||
/**
|
||||
* @param {string} path
|
||||
*/
|
||||
name: path => {
|
||||
if (!/node_modules|bower_components/.test(path)) {
|
||||
return (
|
||||
mix.config.fileLoaderDirs.images +
|
||||
'/[name].[ext]?[hash]'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
mix.config.fileLoaderDirs.images +
|
||||
'/vendor/' +
|
||||
path
|
||||
.replace(/\\/g, '/')
|
||||
.replace(
|
||||
/((.*(node_modules|bower_components))|images|image|img|assets)\//g,
|
||||
''
|
||||
) +
|
||||
'?[hash]'
|
||||
);
|
||||
},
|
||||
publicPath: mix.config.resourceRoot
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
loader: mix.resolve('img-loader'),
|
||||
options: mix.config.imgLoaderOptions || {}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// Add support for loading fonts.
|
||||
rules.push({
|
||||
test: /(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)/,
|
||||
use: [
|
||||
{
|
||||
loader: mix.resolve('file-loader'),
|
||||
options: {
|
||||
/**
|
||||
* @param {string} path
|
||||
*/
|
||||
name: path => {
|
||||
if (!/node_modules|bower_components/.test(path)) {
|
||||
return (
|
||||
mix.config.fileLoaderDirs.fonts + '/[name].[ext]?[hash]'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
mix.config.fileLoaderDirs.fonts +
|
||||
'/vendor/' +
|
||||
path
|
||||
.replace(/\\/g, '/')
|
||||
.replace(
|
||||
/((.*(node_modules|bower_components))|fonts|font|assets)\//g,
|
||||
''
|
||||
) +
|
||||
'?[hash]'
|
||||
);
|
||||
},
|
||||
publicPath: mix.config.resourceRoot
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Add support for loading cursor files.
|
||||
rules.push({
|
||||
test: /\.(cur|ani)$/,
|
||||
use: [
|
||||
{
|
||||
loader: mix.resolve('file-loader'),
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]',
|
||||
publicPath: mix.config.resourceRoot
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return rules;
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Alias extends Component {
|
||||
/** @type {Record<string, string | {raw: string}>} */
|
||||
aliases = {};
|
||||
|
||||
/**
|
||||
* Add resolution aliases to webpack's config
|
||||
*
|
||||
* @param {Record<string, string | {raw: string}>} paths
|
||||
*/
|
||||
register(paths) {
|
||||
this.aliases = { ...this.aliases, ...paths };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('webpack').Configuration} config
|
||||
**/
|
||||
webpackConfig(config) {
|
||||
config.resolve = config.resolve || {};
|
||||
config.resolve.alias = config.resolve.alias || {};
|
||||
|
||||
for (const [alias, path] of Object.entries(this.aliases)) {
|
||||
const resolvedPath =
|
||||
typeof path === 'object' ? path.raw : this.context.paths.root(path);
|
||||
|
||||
config.resolve.alias[alias] = resolvedPath;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
const { concat } = require('lodash');
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Autoload extends Component {
|
||||
/** @type {Record<string, string|string[]>} */
|
||||
aliases = {};
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {Record<string, string>} libs
|
||||
* @return {void}
|
||||
*/
|
||||
register(libs) {
|
||||
Object.keys(libs).forEach(library => {
|
||||
concat([], libs[library]).forEach(alias => {
|
||||
this.aliases[alias] = library.includes('.')
|
||||
? library.split('.')
|
||||
: library;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack plugins to be appended to the master config.
|
||||
*/
|
||||
webpackPlugins() {
|
||||
const { ProvidePlugin } = require('webpack');
|
||||
|
||||
return [new ProvidePlugin(this.aliases)];
|
||||
}
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
/**
|
||||
* @deprecated Instead extend `Component` and set `passive` to `true`
|
||||
**/
|
||||
module.exports = class AutomaticComponent extends Component {
|
||||
passive = true;
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class BabelConfig extends Component {
|
||||
/**
|
||||
* @param {import('@babel/core').TransformOptions} config
|
||||
*/
|
||||
register(config) {
|
||||
this.context.config.babelConfig = config;
|
||||
}
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Before extends Component {
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {void}
|
||||
*/
|
||||
register(callback) {
|
||||
this.context.listen('init', callback);
|
||||
}
|
||||
};
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
/** @typedef {import('../../types/browsersync').Options} BrowserSyncOptions */
|
||||
|
||||
module.exports = class BrowserSync extends Component {
|
||||
requiresReload = true;
|
||||
|
||||
/** @type {BrowserSyncOptions} */
|
||||
userConfig = {};
|
||||
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
return ['browser-sync', 'browser-sync-webpack-plugin@^2.3.0'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {string|BrowserSyncOptions} userConfig
|
||||
*/
|
||||
register(userConfig) {
|
||||
this.userConfig =
|
||||
typeof userConfig == 'string' ? { proxy: userConfig } : userConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack plugins to be appended to the master config.
|
||||
*/
|
||||
webpackPlugins() {
|
||||
let BrowserSyncPlugin = require('browser-sync-webpack-plugin');
|
||||
|
||||
return [new BrowserSyncPlugin(this.config(), { reload: false })];
|
||||
}
|
||||
|
||||
/**
|
||||
* The regex used to determine where the Browsersync
|
||||
* javascript snippet is injected onto each page.
|
||||
*/
|
||||
regex() {
|
||||
return RegExp('(</body>|</pre>)(?!.*(</body>|</pre>))', 'is');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the BrowserSync configuration.
|
||||
*/
|
||||
config() {
|
||||
let userConfig = this.userConfig;
|
||||
let defaultConfig = {
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
proxy: 'app.test',
|
||||
files: [
|
||||
'app/**/*.php',
|
||||
'resources/views/**/*.php',
|
||||
`${this.context.config.publicPath || 'public'}/**/*.(js|css)`
|
||||
],
|
||||
snippetOptions: {
|
||||
rule: {
|
||||
match: this.regex(),
|
||||
fn: function (snippet, match) {
|
||||
return snippet + match;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (userConfig && userConfig.server) {
|
||||
delete defaultConfig.proxy;
|
||||
}
|
||||
|
||||
return Object.assign(defaultConfig, userConfig);
|
||||
}
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
const JavaScript = require('./JavaScript');
|
||||
|
||||
module.exports = class Coffee extends JavaScript {
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
return ['coffee-loader', 'coffeescript'].concat();
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack rules to be appended to the master config.
|
||||
*/
|
||||
webpackRules() {
|
||||
return [
|
||||
{
|
||||
test: /\.coffee$/,
|
||||
use: [{ loader: this.context.resolve('coffee-loader') }]
|
||||
},
|
||||
...super.webpackRules()
|
||||
];
|
||||
}
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
const File = require('../File');
|
||||
const Assert = require('../Assert');
|
||||
const ConcatFilesTask = require('../tasks/ConcatenateFilesTask');
|
||||
const { Component } = require('./Component');
|
||||
const { concat } = require('lodash');
|
||||
|
||||
module.exports = class Combine extends Component {
|
||||
/**
|
||||
* The API name for the component.
|
||||
*/
|
||||
name() {
|
||||
return ['combine', 'scripts', 'babel', 'styles', 'minify'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {string|string[]} src
|
||||
* @param {string} [output]
|
||||
* @param {boolean} babel
|
||||
*/
|
||||
register(src, output = '', babel = false) {
|
||||
// Do we need to perform compilation?
|
||||
babel = babel || this.caller === 'babel';
|
||||
|
||||
const sources = concat([], src);
|
||||
const hasOutputPath = output !== undefined && output !== '';
|
||||
|
||||
if (hasOutputPath) {
|
||||
this.context.addTask(
|
||||
this.createTask({
|
||||
src: sources,
|
||||
dst: output,
|
||||
babel
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.caller !== 'minify') {
|
||||
throw new Error(
|
||||
`An output file path is required when using mix.${this.caller}()`
|
||||
);
|
||||
}
|
||||
|
||||
// We've we're minifying an array of files then the output is the same as the input with a .min extension added
|
||||
for (const source of sources) {
|
||||
this.context.addTask(
|
||||
this.createTask({
|
||||
src: source,
|
||||
dst: source.replace(/\.([a-z]{2,})$/i, '.min.$1'),
|
||||
babel
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {string|string[]} param0.src
|
||||
* @param {string} param0.dst
|
||||
* @param {boolean} param0.babel
|
||||
*/
|
||||
createTask({ src, dst, babel }) {
|
||||
const output = new File(dst);
|
||||
|
||||
Assert.combine(src, output);
|
||||
|
||||
return new ConcatFilesTask({
|
||||
src,
|
||||
output,
|
||||
babel,
|
||||
ignore: [output.relativePath()]
|
||||
});
|
||||
}
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @abstract
|
||||
* @internal (for now)
|
||||
**/
|
||||
class Component {
|
||||
/** Whether or not to automatically register this component */
|
||||
passive = false;
|
||||
|
||||
/** Whether or not this component requires dependency reloading */
|
||||
requiresReload = false;
|
||||
|
||||
/**
|
||||
* The name used to call this component.
|
||||
*
|
||||
* @deprecated
|
||||
**/
|
||||
caller = '';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../Mix")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.context = mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifiy one or more dependencies that must
|
||||
* be installed for this component to work
|
||||
*
|
||||
* @returns {import("../Dependencies").Dependency[]}
|
||||
**/
|
||||
dependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add rules to the webpack config
|
||||
*
|
||||
* @returns {import('webpack').RuleSetRule[]}
|
||||
**/
|
||||
webpackRules() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add plugins to the webpack config
|
||||
*
|
||||
* @returns {import('webpack').WebpackPluginInstance[]}
|
||||
**/
|
||||
webpackPlugins() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the webpack config
|
||||
*
|
||||
* @param {import('webpack').Configuration} config
|
||||
* @returns {import('webpack').Configuration}
|
||||
**/
|
||||
webpackConfig(config) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Component = Component;
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
let mergeWebpackConfig = require('../builder/MergeWebpackConfig');
|
||||
const { Component } = require('./Component');
|
||||
const { concat } = require('lodash');
|
||||
|
||||
let components = [
|
||||
'JavaScript',
|
||||
'Preact',
|
||||
'React',
|
||||
'Coffee',
|
||||
'Define',
|
||||
'TypeScript',
|
||||
'Less',
|
||||
'Sass',
|
||||
'Stylus',
|
||||
'PostCss',
|
||||
'CssWebpackConfig',
|
||||
'BrowserSync',
|
||||
'Combine',
|
||||
'Copy',
|
||||
'Autoload',
|
||||
'Alias',
|
||||
'Vue',
|
||||
'React',
|
||||
'Preact',
|
||||
'Version',
|
||||
'Extend',
|
||||
'Extract',
|
||||
'Notifications',
|
||||
'DisableNotifications',
|
||||
'PurifyCss',
|
||||
'LegacyNodePolyfills',
|
||||
'WebpackConfig',
|
||||
'DumpWebpackConfig',
|
||||
'Then',
|
||||
'Override',
|
||||
'SourceMaps',
|
||||
'SetPublicPath',
|
||||
'SetResourceRoot',
|
||||
'Options',
|
||||
'When',
|
||||
'BabelConfig',
|
||||
'Before'
|
||||
];
|
||||
|
||||
class ComponentRegistrar {
|
||||
/**
|
||||
*
|
||||
* @param {import('../Mix')} [mix]
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix || global.Mix;
|
||||
|
||||
this.components = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Install all default components.
|
||||
*/
|
||||
installAll() {
|
||||
components.map(name => require(`./${name}`)).forEach(c => this.install(c));
|
||||
|
||||
return this.components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a component.
|
||||
*
|
||||
* @param {import("laravel-mix").Component} ComponentDefinition
|
||||
* @param {string[]} [names]
|
||||
*/
|
||||
install(ComponentDefinition, names) {
|
||||
/** @type {import("laravel-mix").Component} */
|
||||
let component;
|
||||
|
||||
// If we're extending from the internal `Component` class then we provide the mix API object
|
||||
if (Component.isPrototypeOf(ComponentDefinition)) {
|
||||
// @ts-ignore
|
||||
// This API is not finalized which is why we've restricted to to the internal component class for now
|
||||
component = new ComponentDefinition(this.mix);
|
||||
} else if (typeof ComponentDefinition === 'function') {
|
||||
component = new ComponentDefinition();
|
||||
} else {
|
||||
component = ComponentDefinition;
|
||||
}
|
||||
|
||||
this.registerComponent(component, names || this.getComponentNames(component));
|
||||
|
||||
this.mix.listen('internal:gather-dependencies', () => {
|
||||
if (!component.activated && !component.passive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!component.dependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mix.dependencies.enqueue(
|
||||
concat([], component.dependencies()),
|
||||
component.requiresReload || false
|
||||
);
|
||||
});
|
||||
|
||||
this.mix.listen('init', () => {
|
||||
if (!component.activated && !component.passive) {
|
||||
return;
|
||||
}
|
||||
|
||||
component.boot && component.boot();
|
||||
component.babelConfig && this.applyBabelConfig(component);
|
||||
|
||||
this.mix.listen('loading-entry', entry => {
|
||||
component.webpackEntry && component.webpackEntry(entry);
|
||||
});
|
||||
|
||||
this.mix.listen('loading-rules', rules => {
|
||||
component.webpackRules && this.applyRules(rules, component);
|
||||
});
|
||||
|
||||
this.mix.listen('loading-plugins', plugins => {
|
||||
component.webpackPlugins && this.applyPlugins(plugins, component);
|
||||
});
|
||||
|
||||
this.mix.listen('configReady', config => {
|
||||
component.webpackConfig && component.webpackConfig(config);
|
||||
});
|
||||
});
|
||||
|
||||
return this.components;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} component
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getComponentNames(component) {
|
||||
if (typeof component.name === 'function') {
|
||||
return concat([], component.name());
|
||||
}
|
||||
|
||||
return [
|
||||
component.constructor.name.replace(/^([A-Z])/, letter => letter.toLowerCase())
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string[]} names
|
||||
*/
|
||||
registerComponent(component, names) {
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
*/
|
||||
const register = name => {
|
||||
this.components[name] = (...args) => {
|
||||
this.mix.components.record(name, component);
|
||||
|
||||
component.caller = name;
|
||||
|
||||
component.register && component.register(...args);
|
||||
|
||||
component.activated = true;
|
||||
|
||||
return this.components;
|
||||
};
|
||||
|
||||
// If we're dealing with a passive component that doesn't
|
||||
// need to be explicitly triggered by the user, we'll
|
||||
// call it now.
|
||||
if (component.passive) {
|
||||
this.components[name]();
|
||||
}
|
||||
|
||||
// Components can optionally write to the Mix API directly.
|
||||
if (component.mix) {
|
||||
Object.keys(component.mix()).forEach(name => {
|
||||
this.components[name] = component.mix()[name];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
names.forEach(name => register(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the component's dependencies.
|
||||
*
|
||||
* @deprecated
|
||||
* @param {Component} component
|
||||
*/
|
||||
installDependencies(component) {
|
||||
throw new Error(
|
||||
'ComponentRegistrar.installDependencies is an implementation detail and no longer used'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Apply the Babel configuration for the component.
|
||||
*
|
||||
* @param {Component} component
|
||||
*/
|
||||
applyBabelConfig(component) {
|
||||
this.mix.config.babelConfig = mergeWebpackConfig(
|
||||
this.mix.config.babelConfig,
|
||||
component.babelConfig()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Apply the webpack rules for the component.
|
||||
*
|
||||
* @param {import('webpack').RuleSetRule[]} rules
|
||||
* @param {Component} component
|
||||
*/
|
||||
applyRules(rules, component) {
|
||||
const newRules = component.webpackRules() || [];
|
||||
|
||||
rules.push(...concat(newRules));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Apply the webpack plugins for the component.
|
||||
*
|
||||
* @param {import('webpack').WebpackPluginInstance[]} plugins
|
||||
* @param {Component} component
|
||||
*/
|
||||
applyPlugins(plugins, component) {
|
||||
const newPlugins = component.webpackPlugins() || [];
|
||||
|
||||
plugins.push(...concat(newPlugins));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComponentRegistrar;
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
class Components {
|
||||
/**
|
||||
* Create a new Components instance.
|
||||
*/
|
||||
constructor() {
|
||||
this.components = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a newly registered component.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Component} component
|
||||
*/
|
||||
record(name, component) {
|
||||
this.components[name] = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a recorded component.
|
||||
*
|
||||
* @param {string} name
|
||||
*/
|
||||
get(name) {
|
||||
return this.components[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given component name has been registered.
|
||||
*/
|
||||
has(name) {
|
||||
return this.components.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all components.
|
||||
*/
|
||||
all() {
|
||||
return this.components;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Components;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
const File = require('../File');
|
||||
const CopyFilesTask = require('../tasks/CopyFilesTask');
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Copy extends Component {
|
||||
/**
|
||||
* The API name for the component.
|
||||
*/
|
||||
name() {
|
||||
return ['copy', 'copyDirectory'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} from
|
||||
* @param {string} to
|
||||
*/
|
||||
register(from, to) {
|
||||
this.context.addTask(new CopyFilesTask({ from, to: new File(to) }));
|
||||
}
|
||||
};
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
let semver = require('semver');
|
||||
let { concat, mapValues } = require('lodash');
|
||||
let { Component } = require('./Component');
|
||||
let MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
let PostCssPluginsFactory = require('../PostCssPluginsFactory');
|
||||
|
||||
class CssWebpackConfig extends Component {
|
||||
passive = true;
|
||||
|
||||
/** @returns {import('../Dependencies').DependencyObject[]} */
|
||||
dependencies() {
|
||||
this.requiresReload = true;
|
||||
|
||||
return [
|
||||
{
|
||||
package: 'postcss@^8.3.1',
|
||||
check: name =>
|
||||
semver.satisfies(require(`${name}/package.json`).version, '^8.3.1')
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack rules to be appended to the master config.
|
||||
*/
|
||||
webpackRules() {
|
||||
return [
|
||||
{
|
||||
command: 'css',
|
||||
type: 'css',
|
||||
test: /\.p?css$/
|
||||
},
|
||||
{
|
||||
command: 'sass',
|
||||
type: 'scss',
|
||||
test: /\.scss$/,
|
||||
loader: {
|
||||
loader: this.context.resolve('sass-loader'),
|
||||
options: {
|
||||
sassOptions: {
|
||||
precision: 8,
|
||||
outputStyle: 'expanded'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
command: 'sass',
|
||||
type: 'sass',
|
||||
test: /\.sass$/,
|
||||
loader: {
|
||||
loader: this.context.resolve('sass-loader'),
|
||||
options: {
|
||||
sassOptions: {
|
||||
precision: 8,
|
||||
outputStyle: 'expanded',
|
||||
indentedSyntax: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
command: 'less',
|
||||
type: 'less',
|
||||
test: /\.less$/,
|
||||
loader: { loader: this.context.resolve('less-loader') }
|
||||
},
|
||||
{
|
||||
command: 'stylus',
|
||||
type: 'stylus',
|
||||
test: /\.styl(us)?$/,
|
||||
loader: { loader: this.context.resolve('stylus-loader') }
|
||||
}
|
||||
].map(rule => this.createRule(rule));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the appropriate loaders for the given rule.
|
||||
*
|
||||
* @param {import('webpack').RuleSetRule & {test: RegExp, command: string, type: string}} rule
|
||||
* @returns {import('webpack').RuleSetRule}
|
||||
*/
|
||||
createRule(rule) {
|
||||
return {
|
||||
test: rule.test,
|
||||
exclude: this.excludePathsFor(rule.command),
|
||||
oneOf: [
|
||||
{
|
||||
// Ex: foo.css?module
|
||||
resourceQuery: /module/,
|
||||
use: this.createLoaderList(rule, {
|
||||
mode: 'local',
|
||||
auto: undefined,
|
||||
localIdentName:
|
||||
this.context.config.cssModuleIdentifier || '[hash:base64]'
|
||||
})
|
||||
},
|
||||
{
|
||||
// Ex: foo.css
|
||||
// Ex: foo.module.css
|
||||
use: this.createLoaderList(rule, {
|
||||
mode: 'local',
|
||||
auto: true,
|
||||
localIdentName:
|
||||
this.context.config.cssModuleIdentifier || '[hash:base64]'
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the appropriate loaders for the given rule.
|
||||
*
|
||||
* @param {import('webpack').RuleSetRule & {test: RegExp, command: string, type: string}} rule
|
||||
* @param {object} useCssModules
|
||||
* @returns {any[]}
|
||||
*/
|
||||
createLoaderList(rule, useCssModules) {
|
||||
return [
|
||||
...CssWebpackConfig.afterLoaders({
|
||||
method: 'auto',
|
||||
location: 'default',
|
||||
context: this.context
|
||||
}),
|
||||
{
|
||||
loader: this.context.resolve('css-loader'),
|
||||
options: {
|
||||
/**
|
||||
* @param {string} url
|
||||
**/
|
||||
url: url => {
|
||||
if (url.startsWith('/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.context.config.processCssUrls;
|
||||
},
|
||||
modules: useCssModules
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: this.context.resolve('postcss-loader'),
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: new PostCssPluginsFactory(this.context).load(),
|
||||
hideNothingWarning: true
|
||||
}
|
||||
}
|
||||
},
|
||||
rule.loader,
|
||||
...CssWebpackConfig.beforeLoaders({
|
||||
context: this.context,
|
||||
type: rule.type,
|
||||
injectGlobalStyles: true
|
||||
})
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paths to be excluded from the loader.
|
||||
*
|
||||
* @param {string} command
|
||||
*/
|
||||
excludePathsFor(command) {
|
||||
let exclusions = this.context.components.get(command);
|
||||
|
||||
if (command === 'css' || !exclusions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return exclusions.details.map(preprocessor => preprocessor.src.path());
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack plugins to be appended to the master config.
|
||||
*/
|
||||
webpackPlugins() {
|
||||
return [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[name].css'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of loaders to handle CSS
|
||||
*
|
||||
* This handles inlining or extraction of CSS based on context.
|
||||
* The default is to inline styles
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {import('../Mix')} options.context The method to use when handling CSS.
|
||||
* @param {"auto" | "inline" | "extract"} options.method The method to use when handling CSS.
|
||||
* @param {"default" | "per-file"} options.location Where these loaders are applied. The `default` set or on a per-file basis (used by preprocessors).
|
||||
*/
|
||||
static afterLoaders({ context, method, location }) {
|
||||
const loaders = [];
|
||||
|
||||
if (method === 'auto') {
|
||||
// TODO: Fix
|
||||
if (context.extractingStyles !== false) {
|
||||
method = 'extract';
|
||||
} else {
|
||||
method = 'inline';
|
||||
}
|
||||
}
|
||||
|
||||
if (method === 'inline') {
|
||||
if (this.wantsVueStyleLoader(context) && location === 'default') {
|
||||
loaders.push({ loader: context.resolve('vue-style-loader') });
|
||||
} else {
|
||||
loaders.push({ loader: context.resolve('style-loader') });
|
||||
}
|
||||
} else if (method === 'extract') {
|
||||
loaders.push({
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
esModule: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unknown css loader method '${method}'. Expected auto, inline, or extract.`
|
||||
);
|
||||
}
|
||||
|
||||
return loaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {import('../Mix')} context
|
||||
**/
|
||||
static wantsVueStyleLoader(context) {
|
||||
const VueFeature = context.components.get('vue');
|
||||
|
||||
return VueFeature && VueFeature.options && VueFeature.options.useVueStyleLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of loaders to run
|
||||
*
|
||||
* This handles inlining or extraction of CSS based on context.
|
||||
* The default is to inline styles
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {import('../Mix')} options.context
|
||||
* @param {string} options.type The file type
|
||||
* @param {boolean} options.injectGlobalStyles Whether or not to inject global styles
|
||||
*/
|
||||
static beforeLoaders({ context, type, injectGlobalStyles }) {
|
||||
const loaders = [];
|
||||
|
||||
if (context.globalStyles && injectGlobalStyles) {
|
||||
let resources =
|
||||
CssWebpackConfig.normalizeGlobalStyles(context, context.globalStyles)[
|
||||
type
|
||||
] || [];
|
||||
|
||||
if (resources.length) {
|
||||
loaders.push({
|
||||
loader: context.resolve('sass-resources-loader'),
|
||||
options: {
|
||||
hoistUseStatements: true,
|
||||
resources
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return loaders;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../Mix')} context
|
||||
* @param {string | Record<string, string|string[]>} styles
|
||||
*/
|
||||
static normalizeGlobalStyles(context, styles) {
|
||||
// Backwards compat with existing Vue globalStyles:
|
||||
// A string only is supported for sass / scss.
|
||||
if (typeof styles !== 'object') {
|
||||
styles = {
|
||||
sass: styles,
|
||||
scss: styles
|
||||
};
|
||||
}
|
||||
|
||||
return mapValues(styles, files => {
|
||||
files = concat([], files);
|
||||
|
||||
return files.map(file => context.paths.root(file));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CssWebpackConfig;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
/** @typedef {undefined | null | string | number | bigint | boolean} CodeValuePrimitive */
|
||||
/** @typedef {() => CodeValuePrimitive} LazyCodeValue */
|
||||
/** @typedef {CodeValuePrimitive | LazyCodeValue} CodeValue */
|
||||
|
||||
module.exports = class Define extends Component {
|
||||
/**
|
||||
* @type {Record<string, CodeValue>}
|
||||
* @internal
|
||||
**/
|
||||
definitions = {};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Record<string, CodeValue>} definitions
|
||||
*/
|
||||
register(definitions) {
|
||||
this.definitions = {
|
||||
...this.definitions,
|
||||
...definitions
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get defaults() {
|
||||
return {
|
||||
'import.meta.env.PROD': () => this.context.api.inProduction(),
|
||||
'import.meta.env.DEV': () => !this.context.api.inProduction()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, CodeValue>} definitions
|
||||
*/
|
||||
resolve(definitions) {
|
||||
for (const [key, value] of Object.entries(definitions)) {
|
||||
if (typeof value === 'function') {
|
||||
definitions[key] = value();
|
||||
}
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
webpackPlugins() {
|
||||
const { DefinePlugin } = require('webpack');
|
||||
|
||||
return [
|
||||
new DefinePlugin(
|
||||
this.resolve({
|
||||
...this.defaults,
|
||||
...this.definitions
|
||||
})
|
||||
)
|
||||
];
|
||||
}
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class DisableNotifications extends Component {
|
||||
/**
|
||||
* The API name for the component.
|
||||
*/
|
||||
name() {
|
||||
return ['disableNotifications', 'disableSuccessNotifications'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*/
|
||||
register() {
|
||||
const enabled = this.caller === 'disableSuccessNotifications' ? ['failure'] : [];
|
||||
|
||||
this.context.config.notifications = {
|
||||
onSuccess: enabled.includes('success'),
|
||||
onFailure: enabled.includes('failure')
|
||||
};
|
||||
}
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class DumpWebpackConfig extends Component {
|
||||
/**
|
||||
* The optional name to be used when called by Mix.
|
||||
*/
|
||||
name() {
|
||||
return ['dumpWebpackConfig', 'dump'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*/
|
||||
register() {
|
||||
this.context.listen('configReadyForUser', config => {
|
||||
this.context.logger.info(this.circularStringify(config));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} item
|
||||
*/
|
||||
circularStringify(item) {
|
||||
const cache = new Set();
|
||||
|
||||
return JSON.stringify(
|
||||
item,
|
||||
(_, value) => {
|
||||
if (value instanceof RegExp) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.has(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
cache.add(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
2
|
||||
);
|
||||
}
|
||||
};
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* This file represents an example component interface
|
||||
* for Mix. All new components can be "inserted" into
|
||||
* Mix, like so:
|
||||
*
|
||||
* // webpack.mix.js
|
||||
*
|
||||
* mix.extend('foo', new Example());
|
||||
*
|
||||
* mix.foo();
|
||||
*/
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Example {
|
||||
/**
|
||||
* The optional name to be used when called by Mix.
|
||||
* Defaults to the class name, lowercased.
|
||||
*
|
||||
* Ex: mix.example();
|
||||
*
|
||||
* @return {string|string[]}
|
||||
*/
|
||||
name() {
|
||||
// Example:
|
||||
// return 'example';
|
||||
// return ['example', 'alias'];
|
||||
}
|
||||
|
||||
/**
|
||||
* All dependencies that should be installed by Mix.
|
||||
*
|
||||
* @return {string[]}
|
||||
*/
|
||||
dependencies() {
|
||||
// Example:
|
||||
// return ['typeScript', 'ts'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* When your component is called, all user parameters
|
||||
* will be passed to this method.
|
||||
*
|
||||
* Ex: register(src, output) {}
|
||||
* Ex: mix.yourPlugin('src/path', 'output/path');
|
||||
*
|
||||
* @param {any} ...params
|
||||
* @return {void}
|
||||
*
|
||||
*/
|
||||
register() {
|
||||
// Example:
|
||||
// this.config = { proxy: arg };
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the component. This method is triggered after the
|
||||
* user's webpack.mix.js file has executed.
|
||||
*/
|
||||
boot() {
|
||||
// Example:
|
||||
// if (this.context.config.foo) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to the master Mix webpack entry object.
|
||||
*
|
||||
* @param {Entry} entry
|
||||
* @return {void}
|
||||
*/
|
||||
webpackEntry(entry) {
|
||||
// Example:
|
||||
// entry.add('foo', 'bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules to be merged with the master webpack loaders.
|
||||
*
|
||||
* @return {any[]}
|
||||
*/
|
||||
webpackRules() {
|
||||
// Example:
|
||||
// return {
|
||||
// test: /\.less$/,
|
||||
// loaders: ['...']
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugins to be merged with the master webpack config.
|
||||
*
|
||||
* @return {any[]}
|
||||
*/
|
||||
webpackPlugins() {
|
||||
// Example:
|
||||
// return new webpack.ProvidePlugin(this.aliases);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the generated webpack configuration.
|
||||
*
|
||||
* @param {import("webpack").Configuration} webpackConfig
|
||||
* @return {void}
|
||||
*/
|
||||
webpackConfig(webpackConfig) {
|
||||
// Example:
|
||||
// webpackConfig.resolve.extensions.push('.ts', '.tsx');
|
||||
}
|
||||
|
||||
/**
|
||||
* Babel config to be merged with Mix's defaults.
|
||||
*
|
||||
* @return {import("@babel/core").TransformOptions}
|
||||
*/
|
||||
babelConfig() {
|
||||
// Example:
|
||||
// return { presets: ['@babel/preset-react'] };
|
||||
}
|
||||
};
|
||||
|
||||
// Usage:
|
||||
// mix.extend('example', new Example());
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
const { concat } = require('lodash');
|
||||
const { Component } = require('./Component');
|
||||
const { createFunctionalComponent } = require('./FunctionalComponent');
|
||||
|
||||
/**
|
||||
* @typedef {import('../../types/component').ClassComponent} ClassComponent
|
||||
* @typedef {import('../../types/component').ComponentInterface} ComponentInterface
|
||||
* @typedef {import('../../types/component').FunctionalComponent} FunctionalComponent
|
||||
* @typedef {import('../../types/component').InstallableComponent} InstallableComponent
|
||||
* @typedef {import('../../types/component').Component} MixComponent
|
||||
**/
|
||||
|
||||
module.exports = class Extend extends Component {
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {string | string[] | FunctionalComponent} name
|
||||
* @param {Component} component
|
||||
*/
|
||||
register(name, component) {
|
||||
if (typeof name === 'function') {
|
||||
return this.context.registrar.install(component);
|
||||
}
|
||||
|
||||
const names = concat([], name);
|
||||
|
||||
if (this.looksLikeSimpleCallback(component)) {
|
||||
return this.context.registrar.install(
|
||||
createFunctionalComponent(names, component)
|
||||
);
|
||||
}
|
||||
|
||||
return this.context.registrar.install(component, names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {MixComponent} component
|
||||
* @returns {component is FunctionalComponent} component
|
||||
*/
|
||||
looksLikeSimpleCallback(component) {
|
||||
if (typeof component !== 'function') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!component.prototype) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
typeof component.prototype.name !== 'function' &&
|
||||
typeof component.prototype.register !== 'function' &&
|
||||
typeof component.prototype.boot !== 'function' &&
|
||||
typeof component.prototype.mix !== 'function' &&
|
||||
typeof component.prototype.dependencies !== 'function'
|
||||
);
|
||||
}
|
||||
};
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
const path = require('path');
|
||||
const File = require('../File');
|
||||
const { Component } = require('./Component');
|
||||
|
||||
/** @typedef {import('../../types/extract').Extraction} Extraction */
|
||||
/** @typedef {import('../../types/extract').ExtractConfig} ExtractConfig */
|
||||
/** @typedef {import('../builder/Entry')} Entry */
|
||||
|
||||
module.exports = class Extract extends Component {
|
||||
/** @type {Entry|null} */
|
||||
entry = null;
|
||||
|
||||
/** @type {Extraction[]} */
|
||||
extractions = [];
|
||||
|
||||
/**
|
||||
* The name of the component.
|
||||
*
|
||||
* mix.extract() or mix.extractVendor()
|
||||
*/
|
||||
name() {
|
||||
return ['extract', 'extractVendors'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {ExtractConfig} [config]
|
||||
* @param {string} [output]
|
||||
*/
|
||||
register(config = null, output = null) {
|
||||
this.context.chunks.runtime = true;
|
||||
this.extractions.push(this.normalizeExtraction(config, output));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assets to append to the webpack entry.
|
||||
*
|
||||
* @param {Entry} entry
|
||||
*/
|
||||
webpackEntry(entry) {
|
||||
this.entry = entry;
|
||||
this.context.chunks.entry = entry;
|
||||
|
||||
if (!this.context.bundlingJavaScript) {
|
||||
throw new Error('You must compile JS to extract vendor code');
|
||||
}
|
||||
|
||||
this.extractions.forEach(extraction => {
|
||||
const path = this.extractionPath(extraction.to);
|
||||
const isDefaultVendorChunk =
|
||||
extraction.to === null ||
|
||||
extraction.to === undefined ||
|
||||
extraction.test.source ===
|
||||
'(?<!node_modules)[\\\\/]node_modules[\\\\/]()';
|
||||
|
||||
this.context.chunks.add(
|
||||
`vendor${this.extractions.indexOf(extraction)}`,
|
||||
path.replace(/\.js$/, ''),
|
||||
extraction.test,
|
||||
{
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
priority: isDefaultVendorChunk ? -10 : 0
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
extractionPath(outputPath) {
|
||||
if (outputPath) {
|
||||
return new File(outputPath).normalizedOutputPath();
|
||||
}
|
||||
|
||||
return path.join(this.entry.base, 'vendor').replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ExtractConfig|null} [config]
|
||||
* @param {string} [output]
|
||||
* @returns {Extraction}
|
||||
*/
|
||||
normalizeExtraction(config = null, output = null) {
|
||||
config = config || {};
|
||||
|
||||
if (typeof config === 'string') {
|
||||
if (output !== null || !config.endsWith('.js')) {
|
||||
throw new Error(
|
||||
'mix.extract(string) expects a file path as its only argument'
|
||||
);
|
||||
}
|
||||
|
||||
config = { to: config };
|
||||
} else if (typeof config === 'function') {
|
||||
config = { test: config };
|
||||
} else if (Array.isArray(config)) {
|
||||
config = { test: this.buildLibraryRegex(config) };
|
||||
}
|
||||
|
||||
return {
|
||||
to: output || null,
|
||||
...config,
|
||||
test: config.test || this.buildLibraryRegex(config.libraries || [])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string[]|RegExp} libraries
|
||||
*/
|
||||
buildLibraryRegex(libraries = []) {
|
||||
let pattern = '(?<!node_modules)[\\\\/]node_modules[\\\\/]';
|
||||
let extra = '';
|
||||
|
||||
if (Array.isArray(libraries)) {
|
||||
extra = libraries.map(lib => `${lib}[\\\\/]`).join('|');
|
||||
} else if (libraries instanceof RegExp) {
|
||||
extra = libraries.source;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected type [${typeof libraries}] passed to mix.extract({ libraries: … }). ` +
|
||||
`You may pass either an array of strings or a regular expression.`
|
||||
);
|
||||
}
|
||||
|
||||
return new RegExp(`${pattern}(${extra})`, 'i');
|
||||
}
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/** @typedef {import('../../types/component').ClassComponent} ClassComponent */
|
||||
/** @typedef {import('../../types/component').FunctionalComponent} FunctionalComponent */
|
||||
|
||||
/**
|
||||
* @param {string[]} names
|
||||
* @param {FunctionalComponent} component
|
||||
* @returns {ClassComponent}
|
||||
*/
|
||||
exports.createFunctionalComponent = function createFunctionalComponent(names, component) {
|
||||
return class {
|
||||
/** @type {any[]} */
|
||||
args = [];
|
||||
|
||||
name() {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {...any} args
|
||||
*/
|
||||
register(...args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('webpack').Configuration} config
|
||||
*/
|
||||
webpackConfig(config) {
|
||||
component.call(this, config, ...this.args);
|
||||
}
|
||||
};
|
||||
};
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
const glob = require('glob');
|
||||
const File = require('../File');
|
||||
const Assert = require('../Assert');
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class JavaScript extends Component {
|
||||
/** @type {{entry: File[], output: File}[]} */
|
||||
toCompile = [];
|
||||
|
||||
/**
|
||||
* The API name for the component.
|
||||
*/
|
||||
name() {
|
||||
let name = this.constructor.name.toLowerCase();
|
||||
|
||||
return [name === 'javascript' ? 'js' : name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} entry
|
||||
* @param {string} output
|
||||
*/
|
||||
register(entry, output) {
|
||||
if (typeof entry === 'string' && entry.includes('*')) {
|
||||
entry = glob.sync(entry);
|
||||
}
|
||||
|
||||
Assert.js(entry, output);
|
||||
|
||||
entry = [].concat(entry).map(file => new File(file));
|
||||
|
||||
this.toCompile.push({ entry, output: new File(output) });
|
||||
|
||||
this.context.bundlingJavaScript = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assets to append to the webpack entry.
|
||||
*
|
||||
* @param {import('../builder/Entry')} entry
|
||||
*/
|
||||
webpackEntry(entry) {
|
||||
this.toCompile.forEach(js => {
|
||||
entry.addFromOutput(
|
||||
js.entry.map(file => file.path()),
|
||||
js.output,
|
||||
js.entry[0]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack rules to be appended to the master config.
|
||||
*/
|
||||
webpackRules() {
|
||||
return [
|
||||
{
|
||||
test: /\.(cjs|mjs|jsx?|tsx?)$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
use: [
|
||||
{
|
||||
loader: this.context.resolve('babel-loader'),
|
||||
options: this.context.config.babel()
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class LegacyNodePolyfills extends Component {
|
||||
passive = true;
|
||||
|
||||
dependencies() {
|
||||
return this.context.config.legacyNodePolyfills ? ['process', 'buffer'] : [];
|
||||
}
|
||||
|
||||
webpackPlugins() {
|
||||
if (!this.context.config.legacyNodePolyfills) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { ProvidePlugin } = require('webpack');
|
||||
|
||||
return [
|
||||
new ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
process: 'process/browser.js'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('webpack').Configuration} _
|
||||
* @returns {import('webpack').Configuration}
|
||||
*/
|
||||
webpackConfig(_) {
|
||||
if (!this.context.config.legacyNodePolyfills) {
|
||||
return {
|
||||
resolve: {
|
||||
fallback: {
|
||||
Buffer: false,
|
||||
process: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
resolve: {
|
||||
fallback: {
|
||||
buffer: this.context.resolve('buffer/'),
|
||||
process: this.context.resolve('process/browser.js')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const Preprocessor = require('./Preprocessor');
|
||||
|
||||
module.exports = class Less extends Preprocessor {
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
return ['less-loader', 'less'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} src
|
||||
* @param {string} output
|
||||
* @param {any} pluginOptions
|
||||
* @param {import('postcss').AcceptedPlugin[]} postCssPlugins
|
||||
*/
|
||||
register(src, output, pluginOptions = {}, postCssPlugins = []) {
|
||||
return this.preprocess('less', src, output, pluginOptions, postCssPlugins);
|
||||
}
|
||||
|
||||
chunkRegex() {
|
||||
return /\.(css|less)$/;
|
||||
}
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Notifications extends Component {
|
||||
passive = true;
|
||||
|
||||
/**
|
||||
* webpack plugins to be appended to the master config.
|
||||
*/
|
||||
webpackPlugins() {
|
||||
if (!this.enabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (process.env.DISABLE_NOTIFICATIONS === '1') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const WebpackNotifierPlugin = require('webpack-notifier');
|
||||
|
||||
return [
|
||||
new WebpackNotifierPlugin({
|
||||
appID: 'Laravel Mix',
|
||||
|
||||
title: 'Laravel Mix',
|
||||
alwaysNotify: this.context.config.notifications.onSuccess,
|
||||
timeout: false,
|
||||
hint: process.platform === 'linux' ? 'int:transient:1' : undefined,
|
||||
contentImage: this.context.paths.root(
|
||||
'node_modules/laravel-mix/icons/laravel.png'
|
||||
)
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
const { notifications } = this.context.config;
|
||||
|
||||
return notifications && (notifications.onSuccess || notifications.onFailure);
|
||||
}
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Options extends Component {
|
||||
/**
|
||||
* @param {Record<string, any>} options
|
||||
*/
|
||||
register(options) {
|
||||
this.context.config.merge(options);
|
||||
|
||||
this.messages(options).forEach(msg => this.context.logger.message(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} options
|
||||
*/
|
||||
messages(options) {
|
||||
/** @type {import("../Log").LogMessage[]} */
|
||||
const messages = [];
|
||||
|
||||
if ('extractVueStyles' in options) {
|
||||
messages.push({
|
||||
type: 'warn',
|
||||
text: 'The option extractVueStyles has been moved. Please pass the extractStyles option to mix.vue() instead.'
|
||||
});
|
||||
}
|
||||
|
||||
if ('globalVueStyles' in options) {
|
||||
messages.push({
|
||||
type: 'warn',
|
||||
text: 'The option globalVueStyles has been moved. Please pass the globalStyles option to mix.vue() instead.'
|
||||
});
|
||||
}
|
||||
|
||||
if ('extractVueStyles' in options || 'globalVueStyles' in options) {
|
||||
messages.push({
|
||||
type: 'info',
|
||||
text: `Example:\n${this.buildVueExample(options)}`
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} options
|
||||
* @returns {string}
|
||||
*/
|
||||
buildVueExample(options) {
|
||||
const props = {};
|
||||
|
||||
if ('extractVueStyles' in options) {
|
||||
props['extractStyles'] = options.extractVueStyles;
|
||||
}
|
||||
|
||||
if ('globalVueStyles' in options) {
|
||||
props['globalStyles'] = options.globalVueStyles;
|
||||
}
|
||||
|
||||
return `mix.vue(${JSON.stringify(props, null, 2)}})`;
|
||||
}
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Override extends Component {
|
||||
/**
|
||||
*
|
||||
* @param {()=>void|Promise<void>} callback
|
||||
*/
|
||||
register(callback) {
|
||||
this.context.listen('configReadyForUser', callback);
|
||||
}
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
const Assert = require('../Assert');
|
||||
const File = require('../File');
|
||||
const Preprocessor = require('./Preprocessor');
|
||||
|
||||
module.exports = class PostCss extends Preprocessor {
|
||||
/**
|
||||
* The Mix API name for the component.
|
||||
*/
|
||||
name() {
|
||||
return ['postCss', 'css'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} src
|
||||
* @param {string} output
|
||||
* @param {import('postcss').AcceptedPlugin[] | Record<string, any>} pluginOptions
|
||||
* @param {import('postcss').AcceptedPlugin[]} postCssPlugins
|
||||
*/
|
||||
register(src, output, pluginOptions = {}, postCssPlugins = []) {
|
||||
// Backwards compat with earlier versions of Mix
|
||||
if (Array.isArray(pluginOptions) && postCssPlugins.length === 0) {
|
||||
postCssPlugins = pluginOptions;
|
||||
pluginOptions = {};
|
||||
}
|
||||
|
||||
if (!Array.isArray(postCssPlugins)) {
|
||||
postCssPlugins = [postCssPlugins];
|
||||
}
|
||||
|
||||
Assert.preprocessor('postCss', src, output);
|
||||
|
||||
const srcFile = new File(src);
|
||||
|
||||
const outputFile = this.normalizeOutput(
|
||||
new File(output),
|
||||
srcFile.nameWithoutExtension() + '.css'
|
||||
);
|
||||
|
||||
this.details.push({
|
||||
type: 'postCss',
|
||||
src: srcFile,
|
||||
output: outputFile,
|
||||
pluginOptions,
|
||||
postCssPlugins
|
||||
});
|
||||
|
||||
// Register a split chunk that takes everything generated
|
||||
// by this file and puts it in a separate file
|
||||
// We use a output-specific chunk name so we don't accidentally merge multiple files
|
||||
this._addChunks(
|
||||
`styles-${outputFile.relativePathWithoutExtension()}`,
|
||||
srcFile,
|
||||
outputFile
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the generated webpack configuration.
|
||||
* @param {import('webpack').Configuration} config
|
||||
*/
|
||||
webpackConfig(config) {
|
||||
config.module.rules.find(rule => rule.test.toString() === '/\\.p?css$/').exclude =
|
||||
this.details.map(postCss => postCss.src.path());
|
||||
}
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Preact extends Component {
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
return ['babel-preset-preact'];
|
||||
}
|
||||
|
||||
register() {
|
||||
if (
|
||||
arguments.length === 2 &&
|
||||
typeof arguments[0] === 'string' &&
|
||||
typeof arguments[1] === 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
'mix.preact() is now a feature flag. Use mix.js(source, destination).preact() instead'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Babel config to be merged with Mix's defaults.
|
||||
*/
|
||||
babelConfig() {
|
||||
return {
|
||||
presets: ['preact']
|
||||
};
|
||||
}
|
||||
};
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
const Assert = require('../Assert');
|
||||
const path = require('path');
|
||||
const File = require('../File');
|
||||
const CssWebpackConfig = require('./CssWebpackConfig');
|
||||
const PostCssPluginsFactory = require('../PostCssPluginsFactory');
|
||||
const Entry = require('../builder/Entry');
|
||||
|
||||
/**
|
||||
* @typedef {object} Detail
|
||||
* @property {string} type
|
||||
* @property {File} src
|
||||
* @property {File} output
|
||||
* @property {any} pluginOptions
|
||||
* @property {any[]} postCssPlugins
|
||||
* @property {boolean} [processUrls]
|
||||
*/
|
||||
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Preprocessor extends Component {
|
||||
/** @type {Detail[]} */
|
||||
details = [];
|
||||
|
||||
/**
|
||||
* Assets to append to the webpack entry.
|
||||
*
|
||||
* @param {Entry} entry
|
||||
*/
|
||||
webpackEntry(entry) {
|
||||
this.details.forEach(detail => {
|
||||
entry.add(entry.keys()[0], detail.src.path());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack rules to be appended to the master config.
|
||||
*/
|
||||
webpackRules() {
|
||||
return this.details.map(preprocessor => ({
|
||||
test: preprocessor.src.path(),
|
||||
use: this.webpackLoaders(preprocessor)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all necessary webpack loaders.
|
||||
*
|
||||
* @param {Detail} preprocessor
|
||||
*/
|
||||
webpackLoaders(preprocessor) {
|
||||
let processUrls = this.shouldProcessUrls(preprocessor);
|
||||
|
||||
/** @type {import('webpack').RuleSetRule[]} */
|
||||
let loaders = [
|
||||
...CssWebpackConfig.afterLoaders({
|
||||
context: this.context,
|
||||
method: 'extract',
|
||||
location: 'per-file'
|
||||
}),
|
||||
{
|
||||
loader: this.context.resolve('css-loader'),
|
||||
options: {
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
*/
|
||||
url: url => {
|
||||
if (url.startsWith('/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return processUrls;
|
||||
},
|
||||
sourceMap: this.context.isUsing('sourcemaps'),
|
||||
importLoaders: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: this.context.resolve('postcss-loader'),
|
||||
options: this.postCssLoaderOptions(preprocessor)
|
||||
}
|
||||
];
|
||||
|
||||
if (preprocessor.type === 'sass' && processUrls) {
|
||||
loaders.push({
|
||||
loader: this.context.resolve('resolve-url-loader'),
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (preprocessor.type !== 'postCss') {
|
||||
loaders.push({
|
||||
loader: `${preprocessor.type}-loader`,
|
||||
options: this.loaderOptions(preprocessor, processUrls)
|
||||
});
|
||||
}
|
||||
|
||||
loaders.push(
|
||||
...CssWebpackConfig.beforeLoaders({
|
||||
context: this.context,
|
||||
type: preprocessor.type,
|
||||
injectGlobalStyles: false
|
||||
})
|
||||
);
|
||||
|
||||
return loaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the preprocessor plugin options.
|
||||
*
|
||||
* @param {Detail} preprocessor
|
||||
* @param {Boolean} processUrls
|
||||
*/
|
||||
loaderOptions(preprocessor, processUrls) {
|
||||
return Object.assign(preprocessor.pluginOptions, {
|
||||
sourceMap:
|
||||
preprocessor.type === 'sass' && processUrls
|
||||
? true
|
||||
: this.context.isUsing('sourcemaps')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the options object for the PostCSS Loader.
|
||||
*
|
||||
* @param {Detail} preprocessor
|
||||
*/
|
||||
postCssLoaderOptions(preprocessor) {
|
||||
return {
|
||||
sourceMap: this.context.isUsing('sourcemaps'),
|
||||
postcssOptions: {
|
||||
plugins: new PostCssPluginsFactory(this.context).load(
|
||||
preprocessor.postCssPlugins
|
||||
),
|
||||
hideNothingWarning: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a generic CSS preprocessor.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} src
|
||||
* @param {string} output
|
||||
* @param {object} pluginOptions
|
||||
* @param {import('postcss').AcceptedPlugin[]} postCssPlugins
|
||||
*/
|
||||
preprocess(type, src, output, pluginOptions = {}, postCssPlugins = []) {
|
||||
Assert.preprocessor(type, src, output);
|
||||
|
||||
const srcFile = new File(src);
|
||||
|
||||
const outputFile = this.normalizeOutput(
|
||||
new File(output),
|
||||
srcFile.nameWithoutExtension() + '.css'
|
||||
);
|
||||
|
||||
this.details.push({
|
||||
type: this.constructor.name.toLowerCase(),
|
||||
src: srcFile,
|
||||
output: outputFile,
|
||||
pluginOptions,
|
||||
postCssPlugins
|
||||
});
|
||||
|
||||
this._addChunks(
|
||||
`styles-${outputFile.relativePathWithoutExtension()}`,
|
||||
srcFile,
|
||||
outputFile
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to apply url preprocessing.
|
||||
*
|
||||
* @param {Detail} preprocessor
|
||||
* @returns {boolean}
|
||||
*/
|
||||
shouldProcessUrls(preprocessor) {
|
||||
const processUrls =
|
||||
preprocessor.pluginOptions.processUrls !== undefined
|
||||
? preprocessor.pluginOptions.processUrls
|
||||
: this.context.config.processCssUrls;
|
||||
|
||||
delete preprocessor.pluginOptions.processUrls;
|
||||
|
||||
return processUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a full output path, using a fallback
|
||||
* file name, if a directory is provided.
|
||||
*
|
||||
* @param {File} output
|
||||
* @param {string} fallbackName
|
||||
*/
|
||||
normalizeOutput(output, fallbackName) {
|
||||
if (output.isDirectory()) {
|
||||
output = new File(path.join(output.filePath, fallbackName));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
chunkRegex() {
|
||||
return /\.p?css$/;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the necessary chunks for this preprocessor
|
||||
*
|
||||
* This method is for internal use only for now.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {File} src
|
||||
* @param {File} output
|
||||
*/
|
||||
_addChunks(name, src, output) {
|
||||
const tests = [
|
||||
// 1. Ensure the file is a CSS file
|
||||
this.chunkRegex(),
|
||||
|
||||
// 2. Ensure that just this file is included in this chunk
|
||||
// _or any dependencies_
|
||||
src.path()
|
||||
];
|
||||
|
||||
const attrs = {
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
type: 'css/mini-extract'
|
||||
};
|
||||
|
||||
this.context.chunks.add(name, output.normalizedOutputPath(), tests, attrs);
|
||||
}
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class PurifyCss extends Component {
|
||||
passive = true;
|
||||
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
if (this.context.config.purifyCss) {
|
||||
throw new Error(
|
||||
'PurifyCSS support is no longer available. We recommend using PurgeCss + postCss instead.'
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
const semver = require('semver');
|
||||
const { Component } = require('./Component');
|
||||
const File = require('../File');
|
||||
|
||||
module.exports = class React extends Component {
|
||||
/** @type {import('laravel-mix').ReactConfig} */
|
||||
options = {
|
||||
extractStyles: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
const dependencies = ['@babel/preset-react'];
|
||||
|
||||
if (this.supportsFastRefreshing()) {
|
||||
return dependencies.concat([
|
||||
{
|
||||
package: '@pmmmwh/react-refresh-webpack-plugin@^0.5.0-rc.0',
|
||||
check: name =>
|
||||
semver.satisfies(
|
||||
require(`${name}/package.json`).version,
|
||||
'^0.5.0-rc.0'
|
||||
)
|
||||
},
|
||||
'react-refresh'
|
||||
]);
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {import('laravel-mix').ReactConfig} options
|
||||
*/
|
||||
register(options = {}) {
|
||||
if (
|
||||
arguments.length === 2 &&
|
||||
typeof arguments[0] === 'string' &&
|
||||
typeof arguments[1] === 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
'mix.react() is now a feature flag. Use mix.js(source, destination).react() instead'
|
||||
);
|
||||
}
|
||||
|
||||
this.options = Object.assign(this.options, options);
|
||||
|
||||
this.context.extractingStyles =
|
||||
this.context.extractingStyles || !!this.options.extractStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack plugins to be appended to the master config.
|
||||
*/
|
||||
webpackPlugins() {
|
||||
if (!this.supportsFastRefreshing()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
|
||||
return [new ReactRefreshPlugin({ overlay: { sockPath: 'ws' } })];
|
||||
}
|
||||
|
||||
/**
|
||||
* Babel config to be merged with Mix's defaults.
|
||||
*/
|
||||
babelConfig() {
|
||||
const plugins = this.supportsFastRefreshing()
|
||||
? [this.context.resolve('react-refresh/babel')]
|
||||
: [];
|
||||
|
||||
return {
|
||||
presets: [['@babel/preset-react', { runtime: 'automatic' }]],
|
||||
plugins
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the React version supports fast refreshing.
|
||||
*/
|
||||
supportsFastRefreshing() {
|
||||
return (
|
||||
this.context.isHot() && semver.satisfies(this.library().version, '>=16.9.0')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the currently installed React library.
|
||||
*/
|
||||
library() {
|
||||
return require('react');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update CSS chunks to extract React styles
|
||||
*/
|
||||
updateChunks() {
|
||||
if (this.options.extractStyles === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.chunks.add(
|
||||
'styles-js',
|
||||
this.styleChunkName(),
|
||||
[/.(j|t)s$/, module => module.type === 'css/mini-extract'],
|
||||
{
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
type: 'css/mini-extract'
|
||||
}
|
||||
);
|
||||
|
||||
this.context.chunks.add(
|
||||
'styles-jsx',
|
||||
this.styleChunkName(),
|
||||
[/.(j|t)sx?$/, module => module.type === 'css/mini-extract'],
|
||||
{
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
type: 'css/mini-extract'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the generated webpack configuration.
|
||||
*
|
||||
* @param {Object} config
|
||||
*/
|
||||
webpackConfig(config) {
|
||||
this.updateChunks();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the style chunk.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
styleChunkName() {
|
||||
// If the user set extractStyles: true, we'll try
|
||||
// to append the React styles to an existing CSS chunk.
|
||||
if (this.options.extractStyles === true) {
|
||||
let chunk = this.context.chunks.find((chunk, id) => id.startsWith('styles-'));
|
||||
|
||||
if (chunk) {
|
||||
return chunk.name;
|
||||
}
|
||||
}
|
||||
|
||||
return this.extractFile().relativePathWithoutExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new File instance for the extracted file.
|
||||
*
|
||||
* @returns {File}
|
||||
*/
|
||||
extractFile() {
|
||||
return new File(this.extractFileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the extract file name.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
extractFileName() {
|
||||
let fileName =
|
||||
typeof this.options.extractStyles === 'string'
|
||||
? this.options.extractStyles
|
||||
: '/css/react-styles.css';
|
||||
|
||||
return fileName.replace(this.context.config.publicPath, '').replace(/^\//, '');
|
||||
}
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
const Preprocessor = require('./Preprocessor');
|
||||
|
||||
module.exports = class Sass extends Preprocessor {
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
this.requiresReload = true;
|
||||
|
||||
const deps = ['sass-loader@^12.1.0', 'sass'];
|
||||
|
||||
if (this.context.config.processCssUrls) {
|
||||
deps.push('resolve-url-loader@^5.0.0');
|
||||
}
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} src
|
||||
* @param {string} output
|
||||
* @param {Record<string, any>} pluginOptions
|
||||
* @param {import('postcss').AcceptedPlugin[]} postCssPlugins
|
||||
*/
|
||||
register(src, output, pluginOptions = {}, postCssPlugins = []) {
|
||||
return this.preprocess(
|
||||
'sass',
|
||||
src,
|
||||
output,
|
||||
this.pluginOptions(pluginOptions),
|
||||
postCssPlugins
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the plugin options for sass-loader.
|
||||
*
|
||||
* @param {Object} pluginOptions
|
||||
* @returns {Object}
|
||||
*/
|
||||
pluginOptions(pluginOptions) {
|
||||
return Object.assign(
|
||||
{
|
||||
sassOptions: {
|
||||
precision: 8,
|
||||
outputStyle: 'expanded'
|
||||
}
|
||||
},
|
||||
pluginOptions,
|
||||
{ sourceMap: true }
|
||||
);
|
||||
}
|
||||
|
||||
chunkRegex() {
|
||||
return /\.(css|s[ac]ss)$/;
|
||||
}
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
const path = require('path');
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class SetPublicPath extends Component {
|
||||
/**
|
||||
* @param {string} defaultPath
|
||||
*/
|
||||
register(defaultPath) {
|
||||
this.context.config.publicPath = path.normalize(defaultPath.replace(/\/$/, ''));
|
||||
}
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class SetResourceRoot extends Component {
|
||||
/**
|
||||
* @param {string} path
|
||||
*/
|
||||
register(path) {
|
||||
this.context.config.resourceRoot = path;
|
||||
}
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class SourceMaps extends Component {
|
||||
register(
|
||||
generateForProduction = true,
|
||||
devType = 'eval-source-map',
|
||||
productionType = 'source-map'
|
||||
) {
|
||||
/** @type {string|false} */
|
||||
let type = devType;
|
||||
|
||||
if (this.context.api.inProduction()) {
|
||||
type = generateForProduction ? productionType : false;
|
||||
}
|
||||
|
||||
this.context.config.sourcemaps = type;
|
||||
}
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
let Preprocessor = require('./Preprocessor');
|
||||
|
||||
class Stylus extends Preprocessor {
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
return ['stylus', 'stylus-loader'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} src
|
||||
* @param {string} output
|
||||
* @param {Record<string, any>} pluginOptions
|
||||
* @param {import('postcss').AcceptedPlugin[]} postCssPlugins
|
||||
*/
|
||||
register(src, output, pluginOptions = {}, postCssPlugins = []) {
|
||||
return this.preprocess('stylus', src, output, pluginOptions, postCssPlugins);
|
||||
}
|
||||
|
||||
chunkRegex() {
|
||||
return /\.(css|styl(us)?)$/;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Stylus;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Then extends Component {
|
||||
/**
|
||||
* The API name for the component.
|
||||
*/
|
||||
name() {
|
||||
return ['then', 'after'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => void | Promise<void>} callback
|
||||
*/
|
||||
register(callback) {
|
||||
this.context.listen('build', callback);
|
||||
}
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
const JavaScript = require('./JavaScript');
|
||||
|
||||
class TypeScript extends JavaScript {
|
||||
/** @type {Record<string, any>} */
|
||||
options = {};
|
||||
|
||||
/**
|
||||
* The API name for the component.
|
||||
*/
|
||||
name() {
|
||||
return ['typeScript', 'ts'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {any} entry
|
||||
* @param {string} output
|
||||
* @param {Record<string, any>} options
|
||||
*/
|
||||
register(entry, output, options = {}) {
|
||||
super.register(entry, output);
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
return ['ts-loader', 'typescript'].concat();
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack rules to be appended to the master config.
|
||||
*/
|
||||
webpackRules() {
|
||||
return [
|
||||
...super.webpackRules(),
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: this.context.resolve('ts-loader'),
|
||||
exclude: /node_modules/,
|
||||
options: Object.assign(
|
||||
{},
|
||||
// TODO: Maybe move to Vue plugin?
|
||||
{ appendTsSuffixTo: [/\.vue$/] },
|
||||
this.options
|
||||
)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the generated webpack configuration.
|
||||
*
|
||||
* @param {import('webpack').Configuration} config
|
||||
*/
|
||||
webpackConfig(config) {
|
||||
config.resolve = config.resolve || {};
|
||||
config.resolve.extensions = config.resolve.extensions || [];
|
||||
config.resolve.extensions.push('.ts', '.tsx');
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TypeScript;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
const { concat } = require('lodash');
|
||||
const VersionFilesTask = require('../tasks/VersionFilesTask');
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class Version extends Component {
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {string|string[]} paths
|
||||
*/
|
||||
register(paths = []) {
|
||||
this.context.addTask(new VersionFilesTask({ files: concat([], paths) }));
|
||||
}
|
||||
};
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
const { Component } = require('./Component');
|
||||
const File = require('../File');
|
||||
const VueVersion = require('../VueVersion');
|
||||
const AppendVueStylesPlugin = require('../webpackPlugins/Css/AppendVueStylesPlugin');
|
||||
|
||||
module.exports = class Vue extends Component {
|
||||
version = 2;
|
||||
|
||||
/**
|
||||
* @type {import('laravel-mix').VueConfig} options
|
||||
*/
|
||||
options = {
|
||||
version: undefined,
|
||||
runtimeOnly: false,
|
||||
options: null,
|
||||
globalStyles: null,
|
||||
extractStyles: false,
|
||||
useVueStyleLoader: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Register the component.
|
||||
*
|
||||
* @param {import('laravel-mix').VueConfig} options
|
||||
*/
|
||||
register(options = {}) {
|
||||
if (
|
||||
arguments.length === 2 &&
|
||||
typeof arguments[0] === 'string' &&
|
||||
typeof arguments[1] === 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
'mix.vue() is a feature flag. Use mix.js(source, destination).vue() instead'
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(this.options, options);
|
||||
|
||||
this.version = new VueVersion(this.context).detect(this.options.version);
|
||||
|
||||
if (this.options.globalStyles !== undefined) {
|
||||
this.context.globalStyles = this.options.globalStyles;
|
||||
}
|
||||
|
||||
if (this.options.extractStyles !== undefined) {
|
||||
this.context.extractingStyles =
|
||||
this.context.extractingStyles || !!this.options.extractStyles;
|
||||
}
|
||||
|
||||
this.addDefines();
|
||||
|
||||
this.context.api.alias({
|
||||
vue$: {
|
||||
raw: this.aliasPath()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Required dependencies for the component.
|
||||
*/
|
||||
dependencies() {
|
||||
this.requiresReload = true;
|
||||
|
||||
let dependencies = [
|
||||
this.version === 2 ? 'vue-template-compiler' : '@vue/compiler-sfc',
|
||||
this.version === 2 ? 'vue-loader@^15.9.7' : 'vue-loader@^16.2.0'
|
||||
];
|
||||
|
||||
if (this.options.extractStyles && this.options.globalStyles) {
|
||||
dependencies.push('sass-resources-loader');
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the generated webpack configuration.
|
||||
*
|
||||
* @param {import('webpack').Configuration} config
|
||||
*/
|
||||
webpackConfig(config) {
|
||||
config.module = config.module || {};
|
||||
config.module.rules = config.module.rules || [];
|
||||
config.resolve = config.resolve || {};
|
||||
config.resolve.extensions = config.resolve.extensions || [];
|
||||
|
||||
// push -> unshift to combat vue loader webpack 5 bug
|
||||
config.module.rules.unshift({
|
||||
test: /\.vue$/,
|
||||
use: [
|
||||
{
|
||||
loader: this.context.resolve('vue-loader'),
|
||||
options: this.options.options || this.context.config.vue || {}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Alias Vue to its ESM build if the user has not already given an alias
|
||||
config.resolve.extensions.push('.vue');
|
||||
|
||||
this.updateChunks();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
aliasPath() {
|
||||
if (this.version === 2) {
|
||||
return this.options.runtimeOnly
|
||||
? 'vue/dist/vue.runtime.esm.js'
|
||||
: 'vue/dist/vue.esm.js';
|
||||
}
|
||||
|
||||
return this.options.runtimeOnly
|
||||
? 'vue/dist/vue.runtime.esm-bundler.js'
|
||||
: 'vue/dist/vue.esm-bundler.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* webpack plugins to be appended to the master config.
|
||||
*/
|
||||
webpackPlugins() {
|
||||
let { VueLoaderPlugin } = require(this.context.resolve('vue-loader'));
|
||||
|
||||
return [new VueLoaderPlugin(), new AppendVueStylesPlugin()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update CSS chunks to extract vue styles
|
||||
*/
|
||||
updateChunks() {
|
||||
if (this.options.extractStyles === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.chunks.add(
|
||||
'styles-vue',
|
||||
this.styleChunkName(),
|
||||
[/.vue$/, module => module.type === 'css/mini-extract'],
|
||||
{
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
type: 'css/mini-extract'
|
||||
}
|
||||
);
|
||||
|
||||
this.context.chunks.add(
|
||||
'styles-jsx',
|
||||
this.styleChunkName(),
|
||||
[/.jsx$/, module => module.type === 'css/mini-extract'],
|
||||
{
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
type: 'css/mini-extract'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the style chunk.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
styleChunkName() {
|
||||
// If the user set extractStyles: true, we'll try
|
||||
// to append the Vue styles to an existing CSS chunk.
|
||||
if (this.options.extractStyles === true) {
|
||||
let chunk = this.context.chunks.find((chunk, id) => id.startsWith('styles-'));
|
||||
|
||||
if (chunk) {
|
||||
return chunk.name;
|
||||
}
|
||||
}
|
||||
|
||||
return this.extractFile().relativePathWithoutExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new File instance for the extracted file.
|
||||
*
|
||||
* @returns {File}
|
||||
*/
|
||||
extractFile() {
|
||||
return new File(this.extractFileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the extract file name.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
extractFileName() {
|
||||
let fileName =
|
||||
typeof this.options.extractStyles === 'string'
|
||||
? this.options.extractStyles
|
||||
: '/css/vue-styles.css';
|
||||
|
||||
return fileName.replace(this.context.config.publicPath, '').replace(/^\//, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the extract file name.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
addDefines() {
|
||||
if (this.version === 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.api.define({
|
||||
__VUE_OPTIONS_API__: 'true',
|
||||
__VUE_PROD_DEVTOOLS__: 'false'
|
||||
});
|
||||
}
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
/** @typedef {import('webpack').Configuration} Configuration */
|
||||
/** @typedef {import('webpack')} webpack */
|
||||
|
||||
module.exports = class WebpackConfig extends Component {
|
||||
/**
|
||||
*
|
||||
* @param {((webpack: webpack, config: Configuration) => Configuration) | Configuration} config
|
||||
*/
|
||||
register(config) {
|
||||
const merge = require('../builder/MergeWebpackConfig');
|
||||
const webpack = require('webpack');
|
||||
|
||||
this.context.api.override(webpackConfig => {
|
||||
config =
|
||||
typeof config === 'function' ? config(webpack, webpackConfig) : config;
|
||||
|
||||
Object.assign(webpackConfig, merge(webpackConfig, config));
|
||||
});
|
||||
}
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
const { Component } = require('./Component');
|
||||
|
||||
module.exports = class When extends Component {
|
||||
/**
|
||||
*
|
||||
* @param {boolean} condition
|
||||
* @param {(api: import("laravel-mix").Api) => void} callback
|
||||
*/
|
||||
register(condition, callback) {
|
||||
if (condition) {
|
||||
callback(this.context.api);
|
||||
}
|
||||
}
|
||||
};
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// @ts-check
|
||||
|
||||
const yargs = require('yargs/yargs');
|
||||
|
||||
/** @typedef {import("@babel/core").TransformOptions} BabelConfig */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("./Mix")} mix
|
||||
*/
|
||||
module.exports = function (mix) {
|
||||
// TODO: Remove in Mix 7 -- Here for backwards compat if a plugin requires this file
|
||||
mix = mix || global.Mix;
|
||||
|
||||
const argv = yargs(process.argv.slice(2))
|
||||
.options({
|
||||
https: { type: 'boolean', default: false },
|
||||
hmrPort: { type: 'string', default: '8080' },
|
||||
p: { type: 'boolean', default: false },
|
||||
hot: { type: 'boolean', default: false }
|
||||
})
|
||||
.parseSync();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Determine if webpack should be triggered in a production environment.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
production: process.env.NODE_ENV === 'production' || argv.p,
|
||||
|
||||
/**
|
||||
* Determine if we should enable hot reloading.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
hmr: argv.hot,
|
||||
|
||||
/**
|
||||
* Hostname and port used for the hot reload module
|
||||
*/
|
||||
hmrOptions: {
|
||||
https: argv.https,
|
||||
host: 'localhost',
|
||||
port: argv.hmrPort
|
||||
},
|
||||
|
||||
/**
|
||||
* PostCSS plugins to be applied to compiled CSS.
|
||||
*
|
||||
* See: https://github.com/postcss/postcss/blob/master/docs/plugins.md
|
||||
*
|
||||
* @type {import('postcss').AcceptedPlugin[]}
|
||||
*/
|
||||
postCss: [],
|
||||
|
||||
/**
|
||||
* Determine if we should enable autoprefixer by default.
|
||||
* May be set to false to disable it.
|
||||
*
|
||||
* @type {false|import('autoprefixer').Options}
|
||||
*/
|
||||
autoprefixer: {},
|
||||
|
||||
/**
|
||||
* The public path for the build.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
publicPath: '',
|
||||
|
||||
/**
|
||||
* The path for the runtime chunk (`manifest.js`).
|
||||
*
|
||||
* Defaults to being placed next to compiled JS files.
|
||||
*
|
||||
* @type {String|null}
|
||||
*/
|
||||
runtimeChunkPath: null,
|
||||
|
||||
/**
|
||||
* Determine if error notifications should be displayed for each build.
|
||||
*
|
||||
* @type {false | {onSuccess: boolean, onFailure: boolean}}
|
||||
*/
|
||||
notifications: {
|
||||
onSuccess: true,
|
||||
onFailure: true
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if sourcemaps should be created for the build.
|
||||
*
|
||||
* @type {false | string}
|
||||
*/
|
||||
sourcemaps: false,
|
||||
|
||||
/**
|
||||
* The resource root for the build.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
resourceRoot: '/',
|
||||
|
||||
/**
|
||||
* Image Loader defaults.
|
||||
* See: https://github.com/thetalecrafter/img-loader#options
|
||||
*
|
||||
* @type {Boolean|Object}
|
||||
*/
|
||||
imgLoaderOptions: {
|
||||
enabled: true,
|
||||
gifsicle: {},
|
||||
mozjpeg: {},
|
||||
optipng: {},
|
||||
svgo: {}
|
||||
},
|
||||
|
||||
/**
|
||||
* File Loader directory defaults.
|
||||
*/
|
||||
fileLoaderDirs: {
|
||||
images: 'images',
|
||||
fonts: 'fonts'
|
||||
},
|
||||
|
||||
/**
|
||||
* The Babel configuration to use when compiling
|
||||
*/
|
||||
babel: function () {
|
||||
const BabelConfig = require('./BabelConfig');
|
||||
|
||||
return new BabelConfig(mix).generate();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if CSS relative url()s should be resolved by webpack.
|
||||
* Disabling this can improve performance greatly.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
processCssUrls: true,
|
||||
|
||||
/**
|
||||
* Terser-specific settings for Webpack.
|
||||
*
|
||||
* See: https://github.com/webpack-contrib/terser-webpack-plugin#options
|
||||
*
|
||||
* @type {import('../types/terser').TerserPluginOptions}
|
||||
*/
|
||||
terser: {
|
||||
parallel: true,
|
||||
terserOptions: {
|
||||
compress: true,
|
||||
output: {
|
||||
comments: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* cssnano-specific settings for Webpack.
|
||||
* Disabled if set to false.
|
||||
*
|
||||
* See: https://cssnano.co/docs/optimisations
|
||||
*
|
||||
* @type {false|Object}
|
||||
*/
|
||||
cssNano: {},
|
||||
|
||||
/**
|
||||
* CleanCss-specific settings for Webpack.
|
||||
*
|
||||
* See: https://github.com/clean-css/clean-css#constructor-options
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
cleanCss: {},
|
||||
|
||||
/**
|
||||
* Custom Webpack-specific configuration to merge/override Mix's.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
webpackConfig: {},
|
||||
|
||||
/**
|
||||
* Custom Babel configuration to be merged with Mix's defaults.
|
||||
*
|
||||
* @type {BabelConfig}
|
||||
*/
|
||||
babelConfig: {},
|
||||
|
||||
/**
|
||||
* Determine if Mix should ask the friendly errors plugin to
|
||||
* clear the console before outputting the results or not.
|
||||
*
|
||||
* https://github.com/geowarin/friendly-errors-webpack-plugin#options
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
clearConsole: true,
|
||||
|
||||
/**
|
||||
* Enable legacy node -> browser polyfills for things like `process` and `Buffer`.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
legacyNodePolyfills: true,
|
||||
|
||||
/**
|
||||
* Options to pass to vue-loader
|
||||
*
|
||||
* @deprecated Use `.vue({options: {…}})` instead
|
||||
*
|
||||
* @type {any}
|
||||
*/
|
||||
vue: {},
|
||||
|
||||
/**
|
||||
* The name / path to the mix manifest.
|
||||
* The path is relative to the public path.
|
||||
*
|
||||
* Set to `false` to disable manifest generation.
|
||||
*
|
||||
* @type {string | false}
|
||||
*/
|
||||
manifest: `mix-manifest.json`,
|
||||
|
||||
/**
|
||||
* Sets the css module identifier pattern
|
||||
*
|
||||
* For more information see https://github.com/webpack-contrib/css-loader/tree/v5.2.7#localidentname
|
||||
*/
|
||||
cssModuleIdentifier: '[hash:base64]',
|
||||
|
||||
/**
|
||||
* Merge the given options with the current defaults.
|
||||
*
|
||||
* @param {Record<string, any>} options
|
||||
*/
|
||||
merge(options) {
|
||||
Object.keys(options).forEach(key => {
|
||||
this[key] = options[key];
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Generic tap function.
|
||||
* @deprecated
|
||||
**/
|
||||
Object.defineProperty(global, 'tap', {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} val
|
||||
* @param {(val: T) => void} callback
|
||||
* @returns {T}
|
||||
*/
|
||||
value(val, callback) {
|
||||
callback(val);
|
||||
|
||||
return val;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add tap to arrays.
|
||||
* @deprecated
|
||||
**/
|
||||
if (!Array.prototype.hasOwnProperty('tap')) {
|
||||
Object.defineProperty(Array.prototype, 'tap', {
|
||||
/**
|
||||
* @param {(arr: self) => void} callback
|
||||
* @returns {self}
|
||||
*/
|
||||
value(callback) {
|
||||
if (this.length) {
|
||||
callback(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add wrap to arrays.
|
||||
* @deprecated
|
||||
**/
|
||||
if (!Array.hasOwnProperty('wrap')) {
|
||||
Object.defineProperty(Array, 'wrap', {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T|T[]} value
|
||||
* @returns {T[]}
|
||||
*/
|
||||
value(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return [value];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten an array.
|
||||
* @deprecated
|
||||
**/
|
||||
Object.defineProperty(global, 'flatten', {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T[]} arr
|
||||
* @returns {T[]}
|
||||
*/
|
||||
value(arr) {
|
||||
// @ts-ignore
|
||||
return [].concat.apply([], Object.values(arr));
|
||||
}
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
const Mix = require('./Mix');
|
||||
|
||||
require('./helpers');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
let mix = Mix.primary;
|
||||
|
||||
mix.boot();
|
||||
|
||||
module.exports = mix.api;
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
let Task = require('./Task');
|
||||
let FileCollection = require('../FileCollection');
|
||||
const { FileGlob } = require('./FileGlob');
|
||||
const File = require('../File');
|
||||
|
||||
/**
|
||||
* @extends {Task<{ src: string|string[], output: File, babel: boolean, ignore?: string[] }>}
|
||||
*/
|
||||
class ConcatenateFilesTask extends Task {
|
||||
/**
|
||||
* Run the task.
|
||||
*/
|
||||
run() {
|
||||
return this.merge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the files into one.
|
||||
*/
|
||||
async merge() {
|
||||
const files = await FileGlob.expand(this.data.src, {
|
||||
ignore: this.data.ignore || []
|
||||
});
|
||||
|
||||
this.files = new FileCollection(files);
|
||||
|
||||
return this.files
|
||||
.merge(this.data.output, this.data.babel)
|
||||
.then(this.assets.push.bind(this.assets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle when a relevant source file is changed.
|
||||
*/
|
||||
onChange() {
|
||||
return this.merge();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConcatenateFilesTask;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
let Task = require('./Task');
|
||||
let FileCollection = require('../FileCollection');
|
||||
let Log = require('../Log');
|
||||
const path = require('path');
|
||||
const File = require('../File');
|
||||
|
||||
/**
|
||||
* @extends {Task<{ from: string|string[], to: File }>}
|
||||
*/
|
||||
class CopyFilesTask extends Task {
|
||||
/**
|
||||
* Run the task.
|
||||
*/
|
||||
run() {
|
||||
let copy = this.data;
|
||||
|
||||
this.files = new FileCollection(copy.from);
|
||||
|
||||
this.files.copyTo(copy.to);
|
||||
|
||||
this.assets = this.files.assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle when a relevant source file is changed.
|
||||
*
|
||||
* @param {string} updatedFile
|
||||
*/
|
||||
onChange(updatedFile) {
|
||||
let destination = this.data.to;
|
||||
|
||||
// If we're copying a src directory recursively, we have to calculate
|
||||
// the correct destination path, based on the src directory tree.
|
||||
if (!Array.isArray(this.data.from) && new File(this.data.from).isDirectory()) {
|
||||
destination = destination.append(
|
||||
path.normalize(updatedFile).replace(path.normalize(this.data.from), '')
|
||||
);
|
||||
}
|
||||
|
||||
Log.feedback(`Copying ${updatedFile} to ${destination.path()}`);
|
||||
|
||||
this.files.copyTo(destination, new File(updatedFile));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CopyFilesTask;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
let path = require('path');
|
||||
let glob = require('glob');
|
||||
let File = require('../File');
|
||||
let { promisify } = require('util');
|
||||
let { concat } = require('lodash');
|
||||
let globAsync = promisify(glob);
|
||||
|
||||
/** @internal */
|
||||
module.exports.FileGlob = class FileGlob {
|
||||
/**
|
||||
* Find all relevant files matching the given source path.
|
||||
*
|
||||
* @param {string|string[]} src
|
||||
* @param {{ ignore?: string[] }} options
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
static async expand(src, { ignore = [] } = {}) {
|
||||
const paths = concat([], src);
|
||||
|
||||
const results = await Promise.all(
|
||||
paths.map(async srcPath => {
|
||||
const result = await this.find(srcPath);
|
||||
|
||||
if (!result.isDir && result.matches.length === 0) {
|
||||
return [srcPath];
|
||||
}
|
||||
|
||||
return result.matches;
|
||||
})
|
||||
);
|
||||
|
||||
const filepaths = results.flatMap(files => files);
|
||||
|
||||
return filepaths.filter(filepath => {
|
||||
return !ignore.includes(filepath);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @internal
|
||||
* @param {string} src
|
||||
* @returns {Promise<{isDir: boolean, matches: string[]}>}
|
||||
*/
|
||||
static async find(src) {
|
||||
const isDir = File.find(src).isDirectory();
|
||||
const pattern = isDir ? path.join(src, '**/*') : src;
|
||||
|
||||
const matches = await globAsync(pattern, { nodir: true });
|
||||
|
||||
return {
|
||||
isDir,
|
||||
matches
|
||||
};
|
||||
}
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
let chokidar = require('chokidar');
|
||||
let File = require('../File');
|
||||
let FileCollection = require('../FileCollection');
|
||||
let NOOP = () => {};
|
||||
|
||||
/**
|
||||
* @template {object} TData
|
||||
*/
|
||||
class Task {
|
||||
/**
|
||||
* Create a new task instance.
|
||||
*
|
||||
* @param {TData} data
|
||||
*/
|
||||
constructor(data) {
|
||||
/** @type {TData} */
|
||||
this.data = data;
|
||||
|
||||
/** @type {File[]} */
|
||||
this.assets = [];
|
||||
|
||||
/** @type {FileCollection} */
|
||||
this.files = new FileCollection();
|
||||
|
||||
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 => {
|
||||
if (event === 'rename') {
|
||||
watcher.unwatch(files);
|
||||
watcher.add(files);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.isBeingWatched = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
run() {
|
||||
throw new Error('Task.run is an abstract method. Please override it.');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} filepath
|
||||
*/
|
||||
onChange(filepath) {
|
||||
throw new Error('Task.onChange is an abstract method. Please override it.');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Task;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
let Task = require('./Task');
|
||||
let File = require('../File');
|
||||
let FileCollection = require('../FileCollection');
|
||||
const { FileGlob } = require('./FileGlob');
|
||||
|
||||
/**
|
||||
* @extends {Task<{ files: string[] }>}
|
||||
*/
|
||||
class VersionFilesTask extends Task {
|
||||
/**
|
||||
* Run the task.
|
||||
*/
|
||||
async run() {
|
||||
const fileGroups = await Promise.all(
|
||||
this.data.files.map(async filepath => {
|
||||
const relativePath = new File(filepath).forceFromPublic().relativePath();
|
||||
|
||||
return FileGlob.expand(relativePath);
|
||||
})
|
||||
);
|
||||
|
||||
const files = fileGroups.flat();
|
||||
|
||||
this.files = new FileCollection(files);
|
||||
this.assets = files.map(filepath => {
|
||||
const file = new File(filepath);
|
||||
|
||||
this.mix.manifest.hash(file.pathFromPublic());
|
||||
|
||||
return file;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle when a relevant source file is changed.
|
||||
*
|
||||
* @param {string} updatedFile
|
||||
*/
|
||||
onChange(updatedFile) {
|
||||
this.mix.manifest.hash(new File(updatedFile).pathFromPublic()).refresh();
|
||||
}
|
||||
|
||||
get mix() {
|
||||
return global.Mix;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VersionFilesTask;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
class BuildCallbackPlugin {
|
||||
/**
|
||||
* Create a new plugin instance.
|
||||
*
|
||||
* @param {(stats: import("webpack").Stats) => any|Promise<any>} callback
|
||||
*/
|
||||
constructor(callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tapPromise('BuildCallbackPlugin', async stats => {
|
||||
return await this.callback(stats);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BuildCallbackPlugin;
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
const _ = require('lodash');
|
||||
const chalk = require('chalk');
|
||||
const Table = require('cli-table3');
|
||||
const readline = require('readline');
|
||||
const stripAnsi = require('strip-ansi');
|
||||
const { formatSize } = require('webpack/lib/SizeFormatHelpers');
|
||||
const { version } = require('../../package.json');
|
||||
|
||||
/**
|
||||
* @typedef {object} BuildOutputOptions
|
||||
* @property {boolean} clearConsole
|
||||
* @property {boolean} showRelated
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef {object} StatsAsset
|
||||
* @property {string} name
|
||||
* @property {number} size
|
||||
* @property {StatsAsset[]|{}} related
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef {object} StatsData
|
||||
* @property {StatsAsset[]} assets
|
||||
**/
|
||||
|
||||
class BuildOutputPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {BuildOutputOptions} options
|
||||
*/
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.patched = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
// TODO: Refactor setup to allow removing this check
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return;
|
||||
}
|
||||
|
||||
compiler.hooks.done.tap('BuildOutputPlugin', stats => {
|
||||
if (stats.hasErrors()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.options.clearConsole) {
|
||||
this.clearConsole();
|
||||
}
|
||||
|
||||
let data = stats.toJson({
|
||||
assets: true,
|
||||
builtAt: true,
|
||||
hash: true,
|
||||
performance: true,
|
||||
relatedAssets: this.options.showRelated
|
||||
});
|
||||
|
||||
this.heading(`Laravel Mix v${version}`);
|
||||
|
||||
console.log(chalk.green.bold(`✔ Compiled Successfully in ${data.time}ms`));
|
||||
|
||||
if (data.assets.length) {
|
||||
console.log(this.statsTable(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a block section heading.
|
||||
*
|
||||
* @param {string} text
|
||||
*/
|
||||
heading(text) {
|
||||
console.log();
|
||||
|
||||
console.log(chalk.bgBlue.white.bold(this.section(text)));
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a block section.
|
||||
*
|
||||
* @param {string} text
|
||||
*/
|
||||
section(text) {
|
||||
const padLength = 3;
|
||||
const padding = ' '.repeat(padLength);
|
||||
|
||||
text = `${padding}${text}${padding}`;
|
||||
|
||||
const line = ' '.repeat(text.length);
|
||||
|
||||
return `${line}\n${text}\n${line}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the stats table.
|
||||
*
|
||||
* @param {StatsData} data
|
||||
* @returns {string}
|
||||
*/
|
||||
statsTable(data) {
|
||||
const assets = this.sortAssets(data);
|
||||
|
||||
const table = new Table({
|
||||
head: [chalk.bold('File'), chalk.bold('Size')],
|
||||
colWidths: [35],
|
||||
colAligns: ['right'],
|
||||
style: {
|
||||
head: [],
|
||||
compact: true
|
||||
}
|
||||
});
|
||||
|
||||
for (const asset of assets) {
|
||||
table.push([chalk.green(asset.name), formatSize(asset.size)]);
|
||||
}
|
||||
|
||||
this.extendTableWidth(table);
|
||||
this.monkeyPatchTruncate();
|
||||
|
||||
return table.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {StatsData} data
|
||||
*/
|
||||
sortAssets(data) {
|
||||
let assets = data.assets;
|
||||
|
||||
assets = _.flatMap(assets, asset => [
|
||||
asset,
|
||||
...(Array.isArray(asset.related) ? asset.related : [])
|
||||
]);
|
||||
|
||||
assets = _.orderBy(assets, ['name', 'size'], ['asc', 'asc']);
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the entire screen.
|
||||
*/
|
||||
clearConsole() {
|
||||
const blank = '\n'.repeat(process.stdout.rows);
|
||||
console.log(blank);
|
||||
|
||||
readline.cursorTo(process.stdout, 0, 0);
|
||||
readline.clearScreenDown(process.stdout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the width of the table
|
||||
*
|
||||
* Currently only increases the file column size
|
||||
*
|
||||
* @param {import("cli-table3").Table} table
|
||||
* @param {number|null} targetWidth
|
||||
* @param {number} maxWidth
|
||||
*/
|
||||
extendTableWidth(table, targetWidth = null, maxWidth = Infinity) {
|
||||
targetWidth = targetWidth === null ? process.stdout.columns : targetWidth;
|
||||
|
||||
if (!targetWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tableWidth = this.calculateTableWidth(table);
|
||||
const fileColIncrease = Math.min(targetWidth - tableWidth, maxWidth - tableWidth);
|
||||
|
||||
if (fileColIncrease <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
table.options.colWidths[0] += fileColIncrease;
|
||||
}
|
||||
|
||||
// Yeah, I know.
|
||||
monkeyPatchTruncate() {
|
||||
if (this.patched) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.patched = true;
|
||||
|
||||
// @ts-ignore
|
||||
const utils = require('cli-table3/src/utils');
|
||||
const oldTruncate = utils.truncate;
|
||||
|
||||
// cli-table3 can only do truncation at the end
|
||||
// We want the asset name to be truncated at the beginning if it's too long
|
||||
// FIXME: We really should set truncation location via a paramter or something
|
||||
// (or PR support for alignment-based truncation)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {number} desiredLength
|
||||
* @param {string} truncateChar
|
||||
*/
|
||||
utils.truncate = (str, desiredLength, truncateChar) => {
|
||||
if (stripAnsi(str).length > desiredLength) {
|
||||
str = `…${str.substr(-desiredLength + 2)}`;
|
||||
}
|
||||
|
||||
return oldTruncate(str, desiredLength, truncateChar);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the width of the CLI Table
|
||||
*
|
||||
* `table.width` does not report the correct width
|
||||
* because it includes ANSI control characters
|
||||
*
|
||||
* @internal
|
||||
* @param {import("cli-table3").Table} table
|
||||
*/
|
||||
calculateTableWidth(table) {
|
||||
const firstRow = table.toString().split('\n')[0];
|
||||
|
||||
return stripAnsi(firstRow).length;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BuildOutputPlugin;
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* This plugin ensures that vue styles are always appended to the end of CSS files
|
||||
*/
|
||||
class AppendVueStylesPlugin {
|
||||
/** @param {import("webpack").Compiler} compiler */
|
||||
apply(compiler) {
|
||||
const name = 'AppendVueStylesPlugin';
|
||||
|
||||
compiler.hooks.compilation.tap(name, compilation => {
|
||||
compilation.hooks.optimizeChunks.tap(name, chunks => {
|
||||
this.reorderModules(compilation.chunkGraph, chunks);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("webpack").ChunkGraph} graph
|
||||
* @param {Iterable<import("webpack").Chunk>} chunks
|
||||
*/
|
||||
reorderModules(graph, chunks) {
|
||||
const queue = this.collectCssChunks(graph, chunks);
|
||||
|
||||
// Find the last module in the bundle
|
||||
let largestIndex = 0;
|
||||
|
||||
for (const { module, group } of queue) {
|
||||
largestIndex = Math.max(largestIndex, group.getModulePostOrderIndex(module));
|
||||
}
|
||||
|
||||
// Push all vue assets after it in their original order
|
||||
for (const { module, group } of queue) {
|
||||
if (module.identifier().includes('?vue')) {
|
||||
group.setModulePostOrderIndex(
|
||||
module,
|
||||
largestIndex + group.getModulePostOrderIndex(module)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("webpack").ChunkGraph} graph
|
||||
* @param {Iterable<import("webpack").Chunk>} chunks
|
||||
*/
|
||||
collectCssChunks(graph, chunks) {
|
||||
const queue = [];
|
||||
|
||||
for (const chunk of chunks) {
|
||||
for (const module of graph.getChunkModulesIterable(chunk)) {
|
||||
if (module.type !== 'css/mini-extract') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const group of chunk.groupsIterable) {
|
||||
queue.push({ module, chunk, group });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppendVueStylesPlugin;
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
let Log = require('../Log');
|
||||
let collect = require('collect.js');
|
||||
|
||||
class CustomTasksPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {import('../Mix')} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tapPromise(this.constructor.name, async stats => {
|
||||
await this.runTasks(stats);
|
||||
|
||||
if (this.mix.components.get('version') && !this.mix.isUsing('hmr')) {
|
||||
this.applyVersioning();
|
||||
}
|
||||
|
||||
if (this.mix.inProduction()) {
|
||||
await this.minifyAssets();
|
||||
}
|
||||
|
||||
if (this.mix.isWatching()) {
|
||||
this.mix.tasks.forEach(task => task.watch(this.mix.isPolling()));
|
||||
}
|
||||
|
||||
this.mix.manifest.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the task.
|
||||
*
|
||||
* @param {import("../tasks/Task")} task
|
||||
* @param {import("webpack").Stats} stats
|
||||
*/
|
||||
async runTask(task, stats) {
|
||||
await Promise.resolve(task.run());
|
||||
|
||||
await Promise.allSettled(task.assets.map(asset => this.addAsset(asset, stats)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add asset to the webpack stats.
|
||||
*
|
||||
* @param {import("../File")} asset
|
||||
* @param {import("webpack").Stats} stats
|
||||
*/
|
||||
async addAsset(asset, stats) {
|
||||
// Skip adding directories to the manifest
|
||||
// TODO: We should probably add the directory but skip hashing
|
||||
if (asset.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = asset.pathFromPublic();
|
||||
|
||||
// Add the asset to the manifest
|
||||
this.mix.manifest.add(path);
|
||||
|
||||
// Update the Webpack assets list for better terminal output.
|
||||
stats.compilation.assets[path] = {
|
||||
size: () => asset.size(),
|
||||
emitted: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute potentially asynchronous tasks sequentially.
|
||||
*
|
||||
* @param stats
|
||||
* @param index
|
||||
*/
|
||||
runTasks(stats, index = 0) {
|
||||
if (index === this.mix.tasks.length) return Promise.resolve();
|
||||
|
||||
const task = this.mix.tasks[index];
|
||||
|
||||
return this.runTask(task, stats).then(() => this.runTasks(stats, index + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the given asset file.
|
||||
*/
|
||||
async minifyAssets() {
|
||||
const assets = collect(this.mix.tasks)
|
||||
.where('constructor.name', '!==', 'VersionFilesTask')
|
||||
.flatMap(({ assets }) => assets);
|
||||
|
||||
const tasks = assets.map(async asset => {
|
||||
try {
|
||||
await asset.minify();
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
`Whoops! We had trouble minifying "${asset.relativePath()}". ` +
|
||||
`Perhaps you need to use mix.babel() instead?`
|
||||
);
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(tasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version all files that are present in the manifest.
|
||||
*/
|
||||
applyVersioning() {
|
||||
collect(this.mix.manifest.get()).each((value, key) =>
|
||||
this.mix.manifest.hash(key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomTasksPlugin;
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
class ManifestPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {import("../Mix")} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
// TODO: Simplify in Mix 7 -- Here for backwards compat if a plugin creates this class directly
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync('ManifestPlugin', (curCompiler, callback) => {
|
||||
let stats = curCompiler.getStats().toJson();
|
||||
|
||||
// Handle the creation of the mix-manifest.json file.
|
||||
this.mix.manifest.transform(stats).refresh();
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ManifestPlugin;
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
let dotenv = require('dotenv');
|
||||
let expand = require('dotenv-expand');
|
||||
|
||||
/** @internal */
|
||||
class MixDefinitionsPlugin {
|
||||
/**
|
||||
*
|
||||
* @param {string} envPath
|
||||
* @param {Record<string, string>} [additionalEnv]
|
||||
*/
|
||||
constructor(envPath, additionalEnv = {}) {
|
||||
this.envPath = envPath;
|
||||
this.additionalEnv = additionalEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('webpack').Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
this.plugin.apply(compiler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all MIX_ definitions for Webpack's DefinePlugin.
|
||||
*/
|
||||
get env() {
|
||||
// Load .env, if it exists, into process.env
|
||||
expand(dotenv.config({ path: this.envPath }));
|
||||
|
||||
// Take everything from process.env that beings with MIX_
|
||||
const regex = /^MIX_/i;
|
||||
const existing = Object.fromEntries(
|
||||
Object.entries(process.env).filter(([key]) => regex.test(key))
|
||||
);
|
||||
|
||||
// Merge in env vaiues from:
|
||||
// - process.env
|
||||
// - the .env file
|
||||
// - the additional env provided to the plugin
|
||||
return {
|
||||
...existing,
|
||||
...this.additionalEnv
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the necessary definitions and add them to the DefinePlugin.
|
||||
*/
|
||||
get plugin() {
|
||||
const { EnvironmentPlugin } = require('webpack');
|
||||
|
||||
return new EnvironmentPlugin(this.env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all MIX_ definitions for Webpack's DefinePlugin.
|
||||
* This is no longer used but here for backwards compat.
|
||||
*
|
||||
* @deprecated
|
||||
* @param {Record<string, string>} additionalEnv
|
||||
*/
|
||||
getDefinitions(additionalEnv) {
|
||||
return Object.fromEntries(
|
||||
Object.entries({ ...this.env, ...additionalEnv }).map(([key, value]) => {
|
||||
return [`process.env.${key}`, JSON.stringify(value)];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the necessary definitions and add them to the DefinePlugin.
|
||||
*
|
||||
* Here for backwards compat only
|
||||
* @deprecated
|
||||
* @param {Record<string, string>} additionalEnv
|
||||
*/
|
||||
static build(additionalEnv) {
|
||||
return new MixDefinitionsPlugin(global.Mix.paths.root('.env'), additionalEnv)
|
||||
.plugin;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MixDefinitionsPlugin;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
let File = require('../File');
|
||||
let path = require('path');
|
||||
|
||||
class MockEntryPlugin {
|
||||
/**
|
||||
* @param {import('../Mix')} mix
|
||||
*/
|
||||
constructor(mix) {
|
||||
// TODO: Remove in Mix 7 -- Here for backwards compat if a plugin requires this file
|
||||
this.mix = mix || global.Mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the deletion of the temporary mix.js
|
||||
* output file that was generated by webpack.
|
||||
*
|
||||
* This file is created when the user hasn't
|
||||
* requested any JavaScript compilation, but
|
||||
* webpack still requires an entry.
|
||||
*
|
||||
* @param {import("webpack").Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tap('MockEntryPlugin', stats => {
|
||||
const assets = stats.toJson().assets || [];
|
||||
const temporaryOutputFile = assets.find(asset => asset.name === 'mix.js');
|
||||
|
||||
if (!temporaryOutputFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete stats.compilation.assets[temporaryOutputFile.name];
|
||||
|
||||
File.find(
|
||||
path.resolve(this.mix.config.publicPath, temporaryOutputFile.name)
|
||||
).delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockEntryPlugin;
|
||||
+461
@@ -0,0 +1,461 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
|
||||
// Pulled from @types/browser-sync because reliance on outdated chokidar
|
||||
// -------- @types/browser-sync/index.d.ts --------
|
||||
|
||||
// Type definitions for browser-sync 2.26
|
||||
// Project: http://www.browsersync.io/
|
||||
// Definitions by: Asana <https://asana.com>,
|
||||
// Joe Skeen <https://github.com/joeskeen>
|
||||
// Thomas "Thasmo" Deinhamer <https://thasmo.com/>
|
||||
// Kiyotoshi Ichikawa <https://github.com/aznnomness>
|
||||
// Yuma Hashimoto <https://github.com/yuma84>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
/// <reference types="node" />
|
||||
/// <reference types="serve-static" />
|
||||
|
||||
import * as chokidar from 'chokidar';
|
||||
import * as http from 'http';
|
||||
import { ServeStaticOptions } from 'serve-static';
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* Browsersync includes a user-interface that is accessed via a separate port. The UI allows to controls
|
||||
* all devices, push sync updates and much more.
|
||||
*
|
||||
* port - Default: 3001
|
||||
* weinre.port - Default: 8080
|
||||
* Note: Requires at least version 2.0.0.
|
||||
*/
|
||||
ui?: UIOptions | boolean;
|
||||
/**
|
||||
* Browsersync can watch your files as you work. Changes you make will either be injected into the page (CSS
|
||||
* & images) or will cause all browsers to do a full-page refresh. See anymatch for more information on glob
|
||||
* patterns.
|
||||
* Default: false
|
||||
*/
|
||||
files?: string | (string | FileCallback | object)[];
|
||||
/**
|
||||
* Specify which file events to respond to.
|
||||
* Available events: `add`, `change`, `unlink`, `addDir`, `unlinkDir`
|
||||
*/
|
||||
watchEvents?: WatchEvents | string[];
|
||||
/**
|
||||
* Watch files automatically.
|
||||
*/
|
||||
watch?: boolean;
|
||||
/**
|
||||
* Patterns for any watchers to ignore.
|
||||
* Anything provided here will end up inside 'watchOptions.ignored'.
|
||||
*/
|
||||
ignore?: string[];
|
||||
/**
|
||||
* Serve an index.html file for all non-asset routes.
|
||||
* Useful when using client-routers.
|
||||
*/
|
||||
single?: boolean;
|
||||
/**
|
||||
* File watching options that get passed along to Chokidar. Check their docs for available options
|
||||
* Default: undefined
|
||||
* Note: Requires at least version 2.6.0.
|
||||
*/
|
||||
watchOptions?: chokidar.WatchOptions;
|
||||
/**
|
||||
* Use the built-in static server for basic HTML/JS/CSS websites.
|
||||
* Default: false
|
||||
*/
|
||||
server?: string | boolean | string[] | ServerOptions;
|
||||
/**
|
||||
* Proxy an EXISTING vhost. Browsersync will wrap your vhost with a proxy URL to view your site.
|
||||
* Passing only a URL as a string equates to passing only target property of ProxyOptions type.
|
||||
* target - Default: undefined
|
||||
* ws - Default: undefined
|
||||
* middleware - Default: undefined
|
||||
* reqHeaders - Default: undefined
|
||||
* proxyRes - Default: undefined (http.ServerResponse if expecting single parameter)
|
||||
* proxyReq - Default: undefined
|
||||
*/
|
||||
proxy?: string | ProxyOptions;
|
||||
/**
|
||||
* Use a specific port (instead of the one auto-detected by Browsersync)
|
||||
* Default: 3000
|
||||
*/
|
||||
port?: number;
|
||||
/**
|
||||
* Functions or actual plugins used as middleware.
|
||||
*/
|
||||
middleware?:
|
||||
| MiddlewareHandler
|
||||
| PerRouteMiddleware
|
||||
| (MiddlewareHandler | PerRouteMiddleware)[];
|
||||
/**
|
||||
* Add additional directories from which static files should be served.
|
||||
* Should only be used in proxy or snippet mode.
|
||||
* Default: []
|
||||
* Note: Requires at least version 2.8.0.
|
||||
*/
|
||||
serveStatic?: StaticOptions[] | string[];
|
||||
/**
|
||||
* Options that are passed to the serve-static middleware when you use the
|
||||
* string[] syntax: eg: `serveStatic: ['./app']`.
|
||||
* Please see [serve-static](https://github.com/expressjs/serve-static) for details.
|
||||
*/
|
||||
serveStaticOptions?: ServeStaticOptions;
|
||||
/**
|
||||
* Enable https for localhost development.
|
||||
* Note: This may not be needed for proxy option as it will try to infer from your target url.
|
||||
* Note: If privacy error is encountered please see HttpsOptions below, setting those will resolve.
|
||||
* Note: Requires at least version 1.3.0.
|
||||
*/
|
||||
https?: boolean | HttpsOptions;
|
||||
/**
|
||||
* Override http module to allow using 3rd party server modules (such as http2).
|
||||
*/
|
||||
httpModule?: string;
|
||||
/**
|
||||
* Clicks, Scrolls & Form inputs on any device will be mirrored to all others.
|
||||
* clicks - Default: true
|
||||
* scroll - Default: true
|
||||
* forms - Default: true
|
||||
*/
|
||||
ghostMode?: GhostOptions | boolean;
|
||||
/**
|
||||
* Can be either "info", "debug", "warn", or "silent"
|
||||
* Default: info
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
/**
|
||||
* Change the console logging prefix. Useful if you're creating your own project based on Browsersync
|
||||
* Default: BS
|
||||
* Note: Requires at least version 1.5.1.
|
||||
*/
|
||||
logPrefix?: string;
|
||||
/**
|
||||
* Whether or not to log connections
|
||||
* Default: false
|
||||
*/
|
||||
logConnections?: boolean;
|
||||
/**
|
||||
* Whether or not to log information about changed files
|
||||
* Default: false
|
||||
*/
|
||||
logFileChanges?: boolean;
|
||||
/**
|
||||
* Log the snippet to the console when you're in snippet mode (no proxy/server)
|
||||
* Default: true
|
||||
* Note: Requires at least version 1.5.2.
|
||||
*/
|
||||
logSnippet?: boolean;
|
||||
/**
|
||||
* You can control how the snippet is injected onto each page via a custom regex + function.
|
||||
* You can also provide patterns for certain urls that should be ignored from the snippet injection.
|
||||
* Note: Requires at least version 2.0.0.
|
||||
*/
|
||||
snippetOptions?: SnippetOptions;
|
||||
/**
|
||||
* Add additional HTML rewriting rules.
|
||||
* Default: false
|
||||
* Note: Requires at least version 2.4.0.
|
||||
*/
|
||||
rewriteRules?: boolean | RewriteRules[];
|
||||
/**
|
||||
* Tunnel the Browsersync server through a random Public URL
|
||||
* Default: null
|
||||
*/
|
||||
tunnel?: string | boolean;
|
||||
/**
|
||||
* Some features of Browsersync (such as xip & tunnel) require an internet connection, but if you're
|
||||
* working offline, you can reduce start-up time by setting this option to false
|
||||
*/
|
||||
online?: boolean;
|
||||
/**
|
||||
* Default: true
|
||||
* Decide which URL to open automatically when Browsersync starts. Defaults to "local" if none set.
|
||||
* Can be true, local, external, ui, ui-external, tunnel or false
|
||||
*/
|
||||
open?: OpenOptions | boolean;
|
||||
/**
|
||||
* The browser(s) to open
|
||||
* Default: default
|
||||
*/
|
||||
browser?: string | string[];
|
||||
/**
|
||||
* Add HTTP access control (CORS) headers to assets served by Browsersync.
|
||||
* Default: false
|
||||
* Note: Requires at least version 2.16.0.
|
||||
*/
|
||||
cors?: boolean;
|
||||
/**
|
||||
* Requires an internet connection - useful for services such as Typekit as it allows you to configure
|
||||
* domains such as *.xip.io in your kit settings
|
||||
* Default: false
|
||||
*/
|
||||
xip?: boolean;
|
||||
/**
|
||||
* Reload each browser when Browsersync is restarted.
|
||||
* Default: false
|
||||
*/
|
||||
reloadOnRestart?: boolean;
|
||||
/**
|
||||
* The small pop-over notifications in the browser are not always needed/wanted.
|
||||
* Default: true
|
||||
*/
|
||||
notify?: boolean;
|
||||
/**
|
||||
* scrollProportionally: false // Sync viewports to TOP position
|
||||
* Default: true
|
||||
*/
|
||||
scrollProportionally?: boolean;
|
||||
/**
|
||||
* How often to send scroll events
|
||||
* Default: 0
|
||||
*/
|
||||
scrollThrottle?: number;
|
||||
/**
|
||||
* Decide which technique should be used to restore scroll position following a reload.
|
||||
* Can be window.name or cookie
|
||||
* Default: 'window.name'
|
||||
*/
|
||||
scrollRestoreTechnique?: string;
|
||||
/**
|
||||
* Sync the scroll position of any element on the page. Add any amount of CSS selectors
|
||||
* Default: []
|
||||
* Note: Requires at least version 2.9.0.
|
||||
*/
|
||||
scrollElements?: string[];
|
||||
/**
|
||||
* Default: []
|
||||
* Note: Requires at least version 2.9.0.
|
||||
* Sync the scroll position of any element on the page - where any scrolled element will cause
|
||||
* all others to match scroll position. This is helpful when a breakpoint alters which element
|
||||
* is actually scrolling
|
||||
*/
|
||||
scrollElementMapping?: string[];
|
||||
/**
|
||||
* Time, in milliseconds, to wait before instructing the browser to reload/inject following a file
|
||||
* change event
|
||||
* Default: 0
|
||||
*/
|
||||
reloadDelay?: number;
|
||||
/**
|
||||
* Restrict the frequency in which browser:reload events can be emitted to connected clients
|
||||
* Default: 0
|
||||
* Note: Requires at least version 2.6.0.
|
||||
*/
|
||||
reloadDebounce?: number;
|
||||
/**
|
||||
* Emit only the first event during sequential time windows of a specified duration.
|
||||
* Note: Requires at least version 2.13.0.
|
||||
*/
|
||||
reloadThrottle?: number;
|
||||
/**
|
||||
* User provided plugins
|
||||
* Default: []
|
||||
* Note: Requires at least version 2.6.0.
|
||||
*/
|
||||
plugins?: any[];
|
||||
/**
|
||||
* Whether to inject changes (rather than a page refresh)
|
||||
* Default: true
|
||||
*/
|
||||
injectChanges?: boolean;
|
||||
/**
|
||||
* The initial path to load
|
||||
*/
|
||||
startPath?: string;
|
||||
/**
|
||||
* Whether to minify the client script
|
||||
* Default: true
|
||||
*/
|
||||
minify?: boolean;
|
||||
/**
|
||||
* Override host detection if you know the correct IP to use
|
||||
*/
|
||||
host?: string;
|
||||
/**
|
||||
* Support environments where dynamic hostnames are not required (ie: electron).
|
||||
*/
|
||||
localOnly?: boolean;
|
||||
/**
|
||||
* Send file-change events to the browser
|
||||
* Default: true
|
||||
*/
|
||||
codeSync?: boolean;
|
||||
/**
|
||||
* Append timestamps to injected files
|
||||
* Default: true
|
||||
*/
|
||||
timestamps?: boolean;
|
||||
/**
|
||||
* ¯\_(ツ)_/¯
|
||||
* Best guess, when ghostMode (or SocketIO?) is setup the events
|
||||
* listed here will be emitted and able to hook into.
|
||||
*/
|
||||
clientEvents?: string[];
|
||||
/**
|
||||
* Alter the script path for complete control over where the Browsersync Javascript is served
|
||||
* from. Whatever you return from this function will be used as the script path.
|
||||
* Note: Requires at least version 1.5.0.
|
||||
*/
|
||||
scriptPath?: (path: string) => string;
|
||||
/**
|
||||
* Configure the Socket.IO path and namespace & domain to avoid collisions.
|
||||
* path - Default: "/browser-sync/socket.io"
|
||||
* clientPath - Default: "/browser-sync"
|
||||
* namespace - Default: "/browser-sync"
|
||||
* domain - Default: undefined
|
||||
* port - Default: undefined
|
||||
* clients.heartbeatTimeout - Default: 5000
|
||||
* Note: Requires at least version 1.6.2.
|
||||
*/
|
||||
socket?: SocketOptions;
|
||||
/**
|
||||
* ¯\_(ツ)_/¯
|
||||
*/
|
||||
tagNames?: TagNamesOptions;
|
||||
/**
|
||||
* ¯\_(ツ)_/¯
|
||||
*/
|
||||
injectFileTypes?: string[];
|
||||
/**
|
||||
* ¯\_(ツ)_/¯
|
||||
*/
|
||||
excludeFileTypes?: string[];
|
||||
}
|
||||
|
||||
type WatchEvents = 'add' | 'change' | 'unlink' | 'addDir' | 'unlinkDir';
|
||||
|
||||
type LogLevel = 'info' | 'debug' | 'warn' | 'silent';
|
||||
|
||||
type OpenOptions = 'local' | 'external' | 'ui' | 'ui-external' | 'tunnel';
|
||||
|
||||
interface Hash<T> {
|
||||
[path: string]: T;
|
||||
}
|
||||
|
||||
interface UIOptions {
|
||||
/** set the default port */
|
||||
port?: number;
|
||||
/** set the default weinre port */
|
||||
weinre?: {
|
||||
port?: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface FileCallback {
|
||||
match?: string | string[];
|
||||
fn: (event: string, file: string) => any;
|
||||
options?: chokidar.WatchOptions;
|
||||
}
|
||||
|
||||
interface ServerOptions {
|
||||
/** set base directory */
|
||||
baseDir?: string | string[];
|
||||
/** enable directory listing */
|
||||
directory?: boolean;
|
||||
/** set index filename */
|
||||
index?: string;
|
||||
/**
|
||||
* key-value object hash, where the key is the url to match,
|
||||
* and the value is the folder to serve (relative to your working directory)
|
||||
*/
|
||||
routes?: Hash<string>;
|
||||
/** configure custom middleware */
|
||||
middleware?: (MiddlewareHandler | PerRouteMiddleware)[];
|
||||
serveStaticOptions?: ServeStaticOptions;
|
||||
}
|
||||
|
||||
interface ProxyOptions {
|
||||
target?: string;
|
||||
middleware?: MiddlewareHandler;
|
||||
ws?: boolean;
|
||||
reqHeaders?: (config: object) => Hash<object>;
|
||||
proxyRes?: ProxyResponseMiddleware | ProxyResponseMiddleware[];
|
||||
proxyReq?:
|
||||
| ((res: http.IncomingMessage) => void)[]
|
||||
| ((res: http.IncomingMessage) => void);
|
||||
error?: (
|
||||
err: NodeJS.ErrnoException,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
) => void;
|
||||
}
|
||||
|
||||
interface ProxyResponseMiddleware {
|
||||
(
|
||||
proxyRes: http.ServerResponse | http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
req: http.IncomingMessage
|
||||
): void;
|
||||
}
|
||||
|
||||
interface HttpsOptions {
|
||||
key?: string;
|
||||
cert?: string;
|
||||
}
|
||||
|
||||
interface StaticOptions {
|
||||
route: string | string[];
|
||||
dir: string | string[];
|
||||
}
|
||||
|
||||
interface MiddlewareHandler {
|
||||
(req: http.IncomingMessage, res: http.ServerResponse, next: () => void): any;
|
||||
}
|
||||
|
||||
interface PerRouteMiddleware {
|
||||
id?: string;
|
||||
route: string;
|
||||
handle: MiddlewareHandler;
|
||||
}
|
||||
|
||||
interface GhostOptions {
|
||||
clicks?: boolean;
|
||||
scroll?: boolean;
|
||||
forms?: FormsOptions | boolean;
|
||||
}
|
||||
|
||||
interface FormsOptions {
|
||||
inputs: boolean;
|
||||
submit: boolean;
|
||||
toggles: boolean;
|
||||
}
|
||||
|
||||
interface SnippetOptions {
|
||||
async?: boolean;
|
||||
whitelist?: string[];
|
||||
blacklist?: string[];
|
||||
rule?: {
|
||||
match?: RegExp;
|
||||
fn?: (snippet: string, match: string) => any;
|
||||
};
|
||||
}
|
||||
|
||||
interface SocketOptions {
|
||||
path?: string;
|
||||
clientPath?: string;
|
||||
namespace?: string;
|
||||
domain?: string;
|
||||
port?: number;
|
||||
clients?: { heartbeatTimeout?: number };
|
||||
}
|
||||
|
||||
interface TagNamesOptions {
|
||||
less?: string;
|
||||
scss?: string;
|
||||
css?: string;
|
||||
jpg?: string;
|
||||
jpeg?: string;
|
||||
png?: string;
|
||||
svg?: string;
|
||||
gif?: string;
|
||||
js?: string;
|
||||
}
|
||||
|
||||
interface RewriteRules {
|
||||
match: RegExp;
|
||||
replace?: string;
|
||||
fn?: (req: http.IncomingMessage, res: http.ServerResponse, match: string) => string;
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
|
||||
import * as webpack from 'webpack';
|
||||
import { TransformOptions as BabelConfig } from '@babel/core';
|
||||
import Entry from '../src/builder/Entry';
|
||||
|
||||
export type DependencyObject = {
|
||||
/** The name of the package */
|
||||
package: string;
|
||||
|
||||
/**
|
||||
* An additional, manual check to see if the package that is installed matches necessary expectations.
|
||||
*
|
||||
* For example you may use this to see if the installed package is the right version or has an expected API.
|
||||
*
|
||||
* The passed object is `require(package)`
|
||||
**/
|
||||
check?: (obj: any) => boolean;
|
||||
};
|
||||
|
||||
/** A required depedency for this component to work */
|
||||
export type Dependency = string | DependencyObject;
|
||||
|
||||
export interface ClassComponent {
|
||||
prototype: ComponentInterface;
|
||||
new (): ComponentInterface;
|
||||
}
|
||||
|
||||
export interface ComponentInterface {
|
||||
/** Whether or not to automatically register this component */
|
||||
passive?: boolean;
|
||||
|
||||
/** Whether or not this component requires dependency reloading */
|
||||
requiresReload?: boolean;
|
||||
|
||||
/**
|
||||
* Register this component
|
||||
*
|
||||
* `args` reflect what was passed in via `mix.component_name(...args)`
|
||||
**/
|
||||
register?(...args: any[]): void;
|
||||
|
||||
/**
|
||||
* The public API name / names for this component.
|
||||
* These get attached to the mix object and when called will register this compoent.
|
||||
*
|
||||
* For example if name returns `['foo', 'bar']` then you may
|
||||
* register this component via both `mix.foo()` and `mix.bar()`
|
||||
**/
|
||||
name?(): string | string[];
|
||||
|
||||
/**
|
||||
* Specifiy one or more dependencies that must
|
||||
* be installed for this component to work
|
||||
**/
|
||||
dependencies?(): Dependency | Dependency[];
|
||||
|
||||
/**
|
||||
* Boot this component
|
||||
* This is called after registration and dependencies have been installed
|
||||
* but before any configuration changes have taken place
|
||||
**/
|
||||
boot?(): void;
|
||||
|
||||
/** Modify the babel config */
|
||||
babelConfig?(): BabelConfig;
|
||||
|
||||
/** Update the build webpack entries */
|
||||
webpackEntry?(entry: Entry): void;
|
||||
|
||||
/** Add one or more rules to the webpack config */
|
||||
webpackRules?(): webpack.RuleSetRule | webpack.RuleSetRule[];
|
||||
|
||||
/** Add one or more plugins to the webpack config */
|
||||
webpackPlugins?(): webpack.WebpackPluginInstance[];
|
||||
|
||||
/** Update the webpack config */
|
||||
webpackConfig?(config: webpack.Configuration): void;
|
||||
|
||||
/** Add additional components to the Mix API */
|
||||
mix?(): Record<string, Component>;
|
||||
}
|
||||
|
||||
export interface FunctionalComponent {
|
||||
(config: webpack.Configuration, ...args: any[]): void;
|
||||
}
|
||||
|
||||
export type InstallableComponent = ComponentInterface | ClassComponent;
|
||||
export type Component = InstallableComponent | FunctionalComponent;
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
|
||||
import * as webpack from 'webpack';
|
||||
import { Options as AutoprefixerConfig } from 'autoprefixer';
|
||||
import { Options as CleanCssConfig } from 'clean-css';
|
||||
// import { CssNanoOptions as CssNanoConfig } from 'cssnano';
|
||||
import { CssNanoOptions as CssNanoConfig } from './cssnano';
|
||||
import { Options as GifsicleConfig } from 'imagemin-gifsicle';
|
||||
import { Options as MozjpegConfig } from 'imagemin-mozjpeg';
|
||||
import { Options as OptipngConfig } from 'imagemin-optipng';
|
||||
import { Options as SvgoConfig } from 'imagemin-svgo';
|
||||
import { TransformOptions as BabelConfig } from '@babel/core';
|
||||
// import { TerserPluginOptions } from 'terser-webpack-plugin';
|
||||
import { TerserPluginOptions } from './terser';
|
||||
import { AcceptedPlugin } from 'postcss';
|
||||
|
||||
interface MixConfig {
|
||||
/** Determine if webpack should be triggered in a production environment. */
|
||||
production?: boolean;
|
||||
|
||||
/** Determine if we should enable hot reloading. */
|
||||
hmr?: boolean;
|
||||
|
||||
/** Hostname and port used for the hot reload module */
|
||||
hmrOptions?: {
|
||||
host: string;
|
||||
port: string;
|
||||
};
|
||||
|
||||
/** Determine if we should enable https on hot reloading. */
|
||||
https?: boolean;
|
||||
|
||||
/**
|
||||
* PostCSS plugins to be applied to compiled CSS.
|
||||
*
|
||||
* See: https://github.com/postcss/postcss/blob/master/docs/plugins.md
|
||||
**/
|
||||
postCss?: AcceptedPlugin[];
|
||||
|
||||
/**
|
||||
* Determine if we should enable autoprefixer by default.
|
||||
* May be set to false to disable it.
|
||||
**/
|
||||
autoprefixer?: false | AutoprefixerConfig;
|
||||
|
||||
/** The public path for the build. */
|
||||
publicPath?: string;
|
||||
|
||||
/**
|
||||
* The name / path to the mix manifest.
|
||||
* The path is relative to the public path.
|
||||
*
|
||||
* Set to `false` to disable manifest generation.
|
||||
**/
|
||||
manifest?: string | false;
|
||||
|
||||
/**
|
||||
* The path for the runtime chunk (`manifest.js`).
|
||||
*
|
||||
* Defaults to being placed next to compiled JS files.
|
||||
**/
|
||||
runtimeChunkPath?: string | null;
|
||||
|
||||
/** Determine if error notifications should be displayed for each build. */
|
||||
notifications?: {
|
||||
onSuccess?: boolean;
|
||||
onFailure?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if sourcemaps should be created for the build.
|
||||
*
|
||||
* @deprecated Please use mix.sourceMaps() instead
|
||||
**/
|
||||
sourcemaps?: false | string;
|
||||
|
||||
/** The resource root for the build. */
|
||||
resourceRoot?: string;
|
||||
|
||||
/**
|
||||
* Image Loader defaults.
|
||||
* See: https://github.com/thetalecrafter/img-loader#options
|
||||
**/
|
||||
imgLoaderOptions?: {
|
||||
enabled?: boolean;
|
||||
gifsicle?: GifsicleConfig;
|
||||
mozjpeg?: MozjpegConfig;
|
||||
optipng?: OptipngConfig;
|
||||
svgo?: SvgoConfig;
|
||||
};
|
||||
|
||||
/** File Loader directory defaults. */
|
||||
fileLoaderDirs?: {
|
||||
images?: string;
|
||||
fonts?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if CSS relative url()s should be resolved by webpack.
|
||||
* Disabling this can improve performance greatly.
|
||||
**/
|
||||
processCssUrls?: boolean;
|
||||
|
||||
/**
|
||||
* Terser-specific settings for Webpack.
|
||||
*
|
||||
* See: https://github.com/webpack-contrib/terser-webpack-plugin#options
|
||||
**/
|
||||
terser?: TerserPluginOptions;
|
||||
|
||||
/**
|
||||
* Sets the css module identifier pattern
|
||||
*
|
||||
* For more information see https://github.com/webpack-contrib/css-loader/tree/v5.2.7#localidentname
|
||||
*/
|
||||
cssModuleIdentifier?: string;
|
||||
|
||||
/**
|
||||
* cssnano-specific settings for Webpack.
|
||||
* Disabled if set to false.
|
||||
*
|
||||
* See: https://cssnano.co/optimisations/
|
||||
**/
|
||||
cssNano?: false | CssNanoConfig;
|
||||
|
||||
/**
|
||||
* CleanCss-specific settings for Webpack.
|
||||
*
|
||||
* See: https://github.com/jakubpawlowicz/clean-css#constructor-options
|
||||
**/
|
||||
cleanCss?: CleanCssConfig;
|
||||
|
||||
/**
|
||||
* Custom Webpack-specific configuration to merge/override Mix's.
|
||||
*
|
||||
* @see mix.webpackConfig()
|
||||
**/
|
||||
webpackConfig?: webpack.Configuration;
|
||||
|
||||
/** Custom Babel configuration to be merged with Mix's defaults. */
|
||||
babelConfig?: BabelConfig;
|
||||
|
||||
/**
|
||||
* Determine if Mix should ask the friendly errors plugin to
|
||||
* clear the console before outputting the results or not.
|
||||
*
|
||||
* https://github.com/geowarin/friendly-errors-webpack-plugin#options
|
||||
**/
|
||||
clearConsole?: boolean;
|
||||
|
||||
/**
|
||||
* Enable legacy node -> browser polyfills for things like `process` and `Buffer`.
|
||||
*/
|
||||
legacyNodePolyfills?: boolean;
|
||||
|
||||
/**
|
||||
* Options to pass to vue-loader
|
||||
*
|
||||
* @deprecated Use `.vue({options: {…}})` instead
|
||||
**/
|
||||
vue?: any;
|
||||
|
||||
/** The default Babel configuration. */
|
||||
babel?: (babelRcPath?: string) => BabelConfig;
|
||||
|
||||
/** Merge the given options with the current defaults. */
|
||||
merge?: (options: MixConfig) => void;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
// cssnano v5 does not have a types file yet
|
||||
|
||||
import { AcceptedPlugin } from 'postcss';
|
||||
|
||||
type CSSNanoPluginResolved = [AcceptedPlugin, Record<string, any>];
|
||||
type CSSNanoPlugin =
|
||||
| string
|
||||
| AcceptedPlugin
|
||||
| [string, Record<string, any>]
|
||||
| CSSNanoPluginResolved;
|
||||
|
||||
type CSSNanoPresetFn = () => CSSNanoPresetResolved;
|
||||
type CSSNanoPresetResolved = { plugins: CSSNanoPluginResolved[] };
|
||||
type CSSNanoPreset =
|
||||
| string
|
||||
| CSSNanoPresetFn
|
||||
| [string | CSSNanoPresetFn, Record<string, any>]
|
||||
| CSSNanoPresetResolved;
|
||||
|
||||
interface CssNanoOptions {
|
||||
configFile?: string;
|
||||
preset?: CSSNanoPreset;
|
||||
plugins?: CSSNanoPlugin[];
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
|
||||
import * as webpack from 'webpack';
|
||||
|
||||
/**
|
||||
* Specify a custom extraction test
|
||||
* Equivalent to providing a function to webpack's split chunks
|
||||
*/
|
||||
export type ExtractTestCallback = (module: webpack.Module, context: any) => boolean;
|
||||
|
||||
/**
|
||||
* Specify a custom extraction test
|
||||
* Can be a regular expression or a function
|
||||
*/
|
||||
export type Extraction = {
|
||||
/** The path to extract files to */
|
||||
to: string;
|
||||
|
||||
/** A custom test regex or callback — takes precedence over libraries */
|
||||
test: RegExp | ExtractTestCallback;
|
||||
|
||||
/** A list of libraries to match against */
|
||||
libraries: string[] | RegExp;
|
||||
};
|
||||
|
||||
export type ExtractConfig =
|
||||
// A filename for node_modules extractions
|
||||
| string
|
||||
|
||||
// A list of vendor files
|
||||
| string[]
|
||||
|
||||
// Custom extraction test function
|
||||
| ExtractTestCallback
|
||||
|
||||
// Extraction config
|
||||
| Partial<Extraction>;
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import api from './index';
|
||||
import { MixConfig } from './config';
|
||||
import MixHelpers from '../src/Mix';
|
||||
import WebpackConfig from '../src/builder/WebpackConfig';
|
||||
|
||||
type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T;
|
||||
|
||||
declare global {
|
||||
// Mix test-only globals
|
||||
declare var mix = api;
|
||||
|
||||
// General globals added by including Mix
|
||||
declare var Mix: MixHelpers;
|
||||
declare var Config: MixConfig;
|
||||
declare var webpackConfig: WebpackConfig;
|
||||
|
||||
// Helpers added by including Mix
|
||||
declare var tap: <T>(val: T, callback: (v: T) => void) => T;
|
||||
declare var flatten: <T extends unknown[]>(arr: T) => ElementType<T>[];
|
||||
|
||||
interface ArrayConstructor {
|
||||
wrap<T>(val: T | T[] | ArrayLike<T>): T[];
|
||||
}
|
||||
|
||||
interface Array {
|
||||
tap(callback: (arr: this) => void): this;
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
// Project: https://github.com/JeffreyWay/laravel-mix#readme
|
||||
// Definitions by: Geoff Garbers <https://github.com/garbetjie>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
import * as webpack from 'webpack';
|
||||
import { Options } from 'ts-loader';
|
||||
import { TransformOptions as BabelConfig } from '@babel/core';
|
||||
import { Options as BrowserSyncConfig } from './browsersync';
|
||||
import * as ExtractTypes from './extract';
|
||||
import { MixConfig } from './config';
|
||||
import { Component } from './component';
|
||||
|
||||
// @ts-ignore - May not be installed initially
|
||||
import { AcceptedPlugin } from 'postcss';
|
||||
|
||||
// @ts-ignore - May not be installed initially
|
||||
import { VueLoaderOptions } from 'vue-loader';
|
||||
|
||||
// @ts-ignore - May not be installed initially
|
||||
import MixHelpers from '../src/Mix';
|
||||
|
||||
// General API
|
||||
interface Api {
|
||||
/** Set the public path */
|
||||
setPublicPath(path: string): Api;
|
||||
|
||||
/** Set the resource path */
|
||||
setResourceRoot(path: string): Api;
|
||||
|
||||
/** Override Mix config options */
|
||||
options(options: MixConfig): Api;
|
||||
|
||||
/** Determine if mix is building for production */
|
||||
inProduction(): boolean;
|
||||
|
||||
/** Extend the mix api. This makes the component available as mix.name_here */
|
||||
extend(name: string, component: Component): Api;
|
||||
|
||||
/** Wait for a callback before starting the build */
|
||||
before(callback: (Mix: MixHelpers) => void | Promise<void>): Api;
|
||||
|
||||
/** Run a callback after the build has completed */
|
||||
after(callback: (stats: webpack.Stats) => void | Promise<void>): Api;
|
||||
|
||||
/** Run a callback after the build has completed */
|
||||
then(callback: (stats: webpack.Stats) => void | Promise<void>): Api;
|
||||
|
||||
/** Run a callback if a condition is true */
|
||||
when(condition: boolean, callback: (mix: Api) => void | Promise<void>): Api;
|
||||
}
|
||||
|
||||
// Webpack config related abilities
|
||||
interface Api {
|
||||
/** Merge custom webpack config */
|
||||
webpackConfig(config: webpack.Configuration): Api;
|
||||
|
||||
/** Merge custom webpack config */
|
||||
webpackConfig(
|
||||
callback: (
|
||||
webpack: typeof import('webpack'),
|
||||
config: webpack.Configuration
|
||||
) => webpack.Configuration
|
||||
): Api;
|
||||
|
||||
/** Override the webpack config after it is built */
|
||||
override(callback: (config: webpack.Configuration) => void): Api;
|
||||
|
||||
/** Override the used babel config */
|
||||
babelConfig(config: BabelConfig): Api;
|
||||
|
||||
/** Dump the webpack config when mix builds */
|
||||
dump(): Api;
|
||||
|
||||
/** Dump the webpack config when mix builds */
|
||||
dumpWebpackConfig(): Api;
|
||||
}
|
||||
|
||||
type CodeValuePrimitive = undefined | null | string | number | bigint | boolean;
|
||||
type LazyCodeValue = () => CodeValuePrimitive;
|
||||
type CodeValue = CodeValuePrimitive | LazyCodeValue;
|
||||
|
||||
// Assorted capabilities
|
||||
interface Api {
|
||||
/** Add webpack-resolution aliases */
|
||||
alias(paths: Record<string, string | { raw: string }>): Api;
|
||||
|
||||
/** Autoload libraries */
|
||||
autoload(libraries: Record<string, string | string[]>): Api;
|
||||
|
||||
/** Use BrowserSync to monitor files for changes and inject changes into the browser */
|
||||
browserSync(proxy: string): Api;
|
||||
|
||||
/** Use BrowserSync to monitor files for changes and inject changes into the browser */
|
||||
browserSync(config?: BrowserSyncConfig): Api;
|
||||
|
||||
/** Generate source maps */
|
||||
sourceMaps(
|
||||
generateForProduction?: boolean,
|
||||
devType?: string,
|
||||
productionType?: string
|
||||
): Api;
|
||||
|
||||
/**
|
||||
* Version assets by hashing them and placing this hash url used in the mix manifest
|
||||
*
|
||||
* You may optionally pass an additional list of files to version in the mix manifest.
|
||||
**/
|
||||
version(files?: string | string[]): Api;
|
||||
|
||||
/** Disable system notifications when mix builds assets */
|
||||
disableNotifications(): Api;
|
||||
|
||||
/** Disable only success notifications when mix builds assets */
|
||||
disableSuccessNotifications(): Api;
|
||||
|
||||
/** Replace variables in code with other values */
|
||||
define(definitions: Record<string, CodeValue>): Api;
|
||||
}
|
||||
|
||||
// JS / Transpilation capabilities
|
||||
interface Api {
|
||||
/**
|
||||
* Compile modern javascript
|
||||
*
|
||||
* `src` may be a glob pattern
|
||||
**/
|
||||
js(src: string | string[], output: string): Api;
|
||||
|
||||
/**
|
||||
* Compile typescript into JS
|
||||
*
|
||||
* `src` may be a glob pattern
|
||||
**/
|
||||
ts(src: string | string[], output: string, options?: Partial<Options>): Api;
|
||||
|
||||
/**
|
||||
* Compile TypeScript into JS
|
||||
*
|
||||
* `src` may be a glob pattern
|
||||
**/
|
||||
typeScript(src: string | string[], output: string, options?: Partial<Options>): Api;
|
||||
|
||||
/**
|
||||
* Compile CoffeeScript into JS
|
||||
*
|
||||
* `src` may be a glob pattern
|
||||
**/
|
||||
coffee(src: string | string[], output: string): Api;
|
||||
}
|
||||
|
||||
// File related capabilities
|
||||
interface Api {
|
||||
/** Copy files specified by `from` and place them in `to` */
|
||||
copy(from: string | string[], to: string): Api;
|
||||
|
||||
/** Copy files specified by `from` and place them in `to` */
|
||||
copyDirectory(from: string | string[], to: string): Api;
|
||||
|
||||
/**
|
||||
* Concatenate files specified by `src` and place them in `output` (or the public dir by default)
|
||||
*
|
||||
* You may pass a glob pattern to `src` to match several files.
|
||||
**/
|
||||
combine(src: string | string[], output?: string, babel?: boolean): Api;
|
||||
|
||||
/**
|
||||
* Concatenate files specified by `src` and place them in `output` (or the public dir by default)
|
||||
*
|
||||
* You may pass a glob pattern to `src` to match several files.
|
||||
**/
|
||||
babel(src: string | string[], output?: string, babel?: boolean): Api;
|
||||
|
||||
/**
|
||||
* Concatenate files specified by `src` and place them in `output` (or the public dir by default)
|
||||
*
|
||||
* You may pass a glob pattern to `src` to match several files.
|
||||
**/
|
||||
minify(src: string | string[], output?: string, babel?: boolean): Api;
|
||||
|
||||
/**
|
||||
* Concatenate files specified by `src` and place them in `output` (or the public dir by default)
|
||||
*
|
||||
* You may pass a glob pattern to `src` to match several files.
|
||||
**/
|
||||
scripts(src: string | string[], output?: string, babel?: boolean): Api;
|
||||
|
||||
/**
|
||||
* Concatenate files specified by `src` and place them in `output` (or the public dir by default)
|
||||
*
|
||||
* You may pass a glob pattern to `src` to match several files.
|
||||
**/
|
||||
styles(src: string | string[], output?: string, babel?: boolean): Api;
|
||||
}
|
||||
|
||||
// Styling related capabilities
|
||||
interface Api {
|
||||
/** Compile CSS via PostCSS */
|
||||
css(src: string, output: string, plugins?: AcceptedPlugin[]): Api;
|
||||
|
||||
/** Compile CSS via PostCSS */
|
||||
css(
|
||||
src: string,
|
||||
output: string,
|
||||
pluginOptions?: object,
|
||||
plugins?: AcceptedPlugin[]
|
||||
): Api;
|
||||
|
||||
/** Compile CSS via PostCSS */
|
||||
postCss(src: string, output: string, plugins?: AcceptedPlugin[]): Api;
|
||||
|
||||
/** Compile CSS via PostCSS */
|
||||
postCss(
|
||||
src: string,
|
||||
output: string,
|
||||
pluginOptions?: object,
|
||||
plugins?: AcceptedPlugin[]
|
||||
): Api;
|
||||
|
||||
/** Compile Stylus into CSS */
|
||||
stylus(
|
||||
src: string,
|
||||
output: string,
|
||||
pluginOptions?: object,
|
||||
postCssPlugins?: AcceptedPlugin[]
|
||||
): Api;
|
||||
|
||||
/** Compile Less into CSS */
|
||||
less(
|
||||
src: string,
|
||||
output: string,
|
||||
pluginOptions?: object,
|
||||
postCssPlugins?: AcceptedPlugin[]
|
||||
): Api;
|
||||
|
||||
/** Compile Sass/SCSS into CSS */
|
||||
sass(
|
||||
src: string,
|
||||
output: string,
|
||||
pluginOptions?: object,
|
||||
postCssPlugins?: AcceptedPlugin[]
|
||||
): Api;
|
||||
}
|
||||
|
||||
// Vendor extraction
|
||||
interface Api {
|
||||
/** Extract all matching chunks to `config.to` / `output` / vendor.js */
|
||||
extract(config: Partial<ExtractTypes.Extraction>, output?: string): Api;
|
||||
|
||||
/** Extract all chunks matching the given test to `output` / vendor.js */
|
||||
extract(test: ExtractTypes.ExtractTestCallback, output?: string): Api;
|
||||
|
||||
/** Extract the given libraries into `output` / vendor.js */
|
||||
extract(libs: string[], output?: string): Api;
|
||||
|
||||
/** Extract libraries from node_modules into `output` / vendor.js */
|
||||
extract(output?: string): Api;
|
||||
}
|
||||
|
||||
type VueConfig = {
|
||||
/** Which version of Vue to support. Detected automatically if not given. */
|
||||
version?: number;
|
||||
|
||||
/** A file to include w/ every vue style block. */
|
||||
globalStyles?: false | string | string[] | Record<string, string | string[]> | null;
|
||||
|
||||
/** Whether or not to extract vue styles. If given a string the name of the file to extract to. */
|
||||
extractStyles?: boolean | string;
|
||||
|
||||
/** Use vue-style-loader to extract Vue Styles. */
|
||||
useVueStyleLoader?: boolean;
|
||||
|
||||
/** Whether or not to use the runtime only version of Vue. */
|
||||
runtimeOnly?: boolean;
|
||||
|
||||
/** Options to pass to Vue Loader */
|
||||
options?: VueLoaderOptions;
|
||||
};
|
||||
|
||||
type ReactConfig = {
|
||||
/** Whether or not to extract React styles. If given a string the name of the file to extract to. */
|
||||
extractStyles?: boolean | string;
|
||||
};
|
||||
|
||||
// Third-party support "feature flags"
|
||||
interface Api {
|
||||
/** Enable support for Preact */
|
||||
preact(): Api;
|
||||
|
||||
/** Enable support for React */
|
||||
react(config?: ReactConfig): Api;
|
||||
|
||||
/**
|
||||
* Enable support for Vue 2 or 3
|
||||
*
|
||||
* The version of Vue in use is detected but may be specified explicitly using `version`
|
||||
*
|
||||
* Pass options to configure global styles and Vue component style extraction
|
||||
**/
|
||||
vue(config?: VueConfig): Api;
|
||||
}
|
||||
|
||||
declare const exports: Api;
|
||||
declare namespace exports {
|
||||
export { Api, ReactConfig, VueConfig };
|
||||
}
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* The context for the current mix build group
|
||||
*
|
||||
* @deprecated This will go away in a future release
|
||||
**/
|
||||
var Mix: import('../src/Mix.js');
|
||||
|
||||
/**
|
||||
* The webpack config helper for the current mix build group
|
||||
*
|
||||
* @deprecated This will go away in a future release
|
||||
**/
|
||||
var webpackConfig: import('../src/builder/WebpackConfig');
|
||||
|
||||
/**
|
||||
* The options for the current mix build group
|
||||
*
|
||||
* @deprecated This will go away in a future release
|
||||
**/
|
||||
var Config: ReturnType<typeof import('../src/config')>;
|
||||
}
|
||||
|
||||
export = exports;
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Type definitions for laravel-mix 6.0
|
||||
|
||||
// This is a workaround because `TerserPluginOptions` isn't exported from webpack-terser-plugin
|
||||
|
||||
// import { Rules } from 'webpack';
|
||||
import { MinifyOptions } from 'terser';
|
||||
import { RawSourceMap } from 'source-map';
|
||||
|
||||
// This type isn't exported from webpack…
|
||||
type Rules = string | RegExp | (string | RegExp)[];
|
||||
|
||||
export type TerserPluginOptions = {
|
||||
test?: Rules;
|
||||
include?: Rules;
|
||||
exclude?: Rules;
|
||||
terserOptions?: MinifyOptions;
|
||||
extractComments?: ExtractCommentsOptions;
|
||||
parallel?: boolean;
|
||||
minify?: CustomMinifyFunction;
|
||||
};
|
||||
|
||||
// None of these types are exported from Terser
|
||||
type ExtractCommentsBanner = boolean | string | ((commentsFile: string) => string);
|
||||
type ExtractCommentsCondition = boolean | string | RegExp | ExtractCommentsFunction;
|
||||
type ExtractCommentsFilename = string | ((fileData: any) => string);
|
||||
|
||||
type ExtractCommentsObject = {
|
||||
condition: ExtractCommentsCondition;
|
||||
filename: ExtractCommentsFilename;
|
||||
banner: ExtractCommentsBanner;
|
||||
};
|
||||
|
||||
type ExtractCommentsFunction = (
|
||||
astNode: any,
|
||||
comment: {
|
||||
value: string;
|
||||
type: 'comment1' | 'comment2' | 'comment3' | 'comment4';
|
||||
pos: number;
|
||||
line: number;
|
||||
col: number;
|
||||
}
|
||||
) => boolean;
|
||||
|
||||
type ExtractCommentsOptions = ExtractCommentsCondition | ExtractCommentsObject;
|
||||
|
||||
type CustomMinifyFunction = (
|
||||
file: Record<string, string>,
|
||||
sourceMap: RawSourceMap,
|
||||
minifyOptions: MinifyOptions
|
||||
) => void;
|
||||
Reference in New Issue
Block a user