UNPKG

nwb

Version:

A toolkit for React, Preact & Inferno apps, React libraries and other npm modules for the web, with no configuration (until you need it)

358 lines (294 loc) 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processWebpackConfig = processWebpackConfig; exports.prepareWebpackRuleConfig = prepareWebpackRuleConfig; exports.prepareWebpackStyleConfig = prepareWebpackStyleConfig; var _chalk = _interopRequireDefault(require("chalk")); var _constants = require("../constants"); var _createWebpackConfig = require("../createWebpackConfig"); var _utils = require("../utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const DEFAULT_STYLE_LOADERS = new Set(['css', 'postcss']); let warnedAboutAutoPrefixerString = false; function processWebpackConfig({ pluginConfig, report, userConfig }) { let { aliases, autoprefixer, compat, copy, debug, define, extractCSS, html, install, publicPath, rules, styles, terser, extra, config, ...unexpectedConfig } = userConfig.webpack; let unexpectedProps = Object.keys(unexpectedConfig); if (unexpectedProps.length > 0) { report.error('webpack', unexpectedProps.join(', '), `Unexpected prop${(0, _utils.pluralise)(unexpectedProps.length)} in ${_chalk.default.cyan('webpack')} config - ` + 'see https://github.com/insin/nwb/blob/master/docs/Configuration.md#webpack-configuration for supported config. ' + `If you were trying to add extra Webpack config, try putting it in ${_chalk.default.cyan('webpack.extra')} instead`); } // aliases if ('aliases' in userConfig.webpack) { if ((0, _utils.typeOf)(aliases) !== 'object') { report.error('webpack.aliases', `type: ${(0, _utils.typeOf)(aliases)}`, `Must be an ${_chalk.default.cyan('Object')}`); } else { checkForRedundantCompatAliases(userConfig.type, aliases, 'webpack.aliases', report); } } // autoprefixer if ('autoprefixer' in userConfig.webpack) { // TODO Deprecated - remove if ((0, _utils.typeOf)(autoprefixer) === 'string') { if (!warnedAboutAutoPrefixerString) { report.deprecated('webpack.autoprefixer as a String', 'Replaced by top-level "browsers" config in nwb v0.25.0 - webpack.autoprefixer can no longer be a String'); warnedAboutAutoPrefixerString = true; } userConfig.webpack.autoprefixer = {}; } else if ((0, _utils.typeOf)(autoprefixer) !== 'object') { report.error('webpack.autoprefixer', `type: ${(0, _utils.typeOf)(autoprefixer)}`, `Must be an ${_chalk.default.cyan('Object')}`); } } // compat if ('compat' in userConfig.webpack) { if ((0, _utils.typeOf)(compat) !== 'object') { report.error('webpack.compat', `type: ${(0, _utils.typeOf)(compat)}`, `Must be an ${_chalk.default.cyan('Object')}`); } else { // Validate compat props let compatProps = Object.keys(compat); let unexpectedCompatProps = compatProps.filter(prop => !(prop in _createWebpackConfig.COMPAT_CONFIGS)); if (unexpectedCompatProps.length > 0) { report.error('webpack.compat', unexpectedCompatProps.join(', '), `Unexpected prop${(0, _utils.pluralise)(unexpectedCompatProps.length)} in ${_chalk.default.cyan('webpack.compat')}. ` + `Valid props are: ${(0, _utils.joinAnd)(Object.keys(_createWebpackConfig.COMPAT_CONFIGS).map(p => _chalk.default.cyan(p)), 'or')}`); } void ['intl', 'moment', 'react-intl'].forEach(compatProp => { if (!(compatProp in compat)) return; let config = compat[compatProp]; let configType = (0, _utils.typeOf)(config); if (configType === 'string') { compat[compatProp] = { locales: [config] }; } else if (configType === 'array') { compat[compatProp] = { locales: config }; } else if (configType === 'object') { if ((0, _utils.typeOf)(config.locales) === 'string') { config.locales = [config.locales]; } else if ((0, _utils.typeOf)(config.locales) !== 'array') { report.error(`webpack.compat.${compatProp}.locales`, config.locales, `Must be a ${_chalk.default.cyan('String')} (single locale name) or an ${_chalk.default.cyan('Array')} of locales`); } } else { report.error(`webpack.compat.${compatProp}`, `type: ${configType}`, `Must be a ${_chalk.default.cyan('String')} (single locale name), an ${_chalk.default.cyan('Array')} ` + `of locales or an ${_chalk.default.cyan('Object')} with a ${_chalk.default.cyan('locales')} property - ` + 'see https://github.com/insin/nwb/blob/master/docs/Configuration.md#compat-object '); } }); } } // copy if ('copy' in userConfig.webpack) { if ((0, _utils.typeOf)(copy) === 'array') { userConfig.webpack.copy = { patterns: copy }; } else if ((0, _utils.typeOf)(copy) === 'object') { if (!copy.patterns && !copy.options) { report.error('webpack.copy', copy, `Must include ${_chalk.default.cyan('patterns')} or ${_chalk.default.cyan('options')}`); } if (copy.patterns && (0, _utils.typeOf)(copy.patterns) !== 'array') { report.error('webpack.copy.patterns', copy.patterns, `Must be an ${_chalk.default.cyan('Array')}`); } if (copy.options && (0, _utils.typeOf)(copy.options) !== 'object') { report.error('webpack.copy.options', copy.options, `Must be an ${_chalk.default.cyan('Object')}`); } } else { report.error('webpack.copy', copy, `Must be an ${_chalk.default.cyan('Array')} or an ${_chalk.default.cyan('Object')}`); } } // debug if (debug) { // Make it harder for the user to forget to disable the production debug build // if they've enabled it in the config file. report.hint('webpack.debug', "Don't forget to disable the debug build before building for production"); } // define if ('define' in userConfig.webpack) { if ((0, _utils.typeOf)(define) !== 'object') { report.error('webpack.define', `type: ${(0, _utils.typeOf)(define)}`, `Must be an ${_chalk.default.cyan('Object')}`); } } // extractCSS if ('extractCSS' in userConfig.webpack) { let configType = (0, _utils.typeOf)(extractCSS); let help = `Must be ${_chalk.default.cyan('false')} (to disable CSS extraction) or ` + `an ${_chalk.default.cyan('Object')} (to configure MiniCssExtractPlugin)`; if (configType === 'boolean') { if (extractCSS !== false) { report.error('webpack.extractCSS', extractCSS, help); } } else if (configType !== 'object') { report.error('webpack.extractCSS', `type: ${configType}`, help); } } // html if ('html' in userConfig.webpack) { if ((0, _utils.typeOf)(html) !== 'object') { report.error('webpack.html', `type: ${(0, _utils.typeOf)(html)}`, `Must be an ${_chalk.default.cyan('Object')}`); } } // install if ('install' in userConfig.webpack) { if ((0, _utils.typeOf)(install) !== 'object') { report.error('webpack.install', `type: ${(0, _utils.typeOf)(install)}`, `Must be an ${_chalk.default.cyan('Object')}`); } } // publicPath if ('publicPath' in userConfig.webpack) { if ((0, _utils.typeOf)(publicPath) !== 'string') { report.error('webpack.publicPath', `type: ${(0, _utils.typeOf)(publicPath)}`, `Must be a ${_chalk.default.cyan('String')}`); } } // rules if ('rules' in userConfig.webpack) { if ((0, _utils.typeOf)(rules) !== 'object') { report.error('webpack.rules', `type: ${(0, _utils.typeOf)(rules)}`, `Must be an ${_chalk.default.cyan('Object')}`); } else { let error = false; Object.keys(rules).forEach(ruleId => { let rule = rules[ruleId]; if (rule.use && (0, _utils.typeOf)(rule.use) !== 'array') { report.error(`webpack.rules.${ruleId}.use`, `type: ${(0, _utils.typeOf)(rule.use)}`, `Must be an ${_chalk.default.cyan('Array')}`); error = true; } }); if (!error) { prepareWebpackRuleConfig(rules); } } } // styles if ('styles' in userConfig.webpack) { let configType = (0, _utils.typeOf)(styles); let help = `Must be an ${_chalk.default.cyan('Object')} (to configure custom style rules) ` + `or ${_chalk.default.cyan('false')} (to disable style rules)`; if (configType === 'boolean' && styles !== false) { report.error('webpack.styles', styles, help); } else if (configType !== 'object' && configType !== 'boolean') { report.error('webpack.styles', `type: ${configType}`, help); } else { let styleTypeIds = ['css']; if (pluginConfig.cssPreprocessors) { styleTypeIds = styleTypeIds.concat(Object.keys(pluginConfig.cssPreprocessors)); } let error = false; Object.keys(styles).forEach(styleType => { if (styleTypeIds.indexOf(styleType) === -1) { report.error('webpack.styles', `property: ${styleType}`, `Unknown style type - must be ${(0, _utils.joinAnd)(styleTypeIds.map(s => _chalk.default.cyan(s)), 'or')}`); error = true; } else if ((0, _utils.typeOf)(styles[styleType]) !== 'array') { report.error(`webpack.styles.${styleType}`, `type: ${(0, _utils.typeOf)(styles[styleType])}`, `Must be an ${_chalk.default.cyan('Array')} - if you don't need multiple custom rules, ` + `configure the defaults via ${_chalk.default.cyan('webpack.rules')} instead`); error = true; } else { styles[styleType].forEach((styleConfig, index) => { let { test, include, exclude, // eslint-disable-line no-unused-vars ...loaderConfig } = styleConfig; Object.keys(loaderConfig).forEach(loaderId => { if (!DEFAULT_STYLE_LOADERS.has(loaderId) && loaderId !== styleType) { // XXX Assumption: preprocessors provide a single loader which is configured with the same id as the style type id let loaderIds = Array.from(new Set([...Array.from(DEFAULT_STYLE_LOADERS), styleType])).map(id => _chalk.default.cyan(id)); report.error(`webpack.styles.${styleType}[${index}]`, `property: ${loaderId}`, `Must be ${_chalk.default.cyan('include')}, ${_chalk.default.cyan('exclude')} or a loader id: ${(0, _utils.joinAnd)(loaderIds, 'or')}`); error = true; } }); }); } }); if (!error) { prepareWebpackStyleConfig(styles); } } } // terser if ('terser' in userConfig.webpack) { if (terser !== false && (0, _utils.typeOf)(terser) !== 'object') { report.error(`webpack.terser`, terser, `Must be ${_chalk.default.cyan('false')} (to disable terser-webpack-plugin) or ` + `an ${_chalk.default.cyan('Object')} (to configure terser-webpack-plugin)`); } } // extra if ('extra' in userConfig.webpack) { if ((0, _utils.typeOf)(extra) !== 'object') { report.error('webpack.extra', `type: ${(0, _utils.typeOf)(extra)}`, `Must be an ${_chalk.default.cyan('Object')}`); } else { if ((0, _utils.typeOf)(extra.output) === 'object' && extra.output.publicPath) { report.hint('webpack.extra.output.publicPath', `You can use the more convenient ${_chalk.default.cyan('webpack.publicPath')} config instead`); } if ((0, _utils.typeOf)(extra.resolve) === 'object' && extra.resolve.alias) { report.hint('webpack.extra.resolve.alias', `You can use the more convenient ${_chalk.default.cyan('webpack.aliases')} config instead`); checkForRedundantCompatAliases(userConfig.type, extra.resolve.alias, 'webpack.extra.resolve.alias', report); } } } // config if ('config' in userConfig.webpack && (0, _utils.typeOf)(config) !== 'function') { report.error(`webpack.config`, `type: ${(0, _utils.typeOf)(config)}`, `Must be a ${_chalk.default.cyan('Function')}`); } } /** * Tell the user if they've manually set up the same React compatibility aliases * nwb configured by default. */ function checkForRedundantCompatAliases(projectType, aliases, configPath, report) { if (!new Set([_constants.INFERNO_APP, _constants.PREACT_APP]).has(projectType)) return; let compatModule = `${projectType.split('-')[0]}-compat`; if (aliases.react && aliases.react.includes(compatModule)) { report.hint(`${configPath}.react`, `nwb aliases ${_chalk.default.yellow('react')} to ${_chalk.default.cyan(compatModule)} by default, so you can remove this config`); } if (aliases['react-dom'] && aliases['react-dom'].includes(compatModule)) { report.hint(`${configPath}.react-dom`, `nwb aliases ${_chalk.default.yellow('react-dom')} to ${_chalk.default.cyan(compatModule)} by default, so you can remove this config`); } } /** * Move loader options into an options object, allowing users to provide flatter * config. */ function prepareWebpackRuleConfig(rules) { Object.keys(rules).forEach(ruleId => { let rule = rules[ruleId]; // XXX Special case for stylus-loader, which uses a 'use' option for plugins if (rule.use && !/stylus$/.test(ruleId) || rule.options) return; let { exclude, include, test, loader, // eslint-disable-line no-unused-vars ...options } = rule; if (Object.keys(options).length > 0) { rule.options = options; Object.keys(options).forEach(prop => delete rule[prop]); } }); } /** * Move loader options into a loaders object, allowing users to provide flatter * config. */ function prepareWebpackStyleConfig(styles) { Object.keys(styles).forEach(type => { styles[type].forEach(styleConfig => { let { exclude, include, // eslint-disable-line no-unused-vars ...loaderConfig } = styleConfig; if (Object.keys(loaderConfig).length > 0) { styleConfig.loaders = {}; Object.keys(loaderConfig).forEach(loader => { styleConfig.loaders[loader] = { options: styleConfig[loader] }; delete styleConfig[loader]; }); } }); }); }