src/services/configurations/browserProductionConfiguration.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { provider } = require('jimple');
const ConfigurationFile = require('../../abstracts/configurationFile');
const { ProjextWebpackRuntimeDefinitions } = require('../../plugins');
/**
* Creates the specifics of a Webpack configuration for a browser target production build.
* @extends {ConfigurationFile}
*/
class WebpackBrowserProductionConfiguration extends ConfigurationFile {
/**
* Class constructor.
* @param {Events} events To reduce the configuration.
* @param {PathUtils} pathUtils Required by `ConfigurationFile`
* in order to build the path to the
* overwrite file.
* @param {TargetsHTML} targetsHTML The service in charge of generating
* a default HTML file in case the
* target doesn't have one.
* @param {WebpackBaseConfiguration} webpackBaseConfiguration The configuration this one will
* extend.
*/
constructor(
events,
pathUtils,
targetsHTML,
webpackBaseConfiguration
) {
super(
pathUtils,
[
'config/webpack/browser.production.config.js',
'config/webpack/browser.config.js',
],
true,
webpackBaseConfiguration
);
/**
* A local reference for the `events` service.
* @type {Events}
*/
this.events = events;
/**
* A local reference for the `targetsHTML` service.
* @type {TargetsHTML}
*/
this.targetsHTML = targetsHTML;
}
/**
* Create the configuration with the `entry`, the `output` and the plugins specifics for a
* browser target production build.
* This method uses the reducer events `webpack-browser-production-configuration` and
* `webpack-browser-configuration`. It sends the configuration, the received `params` and
* expects a configuration on return.
* @param {WebpackConfigurationParams} params A dictionary generated by the top service building
* the configuration and that includes things like the
* target information, its entry settings, output
* paths, etc.
* @return {object}
*/
createConfig(params) {
const {
definitions,
copy,
entry,
target,
output,
additionalWatch,
analyze,
} = params;
// Define the basic stuff: entry, output and mode.
const config = {
entry,
output: {
path: `./${target.folders.build}`,
filename: output.js,
chunkFilename: output.jsChunks,
publicPath: '/',
},
mode: 'production',
};
// If the target has source maps enabled...
if (target.sourceMap.production) {
config.devtool = 'source-map';
}
// Enable or not uglification for the target bundle.
if (target.uglifyOnProduction) {
config.optimization = {
minimizer: [
new TerserPlugin({
sourceMap: !!target.sourceMap.production,
}),
],
};
} else {
config.optimization = {
minimize: false,
};
}
// Setup the plugins.
config.plugins = [
// If the target is a library, it doesn't need HTML on production.
...(
target.library ?
[] :
[
// To automatically inject the `script` tag on the target `html` file.
new HtmlWebpackPlugin(Object.assign({}, target.html, {
template: this.targetsHTML.getFilepath(target),
inject: 'body',
})),
// To add the `async` attribute to the `script` tag.
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'async',
}),
]
),
// To add the _'browser env variables'_.
new ProjextWebpackRuntimeDefinitions(
Object.keys(entry).reduce(
(current, key) => [...current, ...entry[key].filter((file) => path.isAbsolute(file))],
[]
),
definitions
),
// To optimize the SCSS and remove repeated declarations.
new OptimizeCssAssetsPlugin(),
// To compress the emitted assets using gzip, if the target is not a library.
...(!target.library || target.libraryOptions.compress ? [new CompressionPlugin()] : []),
// Copy the files the target specified on its settings.
new CopyWebpackPlugin(copy),
/**
* If the target doesn't inject the styles on runtime, add the plugin to push them all on
* a single file.
*/
...(
target.css.inject ?
[] :
[new MiniCssExtractPlugin({
filename: output.css,
})]
),
// If there are additionals files to watch, add the plugin for it.
...(
additionalWatch.length ?
[new ExtraWatchWebpackPlugin({ files: additionalWatch })] :
[]
),
// If the the bundle should be analyzed, add the plugin for it.
...(
analyze ?
[new BundleAnalyzerPlugin()] :
[]
),
];
// Enable the watch mode if required...
if (target.watch.production) {
config.watch = true;
}
// Reduce the configuration
return this.events.reduce(
[
'webpack-browser-production-configuration',
'webpack-browser-configuration',
],
config,
params
);
}
}
/**
* The service provider that once registered on the app container will set an instance of
* `WebpackBrowserProductionConfiguration` as the `webpackBrowserProductionConfiguration` service.
* @example
* // Register it on the container
* container.register(webpackBrowserProductionConfiguration);
* // Getting access to the service instance
* const webpackBrowserProdConfig = container.get('webpackBrowserProductionConfiguration');
* @type {Provider}
*/
const webpackBrowserProductionConfiguration = provider((app) => {
app.set(
'webpackBrowserProductionConfiguration',
() => new WebpackBrowserProductionConfiguration(
app.get('events'),
app.get('pathUtils'),
app.get('targetsHTML'),
app.get('webpackBaseConfiguration')
)
);
});
module.exports = {
WebpackBrowserProductionConfiguration,
webpackBrowserProductionConfiguration,
};