ui
This commit is contained in:
+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);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user