UNPKG

@dabapps/create-webpack-config

Version:

A utility for creating webpack configs with common settings

276 lines (232 loc) 7.11 kB
const { EnvironmentPlugin } = require('webpack'); // eslint-disable-next-line import/no-extraneous-dependencies const path = require('path'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const CWD = process.cwd(); const POLYFILLS = [require.resolve('raf/polyfill')]; const MATCHES_LEADING_DOT = /^\./; const BABEL_BASE_PRESETS = [ [ require.resolve('@babel/preset-env'), { modules: false, useBuiltIns: 'usage', corejs: { version: 3 }, }, ], require.resolve('@babel/preset-react'), ]; const BABEL_BASE_PLUGINS = [ require.resolve('@babel/plugin-proposal-class-properties'), require.resolve('@babel/plugin-proposal-object-rest-spread'), ]; const BABEL_TYPESCRIPT_PRESETS = BABEL_BASE_PRESETS.concat( require.resolve('@babel/preset-typescript') ); const BABEL_TYPESCRIPT_PLUGINS = BABEL_BASE_PLUGINS.concat( require.resolve('babel-plugin-const-enum') ); function validateOptions(options) { if (!options || typeof options !== 'object' || Array.isArray(options)) { throw new Error('Invalid config options - must be an object'); } if (!('input' in options)) { throw new Error('No "input" in config options'); } if ( !options.input || (typeof options.input !== 'string' && typeof options.input !== 'object') || Array.isArray(options.input) ) { throw new Error( 'Invalid "input" option - must be a string or keyed object' ); } if (typeof options.input === 'object' && !Object.keys(options.input).length) { throw new Error('No keys in "input" option'); } if (!('outDir' in options)) { throw new Error('No "outDir" in config options'); } if (!options.outDir || typeof options.outDir !== 'string') { throw new Error('Invalid "outDir" option - must be a string'); } if (!('tsconfig' in options)) { throw new Error('No "tsconfig" in config options'); } if (!options.tsconfig || typeof options.tsconfig !== 'string') { throw new Error('Invalid "tsconfig" in config options - must be a string'); } if ( (typeof options.env !== 'object' && typeof options.env !== 'undefined') || (typeof options.env === 'object' && !options.env) || Array.isArray(options.env) ) { throw new Error('Invalid "env" option - must be a keyed object'); } if ( !( Array.isArray(options.rawFileExtensions) || typeof options.rawFileExtensions === 'undefined' ) ) { throw new Error('Invalid "rawFileExtensions" option - must be an array'); } if ( typeof options.rootDir !== 'string' && typeof options.rootDir !== 'undefined' ) { throw new Error('Invalid "rootDir" options - must be a string'); } if (typeof options.rootDir === 'string' && !options.rootDir) { throw new Error('Invalid "rootDir" option - cannot be an empty string'); } if ( typeof options.include !== 'undefined' && !(typeof options.include === 'string' || Array.isArray(options.include)) ) { throw new Error('Invalid "include" option - must be a string or array'); } if (typeof options.include === 'string' && !options.include) { throw new Error('Invalid "include" option - cannot be an empty string'); } if (Array.isArray(options.include) && !options.include.length) { throw new Error('Invalid "include" option - cannot be an empty array'); } } function createEntry(options) { if (typeof options.input === 'string') { return POLYFILLS.concat(path.resolve(CWD, options.input)); } const entry = {}; Object.keys(options.input).forEach(key => { entry[key] = POLYFILLS.concat(path.resolve(CWD, options.input[key])); }); return entry; } function getRootDir(options) { if (typeof options.rootDir === 'string') { return path.resolve(CWD, options.rootDir); } if (typeof options.input === 'string') { return path.dirname(path.resolve(CWD, options.input)); } const dirs = []; Object.keys(options.input).forEach(key => { const dir = path.dirname(path.resolve(CWD, options.input[key])); if (dirs.length && dirs.indexOf(dir) < 0) { throw new Error( 'More than one possible root directory - please specify a "rootDir" option' ); } dirs.push(dir); }); return dirs[0]; } function getIncludeDirs(options) { const includes = [] .concat(options.include || []) .map(include => path.resolve(CWD, include)); if (typeof options.input === 'string') { return includes.concat(path.dirname(path.resolve(CWD, options.input))); } const dirs = []; Object.keys(options.input).forEach(key => { const dir = path.dirname(path.resolve(CWD, options.input[key])); if (dirs.indexOf(dir) < 0) { dirs.push(dir); } }); return includes.concat(dirs); } function createFileExtensionRegex(options) { const joinedExtensions = options.rawFileExtensions .map(extension => { return extension.trim().replace(MATCHES_LEADING_DOT, ''); }) .join('|'); return new RegExp(`\\.(?:${joinedExtensions})\$`); } function createWebpackConfig(options) { validateOptions(options); const entry = createEntry(options); const rootDir = getRootDir(options); const includeDirs = getIncludeDirs(options); const outFile = typeof options.input === 'string' ? 'bundle.js' : '[name]-bundle.js'; const outDir = path.resolve(CWD, options.outDir); const rules = [ (options.rawFileExtensions || []).length ? { test: createFileExtensionRegex(options), use: require.resolve('raw-loader'), } : null, { test: /\.jsx?$/, use: [ { loader: require.resolve('babel-loader'), options: { babelrc: false, presets: BABEL_BASE_PRESETS, plugins: BABEL_BASE_PLUGINS, sourceType: 'unambiguous', }, }, ], include: includeDirs, }, { test: /\.tsx?$/, use: [ { loader: require.resolve('babel-loader'), options: { babelrc: false, presets: BABEL_TYPESCRIPT_PRESETS, plugins: BABEL_TYPESCRIPT_PLUGINS, sourceType: 'unambiguous', }, }, ], include: includeDirs, }, ].filter(rule => Boolean(rule)); return { performance: { hints: false, }, stats: 'errors-warnings', devtool: 'source-map', entry, output: { filename: outFile, path: outDir, }, module: { rules, }, resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'], alias: { '^': rootDir, }, }, plugins: [ new ForkTsCheckerWebpackPlugin({ typescript: { configFile: path.resolve(process.cwd(), options.tsconfig), }, }), new CircularDependencyPlugin({ failOnError: true, exclude: /node_modules/, cwd: CWD, }), new EnvironmentPlugin(options.env || {}), ], }; } module.exports = createWebpackConfig;