webpack-atoms
Version:
Small atomic bits for crafting webpack configs
607 lines (534 loc) • 17.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createAtoms = createAtoms;
exports.stats = exports.plugins = exports.rules = exports.loaders = exports.makeExtractLoaders = exports.makeInternalOnly = exports.makeExternalOnly = void 0;
var _path = _interopRequireDefault(require("path"));
var _autoprefixer = _interopRequireDefault(require("autoprefixer"));
var _browserslist = require("browserslist");
var _copyWebpackPlugin = _interopRequireDefault(require("copy-webpack-plugin"));
var _faviconsWebpackPlugin = _interopRequireDefault(require("favicons-webpack-plugin"));
var _htmlWebpackPlugin = _interopRequireDefault(require("html-webpack-plugin"));
var _miniCssExtractPlugin = _interopRequireDefault(require("mini-css-extract-plugin"));
var _cssMinimizerWebpackPlugin = _interopRequireDefault(require("css-minimizer-webpack-plugin"));
var _terserWebpackPlugin = _interopRequireDefault(require("terser-webpack-plugin"));
var _webpack = _interopRequireDefault(require("webpack"));
var _unusedFilesWebpackPlugin = _interopRequireDefault(require("@4c/unused-files-webpack-plugin"));
var _plugins = _interopRequireDefault(require("./plugins"));
var _stats = _interopRequireDefault(require("./stats"));
const _excluded = ["disable", "fallback"],
_excluded2 = ["postcssOptions", "browsers"],
_excluded3 = ["browsers", "extract"],
_excluded4 = ["modules"],
_excluded5 = ["modules", "extract"],
_excluded6 = ["modules"],
_excluded7 = ["modules", "browsers", "extract"],
_excluded8 = ["modules"],
_excluded9 = ["browsers", "modules", "extract"],
_excluded10 = ["modules"],
_excluded11 = ["terserOptions"];
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
const VENDOR_MODULE_REGEX = /node_modules/;
const DEFAULT_BROWSERS = ['> 1%', 'Firefox ESR', 'not ie < 9'];
function createAtoms(options = {}) {
let {
babelConfig = {},
assetRelativeRoot = '',
env = process.env.NODE_ENV,
vendorRegex = VENDOR_MODULE_REGEX,
disableMiniExtractInDev = true,
ignoreBrowserslistConfig = false,
browsers: supportedBrowsers
} = options;
const hasBrowsersListConfig = !!(0, _browserslist.loadConfig)({
path: _path.default.resolve('.')
});
if (ignoreBrowserslistConfig || !hasBrowsersListConfig) {
supportedBrowsers = supportedBrowsers || DEFAULT_BROWSERS;
}
const makeExternalOnly = original => (options = {}) => {
const rule = original(options);
rule.include = vendorRegex;
return rule;
};
const makeInternalOnly = original => (options = {}) => {
const rule = original(options);
rule.exclude = vendorRegex;
return rule;
};
const makeContextual = rule => {
return Object.assign(rule, {
external: makeExternalOnly(rule),
internal: makeInternalOnly(rule)
});
};
const makeExtractLoaders = ({
extract
} = {}, config) => [// eslint-disable-next-line @typescript-eslint/no-use-before-define
loaders.miniCssExtract({
fallback: config.fallback,
disable: extract == undefined ? extract : !extract
}), ...config.use];
const PRODUCTION = env === 'production';
/**
* Loaders
*/
const loaders = {
json: () => ({
loader: require.resolve('json-loader')
}),
yaml: () => ({
loader: require.resolve('yaml-loader')
}),
null: () => ({
loader: require.resolve('null-loader')
}),
raw: () => ({
loader: require.resolve('raw-loader')
}),
style: () => ({
loader: require.resolve('style-loader')
}),
miniCssExtract: (opts = {}) => {
const _ref = opts,
{
disable = !PRODUCTION && disableMiniExtractInDev,
fallback
} = _ref,
options = _objectWithoutProperties(_ref, _excluded);
return disable ? fallback || loaders.style() : {
loader: _miniCssExtractPlugin.default.loader,
options
};
},
css: (options = {}) => ({
loader: require.resolve('css-loader'),
options: _objectSpread(_objectSpread({
sourceMap: !PRODUCTION
}, options), {}, {
modules: options.modules ? _objectSpread({
// https://github.com/webpack-contrib/css-loader/issues/406
localIdentName: '[name]--[local]--[hash:base64:5]',
exportLocalsConvention: 'dashes'
}, options.modules) : false
})
}),
astroturf: options => ({
options: _objectSpread({
extension: '.module.css'
}, options),
loader: require.resolve('astroturf/loader')
}),
postcss: (options = {}) => {
const {
postcssOptions,
browsers = supportedBrowsers
} = options,
rest = _objectWithoutProperties(options, _excluded2);
const loader = require.resolve('postcss-loader');
return {
loader,
options: _objectSpread(_objectSpread({}, rest), {}, {
postcssOptions: (...args) => {
var _postcssOpts$plugins;
const postcssOpts = typeof postcssOptions === `function` ? postcssOptions(...args) : postcssOptions;
const plugins = (_postcssOpts$plugins = postcssOpts === null || postcssOpts === void 0 ? void 0 : postcssOpts.plugins) !== null && _postcssOpts$plugins !== void 0 ? _postcssOpts$plugins : [];
return _objectSpread(_objectSpread({}, postcssOpts), {}, {
plugins: [...plugins, // overrideBrowserslist is only set when browsers is explicit
(0, _autoprefixer.default)({
overrideBrowserslist: browsers,
flexbox: `no-2009`
})]
});
}
})
};
},
less: (options = {}) => ({
options,
loader: require.resolve('less-loader')
}),
sass: (options = {}) => ({
options,
loader: require.resolve('sass-loader')
}),
file: (options = {}) => ({
loader: require.resolve('file-loader'),
options: _objectSpread({
name: `${assetRelativeRoot}[name]-[hash].[ext]`
}, options)
}),
url: (options = {}) => ({
loader: require.resolve('url-loader'),
options: _objectSpread({
limit: 10000,
name: `${assetRelativeRoot}[name]-[hash].[ext]`
}, options)
}),
js: (options = babelConfig) => ({
options,
loader: require.resolve('babel-loader')
}),
imports: (options = {}) => ({
options,
loader: require.resolve('imports-loader')
}),
exports: (options = {}) => ({
options,
loader: require.resolve('exports-loader')
})
};
/**
* Rules
*/
const rules = {};
/**
* Javascript loader via babel, excludes node_modules
*/
{
const js = (options = {}) => ({
test: /\.(j|t)sx?$/,
exclude: vendorRegex,
use: [loaders.js(options)]
});
rules.js = js;
}
rules.yaml = () => ({
test: /\.ya?ml/,
use: [loaders.json(), loaders.yaml()]
});
/**
* Font loader
*/
rules.fonts = () => ({
type: 'asset',
test: /\.(eot|otf|ttf|woff(2)?)(\?.*)?$/,
parser: {
dataUrlCondition: {
maxSize: 10000
}
},
generator: {
filename: `${assetRelativeRoot}[name]-[hash].[ext]`
}
});
/**
* Loads image assets, inlines images via a data URI if they are below
* the size threshold
*/
rules.images = () => ({
type: 'asset',
test: /\.(ico|svg|jpg|jpeg|png|gif|webp)(\?.*)?$/,
parser: {
dataUrlCondition: {
maxSize: 10000
}
},
generator: {
filename: `${assetRelativeRoot}[name]-[hash].[ext]`
}
});
/**
* Loads audio or video assets
*/
rules.audioVideo = () => ({
type: 'asset/resource',
test: /\.(mp4|webm|wav|mp3|m4a|aac|oga|flac)$/,
generator: {
filename: `${assetRelativeRoot}[name]-[hash].[ext]`
}
});
/**
* A catch-all rule for everything that isn't js, json, or html.
* Should only be used in the context of a webpack `oneOf` rule as a fallback
* (see rules.assets())
*/
rules.files = () => ({
// Exclude `js` files to keep "css" loader working as it injects
// it's runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.jsx?$/, /\.html$/, /\.json$/],
type: 'asset/resource',
generator: {
filename: `${assetRelativeRoot}[name]-[hash].[ext]`
}
});
/**
* Astroturf loader.
*/
{
const astroturf = (options = {}) => ({
test: /\.(j|t)sx?$/,
use: [loaders.astroturf(options)]
});
Object.assign(astroturf, {
sass: opts => astroturf(_objectSpread({
extension: '.module.scss'
}, opts)),
less: opts => astroturf(_objectSpread({
extension: '.module.less'
}, opts))
});
rules.astroturf = astroturf;
}
/**
* CSS style loader.
*/
{
const css = (_ref2 = {}) => {
let {
browsers,
extract
} = _ref2,
options = _objectWithoutProperties(_ref2, _excluded3);
return {
test: /\.css$/,
use: makeExtractLoaders({
extract
}, {
fallback: loaders.style(),
use: [loaders.css(_objectSpread(_objectSpread({}, options), {}, {
importLoaders: 1
})), loaders.postcss({
browsers
})]
})
};
};
rules.css = makeContextual((_ref3 = {}) => {
let {
modules = true
} = _ref3,
opts = _objectWithoutProperties(_ref3, _excluded4);
return {
oneOf: [_objectSpread(_objectSpread({}, css(_objectSpread(_objectSpread({}, opts), {}, {
modules
}))), {}, {
test: /\.module\.css$/
}), css(opts)]
};
});
}
/**
* PostCSS loader.
*/
{
const postcss = (_ref4 = {}) => {
let {
modules,
extract
} = _ref4,
opts = _objectWithoutProperties(_ref4, _excluded5);
return {
test: /\.css$/,
use: makeExtractLoaders({
extract
}, {
fallback: loaders.style(),
use: [loaders.css({
importLoaders: 1,
modules
}), loaders.postcss(opts)]
})
};
};
rules.postcss = makeContextual((_ref5 = {}) => {
let {
modules = true
} = _ref5,
opts = _objectWithoutProperties(_ref5, _excluded6);
return {
oneOf: [_objectSpread(_objectSpread({}, postcss(_objectSpread(_objectSpread({}, opts), {}, {
modules
}))), {}, {
test: /\.module\.css$/
}), postcss(opts)]
};
});
}
/**
* Less style loader.
*/
{
const less = (_ref6 = {}) => {
let {
modules,
browsers,
extract
} = _ref6,
options = _objectWithoutProperties(_ref6, _excluded7);
return {
test: /\.less$/,
use: makeExtractLoaders({
extract
}, {
fallback: loaders.style(),
use: [loaders.css({
importLoaders: 2,
modules
}), loaders.postcss({
browsers
}), loaders.less(options)]
})
};
};
rules.less = makeContextual((_ref7 = {}) => {
let {
modules = true
} = _ref7,
opts = _objectWithoutProperties(_ref7, _excluded8);
return {
oneOf: [_objectSpread(_objectSpread({}, less(_objectSpread(_objectSpread({}, opts), {}, {
modules
}))), {}, {
test: /\.module\.less$/
}), less(opts)]
};
});
}
/**
* SASS style loader, excludes node_modules.
*/
{
const sass = (_ref8 = {}) => {
let {
browsers,
modules,
extract
} = _ref8,
options = _objectWithoutProperties(_ref8, _excluded9);
return {
test: /\.s(a|c)ss$/,
use: makeExtractLoaders({
extract
}, {
fallback: loaders.style(),
use: [loaders.css({
importLoaders: 2,
modules
}), loaders.postcss({
browsers
}), loaders.sass(options)]
})
};
};
rules.sass = makeContextual((_ref9 = {}) => {
let {
modules = true
} = _ref9,
opts = _objectWithoutProperties(_ref9, _excluded10);
return {
oneOf: [_objectSpread(_objectSpread({}, sass(_objectSpread(_objectSpread({}, opts), {}, {
modules
}))), {}, {
test: /\.module\.s(a|c)ss$/
}), sass(opts)]
};
});
}
/**
* Plugins
*/
const plugins = _objectSpread(_objectSpread({}, _plugins.default), {}, {
/**
* https://webpack.js.org/plugins/define-plugin/
*
* Replace tokens in code with static values. Defaults to setting NODE_ENV
* which is used by React and other libraries to toggle development mode.
*/
define: (defines = {}) => new _webpack.default.DefinePlugin(_objectSpread({
// eslint-disable-next-line @typescript-eslint/naming-convention
'process.env.NODE_ENV': JSON.stringify(env)
}, defines)),
/**
* Minify javascript code without regard for IE8. Attempts
* to parallelize the work to save time. Generally only add in Production
*/
/**
* Minify javascript code without regard for IE8. Attempts
* to parallelize the work to save time. Generally only add in Production
*/
minifyJs: (_ref10 = {}) => {
let {
terserOptions
} = _ref10,
options = _objectWithoutProperties(_ref10, _excluded11);
return new _terserWebpackPlugin.default(_objectSpread({
parallel: true,
exclude: /\.min\.js/,
terserOptions: _objectSpread({
ecma: 8,
ie8: false
}, terserOptions)
}, options));
},
/**
* Extracts css requires into a single file;
* includes some reasonable defaults
*/
extractCss: options => new _miniCssExtractPlugin.default(_objectSpread({
filename: '[name]-[contenthash].css'
}, options)),
minifyCss: (options = {}) => new _cssMinimizerWebpackPlugin.default(options),
/**
* Generates an html file that includes the output bundles.
* Sepecify a `title` option to set the page title.
*/
html: options => new _htmlWebpackPlugin.default(_objectSpread({
inject: true,
template: _path.default.join(__dirname, '../assets/index.html')
}, options)),
moment: () => new _webpack.default.IgnorePlugin({
contextRegExp: /^\.\/locale$/,
resourceRegExp: /moment$/
}),
copy: (...args) => new _copyWebpackPlugin.default(...args),
unusedFiles: (...args) => new _unusedFilesWebpackPlugin.default(...args),
favicons: args => new _faviconsWebpackPlugin.default(args)
});
const stats = {
none: _stats.default,
minimal: _objectSpread(_objectSpread({}, _stats.default), {}, {
errors: true,
errorDetails: true,
assets: true,
chunks: true,
colors: true,
performance: true,
timings: true,
warnings: true
})
};
return {
loaders,
rules: rules,
plugins: plugins,
stats,
makeExternalOnly,
makeInternalOnly,
makeExtractLoaders
};
}
const {
makeExternalOnly,
makeInternalOnly,
makeExtractLoaders,
stats,
loaders,
rules,
plugins
} = createAtoms();
exports.plugins = plugins;
exports.rules = rules;
exports.loaders = loaders;
exports.stats = stats;
exports.makeExtractLoaders = makeExtractLoaders;
exports.makeInternalOnly = makeInternalOnly;
exports.makeExternalOnly = makeExternalOnly;