From 704963d1482ebbd13e09e58bc12f8170f84dded5 Mon Sep 17 00:00:00 2001 From: Andre Wiggins <459878+andrewiggins@users.noreply.github.com> Date: Mon, 10 Aug 2020 03:04:51 -0700 Subject: [PATCH] Refactor webpack handling (#58) --- package.json | 4 +- src/configure.js | 174 +++++----------------------------------------- src/webpack.js | 176 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 160 deletions(-) create mode 100644 src/webpack.js diff --git a/package.json b/package.json index 8d45c1e..2d74da1 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "errorstacks": "^1.3.0", "expect": "^24.9.0", "jasmine-core": "^3.5.0", - "karma": "^5.1.0", + "karma": "^5.1.1", "karma-chrome-launcher": "^3.1.0", - "karma-coverage": "^2.0.2", + "karma-coverage": "^2.0.3", "karma-firefox-launcher": "^1.3.0", "karma-jasmine": "^3.3.1", "karma-min-reporter": "^0.1.0", diff --git a/src/configure.js b/src/configure.js index dcaa802..aa874ff 100644 --- a/src/configure.js +++ b/src/configure.js @@ -1,25 +1,22 @@ import path from 'path'; import puppeteer from 'puppeteer'; import chalk from 'chalk'; -import delve from 'dlv'; -import { tryRequire, dedupe, cleanStack, readFile, readDir } from './lib/util'; -import babelLoader from './lib/babel-loader'; -import cssLoader from './lib/css-loader'; +import { tryRequire, cleanStack, readFile, readDir } from './lib/util'; +import { shouldUseWebpack, addWebpackConfig } from './webpack'; // import minimatch from 'minimatch'; -const WEBPACK_VERSION = String(require('webpack').version || '3.0.0'); -const WEBPACK_MAJOR = parseInt(WEBPACK_VERSION.split('.')[0], 10); - /** - * @param {Object} options - * @param {Array} options.files - Test files to run - * @param {Array} [options.browsers] - Custom list of browsers to run in - * @param {Boolean} [options.headless=false] - Run in Headless Chrome? - * @param {Boolean} [options.watch=false] - Start a continuous test server and retest when files change - * @param {Boolean} [options.coverage=false] - Instrument and collect code coverage statistics - * @param {Object} [options.webpackConfig] - Custom webpack configuration - * @param {Boolean} [options.downlevel=false] - Downlevel/transpile syntax to ES5 - * @param {string} [options.chromeDataDir] - Use a custom Chrome profile directory + * @typedef Options + * @property {Array} files - Test files to run + * @property {Array} [browsers] - Custom list of browsers to run in + * @property {Boolean} [headless=false] - Run in Headless Chrome? + * @property {Boolean} [watch=false] - Start a continuous test server and retest when files change + * @property {Boolean} [coverage=false] - Instrument and collect code coverage statistics + * @property {Object} [webpackConfig] - Custom webpack configuration + * @property {Boolean} [downlevel=false] - Downlevel/transpile syntax to ES5 + * @property {string} [chromeDataDir] - Use a custom Chrome profile directory + * + * @param {Options} options */ export default function configure(options) { let cwd = process.cwd(), @@ -45,10 +42,9 @@ export default function configure(options) { 'karma-spec-reporter', 'karma-min-reporter', 'karma-sourcemap-loader', - 'karma-webpack', ].concat(options.coverage ? 'karma-coverage' : []); - const preprocessors = ['webpack', 'sourcemap']; + const preprocessors = ['sourcemap']; // Custom launchers to be injected: const launchers = {}; @@ -115,88 +111,8 @@ export default function configure(options) { } } - const WEBPACK_CONFIGS = ['webpack.config.babel.js', 'webpack.config.js']; - - let webpackConfig = options.webpackConfig; - let pkg = tryRequire(res('package.json')); - if (pkg.scripts) { - for (let i in pkg.scripts) { - let script = pkg.scripts[i]; - if (/\bwebpack\b[^&|]*(-c|--config)\b/.test(script)) { - let matches = script.match( - /(?:-c|--config)\s+(?:([^\s])|(["'])(.*?)\2)/ - ); - let configFile = matches && (matches[1] || matches[2]); - if (configFile) WEBPACK_CONFIGS.push(configFile); - } - } - } - - if (!webpackConfig) { - for (let i = WEBPACK_CONFIGS.length; i--; ) { - webpackConfig = tryRequire(res(WEBPACK_CONFIGS[i])); - if (webpackConfig) break; - } - } - - if (typeof webpackConfig === 'function') { - webpackConfig = webpackConfig( - { karmatic: true }, - { mode: 'development', karmatic: true } - ); - } - webpackConfig = webpackConfig || {}; - - let loaders = [].concat( - delve(webpackConfig, 'module.loaders') || [], - delve(webpackConfig, 'module.rules') || [] - ); - - function evaluateCondition(condition, filename, expected) { - if (typeof condition === 'function') { - return condition(filename) == expected; - } else if (condition instanceof RegExp) { - return condition.test(filename) == expected; - } - if (Array.isArray(condition)) { - for (let i = 0; i < condition.length; i++) { - if (evaluateCondition(condition[i], filename)) return expected; - } - } - return !expected; - } - - function getLoader(predicate) { - if (typeof predicate === 'string') { - let filename = predicate; - predicate = (loader) => { - let { test, include, exclude } = loader; - if (exclude && evaluateCondition(exclude, filename, false)) - return false; - if (include && !evaluateCondition(include, filename, true)) - return false; - if (test && evaluateCondition(test, filename, true)) return true; - return false; - }; - } - for (let i = 0; i < loaders.length; i++) { - if (predicate(loaders[i])) { - return { index: i, loader: loaders[i] }; - } - } - return false; - } - - function webpackProp(name, value) { - let configured = delve(webpackConfig, name); - if (Array.isArray(value)) { - return value.concat(configured || []).filter(dedupe); - } - return Object.assign({}, configured || {}, value); - } - const chromeDataDir = options.chromeDataDir ? path.resolve(cwd, options.chromeDataDir) : null; @@ -289,61 +205,6 @@ export default function configure(options) { [rootFiles]: preprocessors, }, - webpack: { - devtool: 'inline-source-map', - // devtool: 'module-source-map', - mode: webpackConfig.mode || 'development', - module: { - // @TODO check webpack version and use loaders VS rules as the key here appropriately: - rules: loaders - .concat( - !getLoader((rule) => - `${rule.use},${rule.loader}`.match(/\bbabel-loader\b/) - ) - ? babelLoader(options) - : false, - !getLoader('foo.css') && cssLoader(options) - ) - .filter(Boolean), - }, - resolve: webpackProp('resolve', { - modules: webpackProp('resolve.modules', [ - 'node_modules', - path.resolve(__dirname, '../node_modules'), - ]), - alias: webpackProp('resolve.alias', { - [pkg.name]: res('.'), - src: res('src'), - }), - }), - resolveLoader: webpackProp('resolveLoader', { - modules: webpackProp('resolveLoader.modules', [ - 'node_modules', - path.resolve(__dirname, '../node_modules'), - ]), - alias: webpackProp('resolveLoader.alias', { - [pkg.name]: res('.'), - src: res('src'), - }), - }), - plugins: (webpackConfig.plugins || []).filter((plugin) => { - let name = plugin && plugin.constructor.name; - return /^\s*(UglifyJS|HTML|ExtractText|BabelMinify)(.*Webpack)?Plugin\s*$/gi.test( - name - ); - }), - node: webpackProp('node', {}), - performance: { - hints: false, - }, - }, - - webpackMiddleware: { - noInfo: true, - logLevel: 'error', - stats: 'errors-only', - }, - colors: true, client: { @@ -355,11 +216,8 @@ export default function configure(options) { }, }; - if (WEBPACK_MAJOR < 4) { - delete generatedConfig.webpack.mode; - let { rules } = generatedConfig.webpack.module; - delete generatedConfig.webpack.module.rules; - generatedConfig.webpack.module.loaders = rules; + if (shouldUseWebpack(options)) { + addWebpackConfig(generatedConfig, pkg, options); } return generatedConfig; diff --git a/src/webpack.js b/src/webpack.js new file mode 100644 index 0000000..a6db999 --- /dev/null +++ b/src/webpack.js @@ -0,0 +1,176 @@ +import path from 'path'; +import delve from 'dlv'; +import { tryRequire, dedupe } from './lib/util'; +import babelLoader from './lib/babel-loader'; +import cssLoader from './lib/css-loader'; + +/** + * @param {import('./configure').Options} options + * @returns {boolean} + */ +export function shouldUseWebpack(options) { + return true; +} + +/** + * @param {Object} karmaConfig + * @param {Object} pkg + * @param {import('./configure').Options} options + */ +export function addWebpackConfig(karmaConfig, pkg, options) { + const WEBPACK_VERSION = String(require('webpack').version || '3.0.0'); + const WEBPACK_MAJOR = parseInt(WEBPACK_VERSION.split('.')[0], 10); + + const WEBPACK_CONFIGS = ['webpack.config.babel.js', 'webpack.config.js']; + + let cwd = process.cwd(), + res = (file) => path.resolve(cwd, file); + + let webpackConfig = options.webpackConfig; + + if (pkg.scripts) { + for (let i in pkg.scripts) { + let script = pkg.scripts[i]; + if (/\bwebpack\b[^&|]*(-c|--config)\b/.test(script)) { + let matches = script.match( + /(?:-c|--config)\s+(?:([^\s])|(["'])(.*?)\2)/ + ); + let configFile = matches && (matches[1] || matches[2]); + if (configFile) WEBPACK_CONFIGS.push(configFile); + } + } + } + + if (!webpackConfig) { + for (let i = WEBPACK_CONFIGS.length; i--; ) { + webpackConfig = tryRequire(res(WEBPACK_CONFIGS[i])); + if (webpackConfig) break; + } + } + + if (typeof webpackConfig === 'function') { + webpackConfig = webpackConfig( + { karmatic: true }, + { mode: 'development', karmatic: true } + ); + } + webpackConfig = webpackConfig || {}; + + let loaders = [].concat( + delve(webpackConfig, 'module.loaders') || [], + delve(webpackConfig, 'module.rules') || [] + ); + + function evaluateCondition(condition, filename, expected) { + if (typeof condition === 'function') { + return condition(filename) == expected; + } else if (condition instanceof RegExp) { + return condition.test(filename) == expected; + } + if (Array.isArray(condition)) { + for (let i = 0; i < condition.length; i++) { + if (evaluateCondition(condition[i], filename)) return expected; + } + } + return !expected; + } + + function getLoader(predicate) { + if (typeof predicate === 'string') { + let filename = predicate; + predicate = (loader) => { + let { test, include, exclude } = loader; + if (exclude && evaluateCondition(exclude, filename, false)) + return false; + if (include && !evaluateCondition(include, filename, true)) + return false; + if (test && evaluateCondition(test, filename, true)) return true; + return false; + }; + } + for (let i = 0; i < loaders.length; i++) { + if (predicate(loaders[i])) { + return { index: i, loader: loaders[i] }; + } + } + return false; + } + + function webpackProp(name, value) { + let configured = delve(webpackConfig, name); + if (Array.isArray(value)) { + return value.concat(configured || []).filter(dedupe); + } + return Object.assign({}, configured || {}, value); + } + + for (let prop of Object.keys(karmaConfig.preprocessors)) { + karmaConfig.preprocessors[prop].unshift('webpack'); + } + + karmaConfig.plugins.push(require.resolve('karma-webpack')); + + karmaConfig.webpack = { + devtool: 'inline-source-map', + // devtool: 'module-source-map', + mode: webpackConfig.mode || 'development', + module: { + // @TODO check webpack version and use loaders VS rules as the key here appropriately: + rules: loaders + .concat( + !getLoader((rule) => + `${rule.use},${rule.loader}`.match(/\bbabel-loader\b/) + ) + ? babelLoader(options) + : false, + !getLoader('foo.css') && cssLoader(options) + ) + .filter(Boolean), + }, + resolve: webpackProp('resolve', { + modules: webpackProp('resolve.modules', [ + 'node_modules', + path.resolve(__dirname, '../node_modules'), + ]), + alias: webpackProp('resolve.alias', { + [pkg.name]: res('.'), + src: res('src'), + }), + }), + resolveLoader: webpackProp('resolveLoader', { + modules: webpackProp('resolveLoader.modules', [ + 'node_modules', + path.resolve(__dirname, '../node_modules'), + ]), + alias: webpackProp('resolveLoader.alias', { + [pkg.name]: res('.'), + src: res('src'), + }), + }), + plugins: (webpackConfig.plugins || []).filter((plugin) => { + let name = plugin && plugin.constructor.name; + return /^\s*(UglifyJS|HTML|ExtractText|BabelMinify)(.*Webpack)?Plugin\s*$/gi.test( + name + ); + }), + node: webpackProp('node', {}), + performance: { + hints: false, + }, + }; + + karmaConfig.webpackMiddleware = { + noInfo: true, + logLevel: 'error', + stats: 'errors-only', + }; + + if (WEBPACK_MAJOR < 4) { + delete karmaConfig.webpack.mode; + let { rules } = karmaConfig.webpack.module; + delete karmaConfig.webpack.module.rules; + karmaConfig.webpack.module.loaders = rules; + } + + return karmaConfig; +}