├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── package.json ├── src ├── index.ts └── webpack.ts ├── test └── config.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .idea 3 | .vscode 4 | npm-debug.log* 5 | /typings 6 | 7 | /yarn.lock 8 | /dist 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /typings 2 | /.vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - 6 10 | before_install: 11 | - npm i -g npm 12 | before_script: 13 | - npm prune 14 | after_success: 15 | - npm run semantic-release 16 | branches: 17 | except: 18 | - /^v\d+\.\d+\.\d+$/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bazyli Brzóska 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy Webpack 2 | 3 | Creating and maintaining configuration files for [Webpack](https://webpack.github.io/) can be both intimidating and a real chore, especially for people who don't want to invest hours or even days to studying the [far-from-great-but-slowly-getting-better docs](https://webpack.github.io/docs/), testing and debugging various options, when in reality, they only wanted to get on with their **real work**: developing apps, sites or libraries. 4 | 5 | **Easy Webpack** presents solutions to this problem to a variety of users in different ways: 6 | - to those that simply "want things to work out of the box", by easily stacking together [pre-made *preset configs*](#the-easy-webpack-ecosystem---feature-and-configuration-presets) to achieve the desired effect 7 | - to more advanced users, by exposing [useful helper methods](#generating-configs) that make it possible to separate concerns and make parts of the config easily extendable or conditional (e.g. based on `NODE_ENV`). 8 | 9 | Instead of defining the Webpack configuration file as a flat, "black box" that contains all the features mixed up together in a long file of strings, objects, plugin imports and arrays, it makes it possible to **modularize** and **clean-up** the config by clearly separating individual *features* - as stackable configuration blocks, which in turn increase readability, [reusability and ease-of-maintainance](#the-easy-webpack-ecosystem---feature-and-configuration-presets). 10 | 11 | ## Usage 12 | 13 | To install the core functionality of **easy-webpack** run the following command: 14 | 15 | ```sh 16 | npm install @easy-webpack/core --save-dev 17 | ``` 18 | 19 | > Note: You'll also need to have `webpack` installed. 20 | 21 | Now, you can use the Easy Webpack's core function, `generateConfig` in your `webpack.config.js` file. 22 | 23 | ```js 24 | const generateConfig = require('@easy-webpack/core').generateConfig; 25 | ``` 26 | 27 | ### Generating Configs 28 | 29 | `generateConfig` is a helper method that can be used to generate a desired config file from different "configuration parts" or "presets" that are passed into it as arguments. The arguments can either be [simple `Objects`](#example-with-pure-objects) or [a `Function`](#object-generators), that returns the desired configuration `Object`, or a mixture of both. The order of arguments matters. 30 | 31 | It is possible to run `generateConfig` many times consecutively, by using the output of the previous execution as the first argument of the next one. This might be useful in situations where you'd like to add a set of features conditionally. 32 | 33 | **Depending on your current knowledge of Webpack, please either read on or jump ahead**: 34 | 35 | - I know very little about Webpack, I'd just like to use it, please. [Jump here](#the-easy-webpack-ecosystem---feature-and-configuration-presets). 36 | - I have already created configuration files for Webpack in the past and I'd like to know how Easy Webpack can help simplify my workflow. Awesome! Read on. 37 | 38 | #### How it works 39 | 40 | By default, when passing an `Object`, the `generateConfig` function does the following: 41 | - recursively merge nested objects (so that new configuration fields can be added, rather than overwritten by successive configs) 42 | - non-recursively append `Array`s (so you can add plugins on top of existing ones, rather than replace all the previously defined ones) 43 | - override `string`s, `boolean`s, `number`s and other types -- in case they were defined in one of the previous configs 44 | 45 | When passing an [Object generating Function](#object-generators), the effects are similar, with one exception: 46 | - `Array`s are always literally replaced 47 | 48 | To get an intuitive feel of what it does in its most basic form, take a look at the following code: 49 | ```js 50 | const a = { a: { text: 'abc', array: [ 1, 2, 3 ] } }; 51 | const b = { a: { text: 'xyz', array: [ 4, 5, 6 ] }, b: 'something else' }; 52 | 53 | const merged = generateConfig(a, b); 54 | 55 | // 'merged' is now a combination of both 'a' and 'b', 56 | // with the exception of 'a.text' which was replaced with the value from 'b' 57 | // { a: { text: 'xyz', array: [ 1, 2, 3, 4, 5, 6 ] }, b: 'something else' }; 58 | ``` 59 | 60 | #### Example with pure `Object`s 61 | 62 | To illustrate how Easy Webpack works, let's create a simple Webpack configuration file using its highlight feature - `generateConfig`. 63 | 64 | First, let's define some configuration objects, each clearly representing separate concerns we'd like to address in the output config file: 65 | 66 |
67 | Click here to expand the example 68 | 69 | ```js 70 | const path = require('path'); 71 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 72 | 73 | const common = { 74 | entry: { 75 | 'app': ['./src/main'] 76 | }, 77 | output: { 78 | path: path.resolve('dist'), 79 | } 80 | } 81 | 82 | const development = { 83 | devtool: 'cheap-module-inline-source-map', 84 | debug: true, 85 | output: { 86 | filename: '[name].bundle.js', 87 | sourceMapFilename: '[name].bundle.map', 88 | chunkFilename: '[id].chunk.js' 89 | }, 90 | plugins: [ 91 | new DefinePlugin({ 92 | 'process.env': { 93 | 'NODE_ENV': JSON.stringify('development') 94 | } 95 | }) 96 | ] 97 | } 98 | 99 | const production = { 100 | devtool: 'source-map', 101 | debug: false, 102 | 103 | output: { 104 | filename: '[name].[chunkhash].bundle.js', 105 | sourceMapFilename: '[name].[chunkhash].bundle.map', 106 | chunkFilename: '[id].[chunkhash].chunk.js' 107 | }, 108 | 109 | plugins: [ 110 | new DefinePlugin({ 111 | 'process.env': { 112 | 'NODE_ENV': JSON.stringify('production') 113 | } 114 | }) 115 | ] 116 | } 117 | 118 | const css = { 119 | module: { 120 | loaders: [{ 121 | test: /\.css$/i, 122 | loaders: ['style', 'css'] 123 | }] 124 | } 125 | } 126 | ``` 127 | 128 |
129 | 130 | We can now use the `generateConfig` function from `@easy-webpack/core` to combine those parts into one, functional configuration object, which differs depending on the `NODE_ENV`: 131 | 132 | ```js 133 | const generateConfig = require('@easy-webpack/core').generateConfig; 134 | 135 | let config; 136 | 137 | switch (process.env.NODE_ENV) { 138 | case 'development': 139 | config = generateConfig(common, development, css); 140 | break; 141 | case 'production': 142 | config = generateConfig(common, production, css); 143 | break; 144 | } 145 | 146 | module.exports = config; 147 | ``` 148 | 149 | This offers you a clear separation of concern in terms of defining your configuration files. 150 | 151 | #### Advanced usage 152 | ##### Literal Replace 153 | 154 | You can achieve literal manipulations by using `literalReplace`: 155 | 156 | ```js 157 | const literalReplace = require('@easy-webpack/core').literalReplace; 158 | 159 | const initial = { 160 | plugins: [ new SomeWebpackPlugin() ] 161 | } 162 | 163 | const override = { 164 | plugins: literalReplace( [ AnotherWebpackPlugin() ] ) 165 | } 166 | 167 | const config = generateConfig(initial, override); 168 | // the output config will only have 'AnotherWebpackPlugin()', but not SomeWebpackPlugin, because of literalReplace 169 | ``` 170 | 171 | ##### Object Generators 172 | 173 | If you need more powerful and fine grained control over the blocks of your configuration (like prepending arrays or re-using current state), instead of passing in the object, you may also pass in a function, that generates the desired configuration part. 174 | 175 | The function you pass in will be evaluated with its `this` bound to the current configuration state, which means you can access previous values in order to modify or extend them. This makes it possible to do virtually any transformation to the config in a consistent manner, e.g. use values previously defined to compute the desired outcome. 176 | 177 | By further creating wrapper functions that generate functions that generate configuration objects (that's a mouthful!), you can create very clean wrappers for specific functionality (this is also how our preset configs are made): 178 | 179 |
180 | 181 | Click here to expand the example 182 | 183 | ```js 184 | import {get} from 'lodash'; // helper for extracting current value // 185 | 186 | function css({ filename = '[name].css', allChunks = false, sourceMap = false, extractText = undefined, resolveRelativeUrl = undefined } = {}) { 187 | return function css() { 188 | const loaders = ['style', `css${sourceMap ? '?sourceMap' : ''}`]; 189 | 190 | if (resolveRelativeUrl) { 191 | loaders.push(`resolve-url${sourceMap ? '?sourceMap' : ''}`); 192 | sourceMap = true; // source maps need to always be on for this 193 | } 194 | 195 | const extractCss = extractText === false; 196 | const providedInstance = extractText instanceof ExtractTextPlugin; 197 | 198 | if (!providedInstance) 199 | extractText = extractCss ? new ExtractTextPlugin(filename, extractText instanceof Object ? extractText : { allChunks, sourceMap }) : null; 200 | 201 | const config = { 202 | module: { 203 | loaders: get(this, 'module.loaders', []).concat([{ 204 | test: /\.css$/i, 205 | loaders: extractCss ? extractText.extract(...loaders.slice(1)) : loaders 206 | }]) 207 | } 208 | } 209 | 210 | if (extractText && !providedInstance) { 211 | config.plugins = [ 212 | extractText 213 | ].concat(get(this, 'plugins', [])) 214 | } 215 | 216 | if (resolveRelativeUrl instanceof Object) { 217 | config['resolveUrlLoader'] = resolveRelativeUrl 218 | } 219 | 220 | return config 221 | } 222 | } 223 | ``` 224 | 225 |
226 | 227 | Note that for simple uses, such as overriding a setting, or adding a plugin, using a simple object is usually enough. 228 | 229 | ## The Easy Webpack Ecosystem - Feature and Configuration Presets 230 | 231 | Easy Webpack offers a number of pre-made **[feature preset NPM packages](https://www.npmjs.com/~easy-webpack)** that can be used to quickly add desired functionality to your Easy Webpack config file. Each feature comes with its own set of dependencies, so that you - the user - do not have to research changes, or worry about the best possible implementation of a given feature at a given time. The idea is as simple as it gets: you install the module, stick it into your config and it should just work. 232 | 233 | This is great in a number of scenarios, such as: 234 | - you're starting out a project and don't want to think too hard about how to configure Webpack 235 | - you want to delegate the task of implementing the configuration of certain Webpack features to other people 236 | - you don't neccessairly feel like researching *ze internets* every other month for an updated tutorial for the up-to-date best practices for implementing a given feature, you'd rather `npm update` and be done with it 237 | - you want a clean "base" config that works, that you can override/append with your own custom features or parts 238 | 239 | If you see value in any of the above mentioned scenarios, you should give feature-config presets a try. 240 | 241 | ### Using Feature Configuration Presets 242 | 243 | You can see all the published feature configs on [NPM](https://www.npmjs.com/~easy-webpack). You'll find a range of packages, from opionionated Production and Development presets, through Babel, TypeScript, CSS, LESS and SASS support, to platform-specific support such as Aurelia or Electron. 244 | 245 | As a rule, all the official Easy Webpack packages are made more robust by returning functions that generate Objects, meaning, the packages can expose a number of parameters to the end-user, which can be used for customization of a given feature (example below). 246 | 247 | Every Easy Webpack package includes typings, which means IDEs such as Visual Studio, Visual Studio Code or Webstorm will show appropriate autocomplete listing all the possibile options. Since the feature configs are still mostly undocumented (Pull Requests appreciated!), its best to rely on the typings and refer to the source code as a last resort. 248 | 249 | To use a given preset simply: 250 | 251 | 1. install it via NPM as you normally would any package 252 | 253 | ```sh 254 | npm install @easy-webpack/config-css --save-dev 255 | ``` 256 | 257 | 2. insert a call to it as an argument to your `generateConfig` call (shown in the example below) 258 | 259 | An example `webpack.config.js` that uses strictly configuration presets might then look like this: 260 | 261 | ```js 262 | const baseConfig = { ... }; // project-specific config like the entry file(s) 263 | 264 | module.exports = generateConfig( 265 | baseConfig, 266 | 267 | require('@easy-webpack/config-env-production') 268 | ({ compress: true }), 269 | 270 | require('@easy-webpack/config-aurelia') 271 | ({ title: 'My Awesome Website' }), 272 | 273 | require('@easy-webpack/config-typescript')(), 274 | require('@easy-webpack/config-html')(), 275 | 276 | require('@easy-webpack/config-css') 277 | ({ filename: 'styles.css', allChunks: true, sourceMap: false }), 278 | 279 | require('@easy-webpack/config-fonts-and-images')(), 280 | require('@easy-webpack/config-global-bluebird')(), 281 | require('@easy-webpack/config-global-jquery')(), 282 | require('@easy-webpack/config-global-regenerator')(), 283 | require('@easy-webpack/config-generate-index-html') 284 | ({ minify: true }), 285 | 286 | require('@easy-webpack/config-uglify') 287 | ({ debug: false }), 288 | 289 | // feel free to add your own overrides, custom plugins, add your own options... e.g.: 290 | // { plugins: [ new CustomPlugin() ] }, // <-- appends a plugin 291 | // { devtool: 'inline-source-map' }, // <-- replaces the devtool 292 | // function () { return { debug: !this.debug } } // <-- uses previous value to compute the new one 293 | ); 294 | ``` 295 | 296 | You may naturally replace, override or append any custom config by simply placing the desired config `Object` or `Object-generating Function` into the arguments passed into `generateConfig` like shown in the comments above. 297 | 298 | For a fully-featured example, take a look at [Aurelia's Navigation Skeleton](https://github.com/aurelia/skeleton-navigation/blob/936693b68209f6c411d869e7f625fcdbbe9fe748/skeleton-typescript-webpack/webpack.config.js). 299 | 300 | ### Why delegate parts of my config file to *ze internets*, why a gazillion NPM modules, one per each simple feature? 301 | 302 | The benefit of having parts of the config in separate NPM modules is that the community can fix bugs and optimize the config file without you - the developers - having to do anything else than `npm update`. No need for blog posts with tedious migrations to new settings -- the deal is: just update the package. 303 | 304 | To the ney-sayers out there, my response is: When somebody wants their own, unmanaged settings, they probably know what and why they're doing something. One can always copy & paste the config out from the managed package or write their own from scratch. Ultimately, it's like the difference between managed and unmanaged webhosting -- you should always choose the right tool for the job. 305 | 306 | ### Notes on usage in production 307 | 308 | If you're running a production-grade product that's using NPM packages, it is considered good practice to either lock your dependencies to a patch level, or use shrinkwrap to deep-lock them altogether and upgrade with the help of tests to confirm your code works in the updated scenario. If you just "upgrade the hell out of it" for the sake of upgrading, then I can't be blamed if things break. 309 | 310 | Every config package follows SemVer, i.e. no breaking changes will be made to config files without bumping a major version. Minor bumps and bug fixes will only include: code maintenance, upgrade tasks, switches for better defaults (e.g. a better compression algorithm, a more compatible default `devtool` - things of that sort). 311 | 312 | Note that it is ultimately the developers responsibility to ensure he has set correct SemVer range for the packages, just like it is the mainainers responsitility to follow SemVer in a SemVer environment. 313 | 314 | ## What are others saying 315 | 316 | > Great job!! Something to 🤔: Make #webpack more usable vs. abstract it's complexity with another tool? What you've made is still 👌💯 317 | 318 | -- Sean T. Larkin | [@TheLarkInn](https://twitter.com/TheLarkInn/) | Webpack Core Team | Source: [Twitter Status](https://twitter.com/TheLarkInn/status/752168761323450369) 319 | 320 | 321 | > You have done a fantastic job by creating easy-webpack. Thank you so much. You don't know how frustrated I was learning Webpack and creating a working webpack.config.js 322 | 323 | -- @rajajhansi | Source: [GitHub Issue](https://github.com/easy-webpack/core/issues/3#issuecomment-230328427) 324 | 325 | 326 | > Webpack has quite a steep learning curve when you start getting knee-deep into it. That new Easy Webpack stuff is simply beautiful -- seriously it's so good. Lots of configurations for Webpack projects without needing to pull your hair out. 327 | 328 | -- @Vheissu | Source: [Gitter @ June 28, 2016 8:39 AM](https://gitter.im/aurelia/Discuss?at=57721bb58441a8124d6e1e84) 329 | 330 | 331 | > easy webpack [is] a great idea 332 | 333 | -- @rquast | Source: [Gitter @ June 28, 2016 8:42 AM](https://gitter.im/aurelia/Discuss?at=57721c3b8c9263ba3011f4cf) 334 | 335 | 336 | ## Contributing 337 | 338 | We use [Semantic Release](https://github.com/semantic-release/semantic-release), which means: 339 | - version is set during release, in the CI, not in Git's copy of `package.json` 340 | - commits should conform to the AngularJS Commit Message Convention, i.e. [these rules](https://github.com/semantic-release/semantic-release#default-commit-message-format) 341 | 342 | If you created an Easy Webpack feature configuration preset package please add the conventional `easy-webpack` tag, so that others may easily find it. In case you would like to migrate it under the `@easy-webpack` scope, open an issue here and we'll see what we can do! It would be best if you used one of the pre-existing packages as a template (Semantic Release + AVA + TypeScript). 343 | 344 | ## Prior art and alternatives 345 | 346 | Direct inspiration for this project: 347 | 348 | - [webpack-merge](https://www.npmjs.com/package/webpack-merge) by @bebraw 349 | - using Array and Object destructuring for Webpack configuration files (like [this](https://github.com/chentsulin/electron-react-boilerplate/blob/d6115ccc957d788c9c8b2a82c4cd2e1685fa0103/webpack.config.development.js#L6)) 350 | 351 | Check out alternatives projects that I was not aware of when developing Easy Webpack: 352 | 353 | - [terse-webpack](https://github.com/ericclemmons/terse-webpack) by @ericclemmons 354 | - [webpack-blocks](https://github.com/andywer/webpack-blocks) by @andywer 355 | - [webpack-parts](https://github.com/andywer/substantial/webpack-parts) by @substantial (deprecated) 356 | - [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) by @eliperelman 357 | - [webpack-config](https://mdreizin.github.io/webpack-config/) by @mdreizin 358 | - [webpack-configurator](https://github.com/lewie9021/webpack-configurator) by @lewie9021 359 | 360 | ## Hall of Fame 361 | 362 | Made possible thanks to these brave souls: 363 | - @niieani (Bazyli Brzóska) https://invent.life 364 | - @Vheissu (Dwayne Charrington) http://ilikekillnerds.com 365 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@easy-webpack/core", 3 | "description": "Easy Webpack configurator", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "scripts": { 7 | "test": "TS_NODE_FAST=true TS_NODE_NO_PROJECT=true ava", 8 | "build": "rimraf dist && tsc -p .", 9 | "semantic-release": "npm run build && semantic-release pre && npm publish --access=public && semantic-release post", 10 | "postpublish": "greenkeeper-postpublish" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/easy-webpack/core.git" 15 | }, 16 | "keywords": [ 17 | "webpack", 18 | "easy", 19 | "configurator", 20 | "configuration", 21 | "config", 22 | "simple" 23 | ], 24 | "author": "Bazyli Brzóska (https://invent.life)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/easy-webpack/core/issues" 28 | }, 29 | "homepage": "https://github.com/easy-webpack/core#readme", 30 | "devDependencies": { 31 | "@types/debug": "0.0.29", 32 | "@types/lodash": "^4.14.38", 33 | "@types/node": "^6.0.46", 34 | "@types/source-map": "^0.1.29", 35 | "@types/webpack": "^1.12.35", 36 | "ava": "^0.16.0", 37 | "greenkeeper-postpublish": "^1.0.0", 38 | "rimraf": "^2.5.4", 39 | "semantic-release": "^4.3.5", 40 | "ts-node": "^1.1.0", 41 | "tslint": "^3.11.0", 42 | "tslint-config-standard": "^1.2.2", 43 | "typescript": "^2.0.0", 44 | "webpack": "beta" 45 | }, 46 | "dependencies": { 47 | "@easy-webpack/assign": "^0.9.9 || ^1.0.0", 48 | "@types/lodash": "^4.14.38", 49 | "lodash": "^4.13.1" 50 | }, 51 | "ava": { 52 | "files": [ 53 | "test/**/*.{ts,js}" 54 | ], 55 | "tap": false, 56 | "require": [ 57 | "ts-node/register" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {Webpack as WebpackConfig} from './webpack' 2 | import {assign, literalReplace} from '@easy-webpack/assign' 3 | export {get} from 'lodash' 4 | export {Webpack as WebpackConfig} from './webpack' 5 | export * from '@easy-webpack/assign' 6 | 7 | function hasProcessFlag(flag) { 8 | return process.argv.join('').indexOf(flag) > -1 9 | } 10 | 11 | export const mergeSummary = { dependencies: [], merged: [], skipped: [] } 12 | export interface ConfigDescription { 13 | name?: string 14 | dependencies?: Array 15 | description?: string 16 | enabled?: undefined | boolean | ((config?: WebpackConfig) => boolean) 17 | action?: 'append' | 'prepend' | 'replace' | ((previousConfig: WebpackConfig, thisConfig?: WebpackConfig, name?: string) => WebpackConfig) 18 | } 19 | 20 | export const description = '📄' 21 | export type WebpackConfigWithDescription = WebpackConfig & { '📄'?: ConfigDescription } 22 | 23 | export function merge(config: WebpackConfig, ...configs: Array) { 24 | mergeSummary.dependencies = [] 25 | mergeSummary.merged = [] 26 | mergeSummary.skipped = [] 27 | let i = 0 28 | for (let overlayConfig of configs) { 29 | let configDescription = overlayConfig[description] || { } 30 | let name = configDescription.name || `unnamed config ${String(i++)}` 31 | let enabled = configDescription.enabled 32 | let action = configDescription.action || 'append' 33 | if (configDescription.dependencies && configDescription.dependencies.length) { 34 | mergeSummary.dependencies.push(...overlayConfig[description].dependencies) 35 | } 36 | delete overlayConfig[description] 37 | if (enabled === undefined || enabled === true || (typeof enabled === 'function' && enabled(config))) { 38 | config = typeof action === 'function' ? action(config, overlayConfig, name) : assign(config, overlayConfig, name, action) 39 | mergeSummary.merged.push(name) 40 | } else { 41 | mergeSummary.skipped.push(name) 42 | } 43 | } 44 | return config 45 | } 46 | 47 | /** 48 | * Below are backwards compatibile easy-webpack configs: 49 | */ 50 | 51 | /** 52 | * A webpack config object with optional 'metadata' 53 | */ 54 | export type WebpackConfigWithMetadata = WebpackConfig & { metadata?: any } 55 | export type EasyWebpackConfig = WebpackConfigWithMetadata | ((this: WebpackConfigWithMetadata) => WebpackConfigWithMetadata) 56 | export const generateConfigOptions = { addDefaultMetadata: true, alwaysAddBaseMetadata: false } 57 | 58 | export function generateConfig(...configs: Array) { 59 | let config = {} as WebpackConfigWithMetadata 60 | if (generateConfigOptions.alwaysAddBaseMetadata || (!config.metadata && generateConfigOptions.addDefaultMetadata)) { 61 | config.metadata = { 62 | port: parseInt(process.env.WEBPACK_PORT) || 9000, 63 | host: process.env.WEBPACK_HOST || 'localhost', 64 | ENV: process.env.NODE_ENV || process.env.ENV || 'development', 65 | HMR: hasProcessFlag('hot') || !!process.env.WEBPACK_HMR, 66 | } 67 | } 68 | 69 | for (let configMethod of configs) { 70 | if (typeof configMethod === 'function') { 71 | let overlayConfig = configMethod.apply(config) as WebpackConfigWithMetadata 72 | config = assign(config, overlayConfig, configMethod['name'] || 'config', 'replace') 73 | } else { 74 | let overlayConfig = configMethod 75 | config = assign(config, overlayConfig, configMethod['name'] || 'config', 'append') 76 | } 77 | } 78 | return config 79 | } 80 | 81 | export function stripMetadata(config: EasyWebpackConfig) { 82 | let overlayConfig: WebpackConfigWithMetadata 83 | if (typeof config === 'function') { 84 | overlayConfig = config.apply({}) 85 | } else { 86 | overlayConfig = Object.assign({}, config) 87 | } 88 | delete overlayConfig.metadata 89 | return overlayConfig as WebpackConfig 90 | } 91 | 92 | export default generateConfig 93 | -------------------------------------------------------------------------------- /src/webpack.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/NejcZdovc/webpack/blob/pr/%233045-definition/schemas/webpack.d.ts 2 | // https://github.com/webpack/webpack/pull/3094 3 | 4 | // Type definitions for Webpack 5 | // Project: https://webpack.github.io/ 6 | // Definitions by: Nejc Zdovc 7 | // Definitions: https://github.com/webpack/webpack 8 | 9 | 10 | export declare namespace WebpackType { 11 | interface Node { 12 | Buffer?: boolean | "mock"; 13 | __dirname?: boolean | "mock"; 14 | __filename?: boolean | "mock"; 15 | console?: boolean | "mock"; 16 | global?: boolean; 17 | process?: boolean | "mock"; 18 | } 19 | 20 | interface WatchOptions { 21 | // Delay the rebuilt after the first change. Value is a time in ms. 22 | aggregateTimeout: number; 23 | 24 | // Use boolean to use polling. Use number for using polling with specified interval. 25 | poll: boolean | number; 26 | } 27 | 28 | interface Module { 29 | exprContextCritical?: boolean; 30 | exprContextRecursive?: boolean; 31 | exprContextRegExp?: RegExp; 32 | exprContextRequest?: string; 33 | 34 | // An array of automatically applied loaders. 35 | loaders?: RuleSetRules; 36 | 37 | // Don't parse files matching. It's matched against the full resolved request. 38 | noParse?: RegExp[] | RegExp; 39 | 40 | // An array of rules applied for modules. 41 | rules?: RuleSetRules; 42 | unknownContextCritical?: boolean; 43 | unknownContextRecursive?: boolean; 44 | unknownContextRegExp?: RegExp; 45 | unknownContextRequest?: string; 46 | wrappedContextCritical?: boolean; 47 | wrappedContextRecursive?: boolean; 48 | wrappedContextRegExp?: RegExp; 49 | } 50 | 51 | interface RuleSetRule { 52 | enforce?: "pre" | "post"; 53 | exclude?: RuleSetCondition; 54 | include?: RuleSetCondition; 55 | issuer?: RuleSetCondition; 56 | loader?: string | RuleSetUse; 57 | loaders?: RuleSetUse; 58 | oneOf?: RuleSetRules; 59 | options?: RuleSetQuery 60 | parser?: Object; 61 | query?: RuleSetQuery 62 | resource?: RuleSetCondition; 63 | rules?: RuleSetRules; 64 | test?: RuleSetCondition; 65 | use?: RuleSetUse; 66 | } 67 | 68 | interface RuleSetConditionObject { 69 | and?: RuleSetConditions; 70 | exclude?: RuleSetCondition; 71 | include?: RuleSetCondition; 72 | not?: RuleSetConditions; 73 | or?: RuleSetConditions; 74 | test?: RuleSetCondition; 75 | } 76 | 77 | interface RuleSetUseItemObject { 78 | loader?: string; 79 | options?: RuleSetQuery; 80 | query?: RuleSetQuery; 81 | } 82 | 83 | interface Output { 84 | // Add a comment in the UMD wrapper. 85 | // String: Append the same comment above each import style. 86 | // OutputAuxiliaryComment: Set explicit comments for `commonjs`, `commonjs2`, `amd`, and `root`. 87 | auxiliaryComment?: string | OutputAuxiliaryComment; 88 | 89 | // The filename of non-entry chunks as relative path inside the `output.path` directory. 90 | chunkFilename?: string; 91 | 92 | // This option enables cross-origin loading of chunks. 93 | crossOriginLoading?: false | "anonymous" | "use-credentials"; 94 | 95 | // Similar to `output.devtoolModuleFilenameTemplate`, but used in the case of duplicate module identifiers. 96 | devtoolFallbackModuleFilenameTemplate?: string | Function; 97 | 98 | // Enable line to line mapped mode for all/specified modules. Line to line mapped mode uses a simple SourceMap where each line of the generated source is mapped to the same line of the original source. It’s a performance optimization. Only use it if your performance need to be better and you are sure that input lines match which generated lines. 99 | // Boolean: `true` enables it for all modules (not recommended) 100 | // Object: An object similar to `module.loaders` enables it for specific files. 101 | devtoolLineToLine?: boolean | OutputDevtoolLineToLine; 102 | 103 | // Filename template string of function for the sources array in a generated SourceMap. 104 | devtoolModuleFilenameTemplate?: string | Function; 105 | 106 | // Specifies the name of each output file on disk. You must **not** specify an absolute path here! The `output.path` option determines the location on disk the files are written to, filename is used solely for naming the individual files. 107 | filename?: string; 108 | hashDigest?: string; 109 | hashDigestLength?: number; 110 | hashFunction?: string; 111 | 112 | // The filename of the Hot Update Chunks. They are inside the output.path directory. 113 | hotUpdateChunkFilename?: string; 114 | 115 | // The JSONP function used by webpack for async loading of hot update chunks. 116 | hotUpdateFunction?: string; 117 | 118 | // The filename of the Hot Update Main File. It is inside the `output.path` directory. 119 | hotUpdateMainFilename?: string; 120 | 121 | // The JSONP function used by webpack for async loading of chunks. 122 | jsonpFunction?: string; 123 | 124 | // If set, export the bundle as library. `output.library` is the name. 125 | library?: string | string[]; 126 | 127 | libraryTarget?: OutputLibraryTarget; 128 | 129 | // The output directory as **absolute path**. 130 | path?: string; 131 | 132 | // Include comments with information about the modules. 133 | pathinfo?: boolean; 134 | 135 | // The `publicPath` specifies the public URL address of the output files when referenced in a browser. 136 | publicPath?: string; 137 | 138 | // The filename of the SourceMaps for the JavaScript files. They are inside the `output.path` directory. 139 | sourceMapFilename?: string; 140 | 141 | // Prefixes every line of the source in the bundle with this string. 142 | sourcePrefix?: string; 143 | 144 | // If `output.libraryTarget` is set to umd and `output.library` is set, setting this to true will name the AMD module. 145 | umdNamedDefine?: boolean; 146 | } 147 | 148 | interface OutputAuxiliaryComment { 149 | amd?: string; 150 | commonjs?: string; 151 | commonjs2?: string; 152 | root?: string; 153 | } 154 | 155 | interface OutputDevtoolLineToLine { 156 | exclude?: string; 157 | include?: string; 158 | test?: string; 159 | } 160 | 161 | interface Resolve { 162 | alias?: { [key: string]: string } | ResolveAlias[]; 163 | aliasFields?: [string | string[]]; 164 | cachePredicate?: Function; 165 | descriptionFiles?: string[]; 166 | enforceExtension?: boolean; 167 | enforceModuleExtension?: boolean; 168 | extensions?: string[]; 169 | fileSystem?: any; // TODO missing type 170 | mainFields?: [string | string[]]; 171 | mainFiles?: string[]; 172 | moduleExtensions?: string[]; 173 | modules?: string[]; 174 | plugins?: any[]; 175 | resolver?: any; // TODO missing type 176 | symlinks?: boolean; 177 | unsafeCache?: boolean | Object; 178 | } 179 | 180 | interface ResolveAlias { 181 | alias?: string; 182 | name?: string; 183 | onlyModule?: boolean; 184 | } 185 | 186 | interface Object { 187 | [key: string]: any; 188 | } 189 | 190 | type EntryItem = string | string[]; 191 | 192 | type OutputLibraryTarget = 193 | "var" 194 | | "assign" 195 | | "this" 196 | | "window" 197 | | "global" 198 | | "commonjs" 199 | | "commonjs2" 200 | | "commonjs-module" 201 | | "amd" 202 | | "umd" 203 | | "umd2" 204 | | "jsonp"; 205 | 206 | type Stats = 207 | "none" 208 | | "errors-only" 209 | | "minimal" 210 | | "normal" 211 | | "verbose"; 212 | 213 | type Target = 214 | "web" 215 | | "webworker" 216 | | "node" 217 | | "async-node" 218 | | "node-webkit" 219 | | "atom" 220 | | "electron" 221 | | "electron-main" 222 | | "electron-renderer"; 223 | 224 | type Externals = string | Object | ((context: any, request: any, callback: (err: any, result: any)=> any) => any) | RegExp; 225 | 226 | type RuleSetConditionSimple = RegExp | string | Function | RuleSetConditionObject; 227 | type RuleSetCondition = RuleSetConditionSimple | RuleSetConditionSimple[]; 228 | type RuleSetConditions = RuleSetCondition[]; 229 | 230 | type RuleSetUse = RuleSetUseItem | RuleSetUseItem[]; 231 | type RuleSetUseItem = string | RuleSetUseItemObject; 232 | 233 | type RuleSetQuery = Object | string; 234 | 235 | type RuleSetRules = RuleSetRule[]; 236 | } 237 | 238 | export interface Webpack { 239 | /** 240 | * Set the value of `require.amd` and `define.amd`. 241 | */ 242 | amd?: {[key: string]: boolean}; 243 | 244 | /** 245 | * Report the first error as a hard error instead of tolerating it. 246 | */ 247 | bail?: boolean; 248 | 249 | /** 250 | * Cache generated modules and chunks to improve performance for multiple incremental builds. 251 | * 252 | * Boolean: You can pass `false` to disable it. 253 | * Object: You can pass an object to enable it and let webpack use the passed object as cache.This way you can share the cache object between multiple compiler calls. 254 | */ 255 | cache?: false | WebpackType.Object; 256 | 257 | /* 258 | The base directory (absolute path!) for resolving the `entry` option. If `output.pathinfo` is set, the included pathinfo is shortened to this directory. 259 | */ 260 | context?: string; 261 | 262 | /** 263 | * References to other configurations to depend on. 264 | */ 265 | dependencies?: string[]; 266 | 267 | /** 268 | * Can be used to configure the behaviour of webpack-dev-server when the webpack config is passed to webpack-dev-server CLI. 269 | */ 270 | devServer?: WebpackType.Object; 271 | 272 | /** 273 | * A developer tool to enhance debugging. 274 | * Note: Boolean can be used only with value `false`. 275 | */ 276 | devtool?: string | false; 277 | 278 | /** 279 | * The entry point(s) of the compilation. 280 | * 281 | * Object: Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array. If its' array all modules are loaded upon startup. The last one is exported. If it's a string look at the definition for string. 282 | * String: The entry point for one output file. The string is resolved to a module which is loaded upon startup. 283 | */ 284 | entry?: WebpackType.EntryItem | {[key: string]: WebpackType.EntryItem}; 285 | 286 | /** 287 | * Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on `output.libraryTarget`. 288 | * 289 | * String: An exact matched dependency becomes external. The same string is used as external dependency. 290 | * Object: If an dependency matches exactly a property of the object, the property value is used as dependency. 291 | * Function: `function(context, request, callback(err, result))` The function is called on each dependency. 292 | * RegExp: Every matched dependency becomes external. 293 | * Array: Use the all externals options defined above. 294 | */ 295 | externals?: WebpackType.Externals | WebpackType.Externals[]; 296 | 297 | /** 298 | * Custom values available in the loader context. 299 | */ 300 | loader?: WebpackType.Object; 301 | 302 | /** 303 | * Options affecting the normal modules (`NormalModuleFactory`). 304 | */ 305 | module?: WebpackType.Module; 306 | 307 | /** 308 | * Name of the configuration. Used when loading multiple configurations. 309 | */ 310 | name?: string; 311 | 312 | /** 313 | * Include polyfills or mocks for various node stuff. 314 | */ 315 | node?: WebpackType.Node; 316 | 317 | /** 318 | * Options affecting the output of the compilation. `output` options tell webpack how to write the compiled files to disk. 319 | */ 320 | output?: WebpackType.Output; 321 | 322 | /** 323 | * Add additional plugins to the compiler. 324 | */ 325 | plugins?: WebpackType.Object[]; 326 | 327 | /** 328 | * Capture timing information for each module. 329 | */ 330 | profile?: boolean; 331 | 332 | /** 333 | * Store compiler state to a json file. 334 | */ 335 | recordsInputPath?: string; 336 | 337 | /** 338 | * Load compiler state from a json file. 339 | */ 340 | recordsOutputPath?: string; 341 | 342 | /** 343 | * Store/Load compiler state from/to a json file. This will result in persistent ids of modules and chunks. An absolute path is expected. `recordsPath` is used for `recordsInputPath` and `recordsOutputPath` if they left undefined. 344 | */ 345 | recordsPath?: string; 346 | 347 | /** 348 | * Options affecting the resolving of modules. 349 | */ 350 | resolve?: WebpackType.Resolve; 351 | 352 | resolveLoader?: WebpackType.Resolve; 353 | 354 | /** 355 | * Used by the webpack CLI program to pass stats options. 356 | */ 357 | stats?: WebpackType.Object | boolean | WebpackType.Stats; 358 | 359 | /** 360 | * Specifies webpack deployment target. This modifies how the webpack bootstrap function is generated based on each target. 361 | */ 362 | target?: Function | WebpackType.Target; 363 | 364 | /** 365 | * Enter watch mode, which rebuilds on file change. 366 | */ 367 | watch?: boolean; 368 | 369 | /** 370 | * TODO add description 371 | */ 372 | watchOptions?: WebpackType.WatchOptions; 373 | } -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import {assign} from '../src' 3 | 4 | test(`dummy`, t => { 5 | t.true(true) 6 | }) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "experimentalDecorators": true, 6 | "moduleResolution": "node", 7 | "allowJs": false, 8 | "declaration": true, 9 | "outDir": "dist", 10 | "rootDir": "src", 11 | "sourceMap": true, 12 | "lib": ["es6"] 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "dist", 17 | "test", 18 | "example" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-standard" 3 | } 4 | --------------------------------------------------------------------------------