This commit is contained in:
2022-04-07 18:09:31 +05:30
parent 36ea86d8e6
commit 1c779ef816
14095 changed files with 1769361 additions and 47 deletions
+113
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
View File
+71
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};