├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── .webpackrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── index.js ├── lib ├── actions.js ├── evaluate.js ├── formats.js ├── index.js ├── parse.js ├── presets.js └── resolve_paths.js ├── package.json ├── src ├── actions.js ├── evaluate.js ├── formats.js ├── index.js ├── parse.js ├── presets.js └── resolve_paths.js └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | // -*- mode: Javascript -*- 2 | 3 | 4 | // Use this file as a starting point for your project's .eslintrc. 5 | // Copy this file, and add rule overrides as needed. 6 | { 7 | "extends": "airbnb/base", 8 | 9 | /** 10 | * Overrides to the airbnb rules 11 | */ 12 | "rules": { 13 | "func-names": 0, 14 | "comma-dangle": [2, "never"], 15 | "no-console": 0, 16 | "no-param-reassign": 0, 17 | "no-undef": [2], 18 | "no-undefined": 1, 19 | "no-use-before-define": 0, 20 | "object-shorthand": 0, 21 | // http://eslint.org/docs/rules/prefer-const 22 | "prefer-const": 1, 23 | // http://eslint.org/docs/rules/no-unused-vars.html 24 | "no-unused-vars": [1, {"vars": "all", "args": "after-used"}], 25 | "padded-blocks": [0, "always"], 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2.3" 4 | script: 5 | - npm run build 6 | - npm run test 7 | - npm run lint 8 | 9 | -------------------------------------------------------------------------------- /.webpackrc: -------------------------------------------------------------------------------- 1 | { 2 | "formats": [ 3 | "jpg", 4 | "json", 5 | "png" 6 | ], 7 | "paths": { 8 | "babel": ["./demo", "./src"], 9 | "jpg": "./demo", 10 | "json": "./package.json", 11 | "png": "./demo", 12 | "css": [ 13 | "./demo", 14 | "./style.css", 15 | "./node_modules/purecss", 16 | "./node_modules/highlight.js/styles/github.css", 17 | "./node_modules/react-ghfork/gh-fork-ribbon.ie.css", 18 | "./node_modules/react-ghfork/gh-fork-ribbon.css" 19 | ] 20 | }, 21 | "presets": [ 22 | "setupReact", 23 | "separateCSS(gh-pages, styles)", 24 | "extractEntry(gh-pages, vendors, [react])" 25 | ], 26 | "common": { 27 | "dist": { 28 | "devtool": "source-map", 29 | "entry": "./src", 30 | "output": { 31 | "path": "./dist", 32 | "libraryTarget": "umd", 33 | "library": "Boilerplate" 34 | } 35 | }, 36 | "test": { 37 | "paths": { 38 | "babel": ["./src", "./tests"] 39 | } 40 | } 41 | }, 42 | "env": { 43 | "start": { 44 | "devtool": "eval-source-map", 45 | "entry": "./demo" 46 | }, 47 | "gh-pages": { 48 | "entry": { 49 | "app": "./demo" 50 | }, 51 | "output": { 52 | "path": "./gh-pages", 53 | "filename": "[name].[chunkhash].js", 54 | "chunkFilename": "[chunkhash].js" 55 | }, 56 | "actions": [ 57 | "setEnvironment(production)", 58 | "minify" 59 | ] 60 | }, 61 | "dist": { 62 | "output": { 63 | "filename": "boilerplate.js" 64 | } 65 | }, 66 | "dist:min": { 67 | "output": { 68 | "filename": "boilerplate.min.js" 69 | }, 70 | "actions": [ 71 | "minify" 72 | ] 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.0 / 2016-01-12 2 | ================== 3 | 4 | * Initial implementation 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Juho Vepsalainen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECATED!** - Use functional composition and **webpack-merge** instead! 2 | 3 | [![build status](https://secure.travis-ci.org/survivejs/webpack-presets.png)](http://travis-ci.org/survivejs/webpack-presets) 4 | # webpack-presets - Shareable configuration presets for Webpack 5 | 6 | *webpack-presets* provides an abstraction over top of regular Webpack configuration. Even though this adds some complexity, it also allows you to implement shareable presets. Each preset may consist of regular Webpack configuration, *actions*, and *formats*. Actions provide abstraction over smaller tasks while formats give simple means to define loader mappings. They rely on optional *paths* configuration for enhanced performance. 7 | 8 | > Check out [SurviveJS - Webpack and React](http://survivejs.com/) to dig deeper into the topic. 9 | 10 | ## API 11 | 12 | ```javascript 13 | import * as fs from 'fs'; 14 | 15 | import { 16 | evaluate, 17 | actions, 18 | formats, 19 | presets 20 | } = require('./lib'); 21 | 22 | // load higher level configuration. *.webpackrc* is just one alternative 23 | const webpackrc = JSON.parse(fs.readFileSync('./.webpackrc', { 24 | encoding: 'utf-8' 25 | })); 26 | 27 | export default evaluate({ 28 | rootPath: __dirname, // root path of the project 29 | actions, // actions to map against 30 | formats, // loader mappings 31 | presets, // presets (consists of actions and formats) 32 | webpackrc, // configuration binding actions/formats/presets 33 | target: 'dist' // pick from process.ENV for instance 34 | }, 35 | // optional custom configuration. as many fragments as you like 36 | // 37 | // this is handy if you want to set up custom plugins per target 38 | // for example 39 | { 40 | foo: 'bar' 41 | }, 42 | { 43 | bar: 'baz' 44 | } 45 | ); 46 | ``` 47 | 48 | ## Example 49 | 50 | **.webpackrc** 51 | 52 | ```js 53 | { 54 | // which formats to use in the project 55 | "formats": [ 56 | "jpg", 57 | "json", 58 | "png" 59 | ], 60 | // where to find them (optional, helps with performance) 61 | // this maps directly to loader `include` 62 | "paths": { 63 | "babel": ["./demo", "./src"], 64 | "jpg": "./demo", 65 | "json": "./package.json", 66 | "png": "./demo", 67 | "css": [ 68 | "./demo", 69 | "./style.css", 70 | "./node_modules/purecss", 71 | "./node_modules/highlight.js/styles/github.css", 72 | "./node_modules/react-ghfork/gh-fork-ribbon.ie.css", 73 | "./node_modules/react-ghfork/gh-fork-ribbon.css" 74 | ] 75 | }, 76 | // which presets to use. presets use the same format 77 | // and allow abstraction 78 | "presets": [ 79 | "setupReact", 80 | "separateCSS(gh-pages, styles)", 81 | "extractEntry(gh-pages, vendors, [react])" 82 | ], 83 | // common configuration for each namespace 84 | "common": { 85 | // dist configuration (dist, dist:min, ...) 86 | "dist": { 87 | "devtool": "source-map", 88 | "entry": "./src", 89 | "output": { 90 | "path": "./dist", 91 | "libraryTarget": "umd", 92 | "library": "Boilerplate" 93 | } 94 | }, 95 | // test configuration (test, test:tdd, ...) 96 | "test": { 97 | // override paths so tests are found by babel 98 | "paths": { 99 | "babel": ["./src", "./tests"] 100 | } 101 | } 102 | }, 103 | // build targets. `target` parameter selects this 104 | "env": { 105 | "start": { 106 | "devtool": "eval-source-map", 107 | "entry": "./demo" 108 | }, 109 | "gh-pages": { 110 | "entry": { 111 | "app": "./demo" 112 | }, 113 | "output": { 114 | "path": "./gh-pages", 115 | "filename": "[name].[chunkhash].js", 116 | "chunkFilename": "[chunkhash].js" 117 | }, 118 | // custom actions to trigger per target 119 | "actions": [ 120 | "setEnvironment(production)", 121 | "minify" 122 | ] 123 | }, 124 | // note namespacing. `common` configuration applies to both 125 | "dist": { 126 | "output": { 127 | "filename": "boilerplate.js" 128 | } 129 | }, 130 | "dist:min": { 131 | "output": { 132 | "filename": "boilerplate.min.js" 133 | }, 134 | "actions": [ 135 | "minify" 136 | ] 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | **webpack.config.babel.js** 143 | 144 | ```javascript 145 | import * as fs from 'fs'; 146 | 147 | import webpack from 'webpack'; 148 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 149 | import SystemBellPlugin from 'system-bell-webpack-plugin'; 150 | import Clean from 'clean-webpack-plugin'; 151 | import React from 'react'; 152 | 153 | import App from './demo/App.jsx'; 154 | import pkg from './package.json'; 155 | 156 | import webpackActions from './lib/actions'; 157 | import webpackFormats from './lib/formats'; 158 | import webpackPresets from './lib/presets'; 159 | import evaluate from './lib/evaluate'; 160 | 161 | // read *.webpackrc*. This could be in *package.json* etc. or code even 162 | const webpackrc = JSON.parse(fs.readFileSync('./.webpackrc', { 163 | encoding: 'utf-8' 164 | })); 165 | 166 | // set target based on env 167 | const TARGET = process.env.npm_lifecycle_event || 'test'; 168 | 169 | process.env.BABEL_ENV = TARGET; 170 | 171 | // set up some custom plugins 172 | const commonConfig = { 173 | plugins: [ 174 | new SystemBellPlugin() 175 | ] 176 | }; 177 | const extraConfig = { 178 | start: { 179 | plugins: [ 180 | new HtmlWebpackPlugin({ 181 | title: pkg.name + ' - ' + pkg.description + ' - DEVELOPMENT' 182 | }) 183 | ] 184 | }, 185 | 'gh-pages': { 186 | plugins: [ 187 | new Clean(['gh-pages']), 188 | new HtmlWebpackPlugin({ 189 | title: pkg.name + ' - ' + pkg.description, 190 | }) 191 | ] 192 | } 193 | }[TARGET] || {}; 194 | 195 | // compose configuration 196 | module.exports = evaluate({ 197 | actions: webpackActions, 198 | formats: webpackFormats, 199 | presets: webpackPresets, 200 | webpackrc: webpackrc, 201 | target: TARGET 202 | }, 203 | commonConfig, extraConfig 204 | ); 205 | ``` 206 | 207 | ## Paths 208 | 209 | `paths` configuration maps to `loader` `include` fields. It's an optional, but setting it is advisable given it leads to a better understanding of how the assets are mapped. It also improves performance. The paths should be given in a relative format. Adjust `rootPath` to set up the lookup. `__dirname` is a good default. 210 | 211 | ## Actions 212 | 213 | `actions` capture some cross-cutting concern, such as setting up **HMR**. For instance, that action would return a configuration fragment that sets up `plugins` and `devServer`. Here's an example: 214 | 215 | ```javascript 216 | export default (paths) => ({ 217 | enableHMR: () => ({ 218 | plugins: [ 219 | new webpack.HotModuleReplacementPlugin() 220 | ], 221 | devServer: { 222 | historyApiFallback: true, 223 | hot: true, 224 | inline: true, 225 | progress: true, 226 | host: process.env.HOST, 227 | port: process.env.PORT, 228 | stats: 'errors-only' 229 | } 230 | }) 231 | }); 232 | ``` 233 | 234 | The system provides a couple of sample actions like this. The interface accepts `paths` for optimization. 235 | 236 | In order to make it easier to compose, `evaluate` accepts an array of definitions like this. As long as you stick with the interface (`(Object of paths) => {string => function => Object}`) for each, it should work. 237 | 238 | ## Formats 239 | 240 | `formats` encapsulate common loader configurations. Example: 241 | 242 | ```javascript 243 | export default (paths) => ({ 244 | png: (format='png') => ({ 245 | resolve: { 246 | extensions: ['.' + format] 247 | }, 248 | module: { 249 | loaders: [ 250 | { 251 | test: new RegExp('\.' + format + '$'), 252 | loader: 'url?limit=100000&mimetype=image/png', 253 | include: paths.png 254 | } 255 | ] 256 | } 257 | }) 258 | }); 259 | ``` 260 | 261 | The interface is similar again. Each format accepts an optional `format`. That can be used to perform more exotic mappings. I.e., `babel(jsx)`. Again, the same idea as above. 262 | 263 | ## Presets 264 | 265 | Presets are a higher level concept that allow composition. They are higher level fragments that are composited to the output before evaluating any other configuration. This means they can contain other concepts. Consider the example below: 266 | 267 | ```javascript 268 | export default (paths) => ({ 269 | extractEntry: (distEnv, vendorsName, vendorsValue) => { 270 | const ret = { 271 | env: { 272 | actions: [ 273 | `generateCommonsChunk($(vendorsName))` 274 | ] 275 | } 276 | }; 277 | 278 | ret[distEnv] = { 279 | entry: { 280 | [vendorsName]: vendorsValue 281 | } 282 | }; 283 | 284 | return ret; 285 | } 286 | }) 287 | ``` 288 | 289 | The interface is similar as earlier. This time, though, function signatures are flexible and can be customized based on the exact need. Presets allow you to extract common workflows into a format you may compose later. 290 | 291 | ## Contributors 292 | 293 | * [Tim Dorr](https://github.com/timdorr) - Improved README formatting. 294 | 295 | ## License 296 | 297 | *webpack-presets* is available under MIT. See LICENSE for more details. 298 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const program = require('commander'); 3 | const webpack = require('webpack'); 4 | 5 | const webpackPresets = require('../'); 6 | const VERSION = require('../package.json').version; 7 | 8 | main(); 9 | 10 | function main() { 11 | const presets = webpackPresets.presets(); 12 | 13 | program.version(VERSION); 14 | 15 | // TODO: support multiple presets 16 | program 17 | .command('preset [preset]') 18 | .description('Use preset') 19 | .option('-t, --target [target]', 'Build target') 20 | .action(function (preset, options) { 21 | const target = options.target; 22 | 23 | if (preset in presets) { 24 | const configuration = webpackPresets.evaluate({ 25 | rootPath: __dirname, 26 | actions: webpackPresets.actions, 27 | formats: webpackPresets.formats, 28 | presets: webpackPresets.presets, 29 | webpackrc: { 30 | presets: [preset] 31 | }, 32 | target: target 33 | }); 34 | 35 | webpack(configuration, function (err) { 36 | if (err) { 37 | return console.error(err); 38 | } 39 | 40 | console.log('Finished!'); 41 | }); 42 | 43 | // TODO: if target is `start`, run through webpack-dev-server 44 | // instead. note that this will need some special setup for 45 | // hmr to work as discussed at 46 | // https://webpack.github.io/docs/webpack-dev-server.html#inline-mode-with-node-js-api 47 | } else { 48 | console.error('Failed to find "' + preset + '" in "' + 49 | Object.keys(presets).join('", "') + '"'); 50 | } 51 | }); 52 | 53 | program.parse(process.argv); 54 | } 55 | -------------------------------------------------------------------------------- /lib/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _webpack = require('webpack'); 8 | 9 | var _webpack2 = _interopRequireDefault(_webpack); 10 | 11 | var _extractTextWebpackPlugin = require('extract-text-webpack-plugin'); 12 | 13 | var _extractTextWebpackPlugin2 = _interopRequireDefault(_extractTextWebpackPlugin); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | exports.default = function (paths) { 18 | return { 19 | setEnvironment: function setEnvironment(target) { 20 | return { 21 | plugins: [new _webpack2.default.DefinePlugin({ 22 | 'process.env.NODE_ENV': JSON.stringify(target) 23 | })] 24 | }; 25 | }, 26 | lint: function lint(format) { 27 | return { 28 | module: { 29 | preLoaders: [{ 30 | test: new RegExp('\.' + format + '$'), 31 | loader: 'eslint', 32 | include: paths.babel 33 | }] 34 | } 35 | }; 36 | }, 37 | enableHMR: function enableHMR() { 38 | return { 39 | plugins: [new _webpack2.default.HotModuleReplacementPlugin()], 40 | devServer: { 41 | historyApiFallback: true, 42 | hot: true, 43 | inline: true, 44 | progress: true, 45 | host: process.env.HOST, 46 | port: process.env.PORT, 47 | stats: 'errors-only' 48 | } 49 | }; 50 | }, 51 | extractCSS: function extractCSS(name) { 52 | return { 53 | plugins: [new _extractTextWebpackPlugin2.default(name + '.[chunkhash].css')], 54 | module: { 55 | loaders: [{ 56 | test: /\.css$/, 57 | loader: _extractTextWebpackPlugin2.default.extract('style', 'css'), 58 | include: paths.css 59 | }] 60 | } 61 | }; 62 | }, 63 | generateCommonsChunk: function generateCommonsChunk(name) { 64 | return { 65 | plugins: [new _webpack2.default.optimize.CommonsChunkPlugin({ 66 | names: [name, 'manifest'] 67 | }), new _webpack2.default.optimize.DedupePlugin()] 68 | }; 69 | }, 70 | minify: function minify() { 71 | return { 72 | plugins: [new _webpack2.default.optimize.UglifyJsPlugin({ 73 | compress: { 74 | warnings: false 75 | } 76 | })] 77 | }; 78 | } 79 | }; 80 | }; -------------------------------------------------------------------------------- /lib/evaluate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = evaluate; 7 | 8 | var _webpackMerge = require('webpack-merge'); 9 | 10 | var _webpackMerge2 = _interopRequireDefault(_webpackMerge); 11 | 12 | var _resolve_paths = require('./resolve_paths'); 13 | 14 | var _resolve_paths2 = _interopRequireDefault(_resolve_paths); 15 | 16 | var _parse = require('./parse'); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | function evaluate(_ref) { 21 | var rootPath = _ref.rootPath; 22 | var actions = _ref.actions; 23 | var formats = _ref.formats; 24 | var presets = _ref.presets; 25 | var webpackrc = _ref.webpackrc; 26 | var target = _ref.target; 27 | 28 | actions = actions || noop; 29 | formats = formats || noop; 30 | presets = presets || noop; 31 | 32 | var rcConfiguration = _webpackMerge2.default.apply(null, [webpackrc].concat((0, _parse.parse)(apply(presets, paths), webpackrc.presets))); 33 | var parsedEnv = rcConfiguration.env[target] || {}; 34 | var commonConfig = rcConfiguration.common ? rcConfiguration.common[target.split(':')[0]] || {} : {}; 35 | var paths = (0, _resolve_paths2.default)(rootPath, Object.assign({}, rcConfiguration.paths, commonConfig.paths, parsedEnv.paths)); 36 | var evaluatedActions = apply(actions, paths); 37 | var evaluatedFormats = apply(formats, paths); 38 | var rootConfig = { 39 | resolve: { 40 | extensions: [''] 41 | } 42 | }; 43 | var parsedRootActions = (0, _parse.parse)(evaluatedActions, rcConfiguration.actions); 44 | var parsedActions = (0, _parse.parse)(evaluatedActions, parsedEnv.actions); 45 | var parsedRootFormats = (0, _parse.parse)(evaluatedFormats, rcConfiguration.formats); 46 | var parsedFormats = (0, _parse.parse)(evaluatedFormats, parsedEnv.formats); 47 | 48 | for (var _len = arguments.length, config = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 49 | config[_key - 1] = arguments[_key]; 50 | } 51 | 52 | return _webpackMerge2.default.apply(null, [rootConfig, commonConfig].concat(parsedRootActions).concat(parsedRootFormats).concat([parsedEnv]).concat(config).concat(parsedActions).concat(parsedFormats)); 53 | } 54 | 55 | function apply(configuration, paths) { 56 | if (Array.isArray(configuration)) { 57 | return _webpackMerge2.default.apply(null, configuration.map(function (conf) { 58 | return conf(paths); 59 | })); 60 | } 61 | 62 | return configuration(paths); 63 | } 64 | 65 | function noop() {} -------------------------------------------------------------------------------- /lib/formats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (paths) { 8 | return { 9 | png: function png() { 10 | var format = arguments.length <= 0 || arguments[0] === undefined ? 'png' : arguments[0]; 11 | return { 12 | resolve: { 13 | extensions: ['.' + format] 14 | }, 15 | module: { 16 | loaders: [{ 17 | test: new RegExp('\.' + format + '$'), 18 | loader: 'url?limit=100000&mimetype=image/png', 19 | include: paths.png 20 | }] 21 | } 22 | }; 23 | }, 24 | jpg: function jpg() { 25 | var format = arguments.length <= 0 || arguments[0] === undefined ? 'jpg' : arguments[0]; 26 | return { 27 | resolve: { 28 | extensions: ['.' + format] 29 | }, 30 | module: { 31 | loaders: [{ 32 | test: new RegExp('\.' + format + '$'), 33 | loader: 'file', 34 | include: paths.jpg 35 | }] 36 | } 37 | }; 38 | }, 39 | json: function json() { 40 | var format = arguments.length <= 0 || arguments[0] === undefined ? 'json' : arguments[0]; 41 | return { 42 | resolve: { 43 | extensions: ['.' + format] 44 | }, 45 | module: { 46 | loaders: [{ 47 | test: new RegExp('\.' + format + '$'), 48 | loader: 'json', 49 | include: paths.json 50 | }] 51 | } 52 | }; 53 | }, 54 | babel: function babel() { 55 | var format = arguments.length <= 0 || arguments[0] === undefined ? 'js' : arguments[0]; 56 | return { 57 | resolve: { 58 | extensions: ['.' + format] 59 | }, 60 | module: { 61 | loaders: [{ 62 | test: new RegExp('\.' + format + '$'), 63 | loader: 'babel?cacheDirectory', 64 | include: paths.babel 65 | }] 66 | } 67 | }; 68 | }, 69 | css: function css() { 70 | var format = arguments.length <= 0 || arguments[0] === undefined ? 'css' : arguments[0]; 71 | return { 72 | resolve: { 73 | extensions: ['' + format] 74 | }, 75 | module: { 76 | loaders: [{ 77 | test: new RegExp('\.' + format + '$'), 78 | loaders: ['style', 'css'], 79 | include: paths.css 80 | }] 81 | } 82 | }; 83 | } 84 | }; 85 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports._parse = exports._resolvePaths = exports.presets = exports.formats = exports.actions = exports.evaluate = undefined; 7 | 8 | var _evaluate = require('./evaluate'); 9 | 10 | var _evaluate2 = _interopRequireDefault(_evaluate); 11 | 12 | var _actions = require('./actions'); 13 | 14 | var _actions2 = _interopRequireDefault(_actions); 15 | 16 | var _formats = require('./formats'); 17 | 18 | var _formats2 = _interopRequireDefault(_formats); 19 | 20 | var _presets = require('./presets'); 21 | 22 | var _presets2 = _interopRequireDefault(_presets); 23 | 24 | var _resolve_paths = require('./resolve_paths'); 25 | 26 | var _resolve_paths2 = _interopRequireDefault(_resolve_paths); 27 | 28 | var _parse2 = require('./parse'); 29 | 30 | var _parse = _interopRequireWildcard(_parse2); 31 | 32 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 33 | 34 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 35 | 36 | exports.evaluate = _evaluate2.default; 37 | exports.actions = _actions2.default; 38 | exports.formats = _formats2.default; 39 | exports.presets = _presets2.default; 40 | exports._resolvePaths = _resolve_paths2.default; 41 | exports._parse = _parse; -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | function parse(definition, items) { 7 | if (!items) { 8 | return []; 9 | } 10 | 11 | return items.map(function (item) { 12 | if (item.indexOf('(') >= 0) { 13 | var parts = item.split('('); 14 | var name = parts[0]; 15 | var parameters = parseParameters(parts[1].split(')')[0]); 16 | 17 | return definition[name].apply(null, parameters); 18 | } 19 | 20 | return definition[item](); 21 | }); 22 | } 23 | 24 | function parseParameters(item) { 25 | return item.split('[').map(function (v) { 26 | var value = v.trim(); 27 | 28 | if (value.indexOf(']') >= 0) { 29 | return [value.split(']')[0].split(',').map(function (s) { 30 | return s.trim(); 31 | })]; 32 | } 33 | 34 | return value.split(',').map(function (s) { 35 | return s.trim(); 36 | }); 37 | }).reduce(function (a, b) { 38 | return a.concat(b); 39 | }, []).filter(function (a) { 40 | return a; 41 | }); 42 | } 43 | 44 | exports.parse = parse; 45 | exports.parseParameters = parseParameters; -------------------------------------------------------------------------------- /lib/presets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | 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; } 8 | 9 | exports.default = function () { 10 | return (/* paths */{ 11 | setupReact: function setupReact() { 12 | return { 13 | formats: ['babel(js)', 'babel(jsx)'], 14 | common: { 15 | dist: { 16 | externals: { 17 | react: { 18 | commonjs: 'react', 19 | commonjs2: 'react', 20 | amd: 'React', 21 | root: 'React' 22 | } 23 | } 24 | }, 25 | test: { 26 | actions: ['lint(js)', 'lint(jsx)'] 27 | } 28 | }, 29 | env: { 30 | start: { 31 | actions: ['setEnvironment(development)', 'lint(js)', 'lint(jsx)', 'enableHMR'] 32 | } 33 | } 34 | }; 35 | }, 36 | separateCSS: function separateCSS(distEnv, outputFile) { 37 | var ret = { 38 | env: { 39 | start: { 40 | formats: ['css'] 41 | } 42 | } 43 | }; 44 | 45 | ret.env[distEnv] = { 46 | actions: ['extractCSS(' + outputFile + ')'] 47 | }; 48 | 49 | return ret; 50 | }, 51 | extractEntry: function extractEntry(distEnv, vendorsName, vendorsValue) { 52 | var ret = { 53 | env: { 54 | actions: ['generateCommonsChunk($(vendorsName))'] 55 | } 56 | }; 57 | 58 | ret[distEnv] = { 59 | entry: _defineProperty({}, vendorsName, vendorsValue) 60 | }; 61 | 62 | return ret; 63 | } 64 | } 65 | ); 66 | }; -------------------------------------------------------------------------------- /lib/resolve_paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = resolvePaths; 7 | 8 | var _path = require('path'); 9 | 10 | var _path2 = _interopRequireDefault(_path); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | function resolvePaths(rootPath, paths) { 15 | var ret = {}; 16 | 17 | Object.keys(paths).forEach(function (k) { 18 | var v = paths[k]; 19 | 20 | if (Array.isArray(v)) { 21 | ret[k] = v.map(function (p) { 22 | return _path2.default.join(rootPath, p); 23 | }); 24 | } else { 25 | ret[k] = _path2.default.join(rootPath, v); 26 | } 27 | }); 28 | 29 | return ret; 30 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-presets", 3 | "description": "Shareable configuration presets for Webpack", 4 | "author": "Juho Vepsalainen ", 5 | "version": "0.1.1", 6 | "scripts": { 7 | "build": "babel src -d lib", 8 | "watch": "babel src --watch -d lib", 9 | "test": "mocha ./test", 10 | "lint": "eslint .", 11 | "watch:test": "mocha ./test --watch", 12 | "preversion": "npm run lint && npm run build && npm test && git commit --allow-empty -am \"Update lib\"" 13 | }, 14 | "main": "./lib", 15 | "bin": "./bin", 16 | "dependencies": { 17 | "commander": "^2.9.0", 18 | "webpack-merge": "^0.7.3" 19 | }, 20 | "devDependencies": { 21 | "babel-cli": "^6.4.0", 22 | "babel-preset-es2015": "^6.3.13", 23 | "eslint": "^1.10.3", 24 | "eslint-config-airbnb": "^3.1.0", 25 | "extract-text-webpack-plugin": "^1.0.1", 26 | "mocha": "^2.3.4", 27 | "webpack": "^1.12.11" 28 | }, 29 | "peerDependencies": { 30 | "extract-text-webpack-plugin": ">= 1.0.0 < 2.0.0", 31 | "webpack": ">= 1.0.0 < 2.0.0" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/survivejs/webpack-presets.git" 36 | }, 37 | "homepage": "https://github.com/survivejs/webpack-presets", 38 | "bugs": { 39 | "url": "https://github.com/survivejs/webpack-presets/issues" 40 | }, 41 | "keywords": [ 42 | "webpack", 43 | "presets", 44 | "preset" 45 | ], 46 | "license": "MIT" 47 | } 48 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 3 | 4 | export default (paths) => ({ 5 | setEnvironment: (target) => ({ 6 | plugins: [ 7 | new webpack.DefinePlugin({ 8 | 'process.env.NODE_ENV': JSON.stringify(target) 9 | }) 10 | ] 11 | }), 12 | lint: (format) => ({ 13 | module: { 14 | preLoaders: [ 15 | { 16 | test: new RegExp('\.' + format + '$'), 17 | loader: 'eslint', 18 | include: paths.babel 19 | } 20 | ] 21 | } 22 | }), 23 | enableHMR: () => ({ 24 | plugins: [ 25 | new webpack.HotModuleReplacementPlugin() 26 | ], 27 | devServer: { 28 | historyApiFallback: true, 29 | hot: true, 30 | inline: true, 31 | progress: true, 32 | host: process.env.HOST, 33 | port: process.env.PORT, 34 | stats: 'errors-only' 35 | } 36 | }), 37 | extractCSS: (name) => ({ 38 | plugins: [ 39 | new ExtractTextPlugin(name + '.[chunkhash].css') 40 | ], 41 | module: { 42 | loaders: [ 43 | { 44 | test: /\.css$/, 45 | loader: ExtractTextPlugin.extract('style', 'css'), 46 | include: paths.css 47 | } 48 | ] 49 | } 50 | }), 51 | generateCommonsChunk: (name) => ({ 52 | plugins: [ 53 | new webpack.optimize.CommonsChunkPlugin({ 54 | names: [name, 'manifest'] 55 | }), 56 | new webpack.optimize.DedupePlugin() 57 | ] 58 | }), 59 | minify: () => ({ 60 | plugins: [ 61 | new webpack.optimize.UglifyJsPlugin({ 62 | compress: { 63 | warnings: false 64 | } 65 | }) 66 | ] 67 | }) 68 | }); 69 | -------------------------------------------------------------------------------- /src/evaluate.js: -------------------------------------------------------------------------------- 1 | import merge from 'webpack-merge'; 2 | 3 | import resolvePaths from './resolve_paths'; 4 | import { parse } from './parse'; 5 | 6 | export default function evaluate({ 7 | rootPath, actions, formats, presets, webpackrc, target 8 | }, ...config) { 9 | actions = actions || noop; 10 | formats = formats || noop; 11 | presets = presets || noop; 12 | 13 | const rcConfiguration = merge.apply(null, [webpackrc].concat( 14 | parse(apply(presets, paths), webpackrc.presets)) 15 | ); 16 | const parsedEnv = rcConfiguration.env[target] || {}; 17 | const commonConfig = rcConfiguration.common ? 18 | rcConfiguration.common[target.split(':')[0]] || {} : 19 | {}; 20 | const paths = resolvePaths( 21 | rootPath, 22 | Object.assign({}, rcConfiguration.paths, commonConfig.paths, parsedEnv.paths) 23 | ); 24 | const evaluatedActions = apply(actions, paths); 25 | const evaluatedFormats = apply(formats, paths); 26 | const rootConfig = { 27 | resolve: { 28 | extensions: [''] 29 | } 30 | }; 31 | const parsedRootActions = parse(evaluatedActions, rcConfiguration.actions); 32 | const parsedActions = parse(evaluatedActions, parsedEnv.actions); 33 | const parsedRootFormats = parse(evaluatedFormats, rcConfiguration.formats); 34 | const parsedFormats = parse(evaluatedFormats, parsedEnv.formats); 35 | 36 | return merge.apply(null, [rootConfig, commonConfig]. 37 | concat(parsedRootActions).concat(parsedRootFormats).concat([ 38 | parsedEnv 39 | ]).concat(config).concat(parsedActions).concat(parsedFormats)); 40 | } 41 | 42 | function apply(configuration, paths) { 43 | if (Array.isArray(configuration)) { 44 | return merge.apply(null, configuration.map((conf) => conf(paths))); 45 | } 46 | 47 | return configuration(paths); 48 | } 49 | 50 | function noop() {} 51 | -------------------------------------------------------------------------------- /src/formats.js: -------------------------------------------------------------------------------- 1 | export default (paths) => ({ 2 | png: (format = 'png') => ({ 3 | resolve: { 4 | extensions: ['.' + format] 5 | }, 6 | module: { 7 | loaders: [ 8 | { 9 | test: new RegExp('\.' + format + '$'), 10 | loader: 'url?limit=100000&mimetype=image/png', 11 | include: paths.png 12 | } 13 | ] 14 | } 15 | }), 16 | jpg: (format = 'jpg') => ({ 17 | resolve: { 18 | extensions: ['.' + format] 19 | }, 20 | module: { 21 | loaders: [ 22 | { 23 | test: new RegExp('\.' + format + '$'), 24 | loader: 'file', 25 | include: paths.jpg 26 | } 27 | ] 28 | } 29 | }), 30 | json: (format = 'json') => ({ 31 | resolve: { 32 | extensions: ['.' + format] 33 | }, 34 | module: { 35 | loaders: [ 36 | { 37 | test: new RegExp('\.' + format + '$'), 38 | loader: 'json', 39 | include: paths.json 40 | } 41 | ] 42 | } 43 | }), 44 | babel: (format = 'js') => ({ 45 | resolve: { 46 | extensions: ['.' + format] 47 | }, 48 | module: { 49 | loaders: [ 50 | { 51 | test: new RegExp('\.' + format + '$'), 52 | loader: 'babel?cacheDirectory', 53 | include: paths.babel 54 | } 55 | ] 56 | } 57 | }), 58 | css: (format = 'css') => ({ 59 | resolve: { 60 | extensions: ['' + format] 61 | }, 62 | module: { 63 | loaders: [ 64 | { 65 | test: new RegExp('\.' + format + '$'), 66 | loaders: ['style', 'css'], 67 | include: paths.css 68 | } 69 | ] 70 | } 71 | }) 72 | }); 73 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import evaluate from './evaluate'; 2 | import actions from './actions'; 3 | import formats from './formats'; 4 | import presets from './presets'; 5 | import _resolvePaths from './resolve_paths'; 6 | import * as _parse from './parse'; 7 | 8 | export { 9 | evaluate, 10 | actions, 11 | formats, 12 | presets, 13 | _resolvePaths, 14 | _parse 15 | }; 16 | -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | function parse(definition, items) { 2 | if (!items) { 3 | return []; 4 | } 5 | 6 | return items.map((item) => { 7 | if (item.indexOf('(') >= 0) { 8 | const parts = item.split('('); 9 | const name = parts[0]; 10 | const parameters = parseParameters(parts[1].split(')')[0]); 11 | 12 | return definition[name].apply(null, parameters); 13 | } 14 | 15 | return definition[item](); 16 | }); 17 | } 18 | 19 | function parseParameters(item) { 20 | return item.split('[').map((v) => { 21 | const value = v.trim(); 22 | 23 | if (value.indexOf(']') >= 0) { 24 | return [value.split(']')[0].split(',').map((s) => s.trim())]; 25 | } 26 | 27 | return value.split(',').map((s) => s.trim()); 28 | }).reduce((a, b) => a.concat(b), []).filter((a) => a); 29 | } 30 | 31 | export { 32 | parse, 33 | parseParameters 34 | }; 35 | -------------------------------------------------------------------------------- /src/presets.js: -------------------------------------------------------------------------------- 1 | export default (/* paths */) => ({ 2 | setupReact: () => ({ 3 | formats: [ 4 | 'babel(js)', 5 | 'babel(jsx)' 6 | ], 7 | common: { 8 | dist: { 9 | externals: { 10 | react: { 11 | commonjs: 'react', 12 | commonjs2: 'react', 13 | amd: 'React', 14 | root: 'React' 15 | } 16 | } 17 | }, 18 | test: { 19 | actions: [ 20 | 'lint(js)', 21 | 'lint(jsx)' 22 | ] 23 | } 24 | }, 25 | env: { 26 | start: { 27 | actions: [ 28 | 'setEnvironment(development)', 29 | 'lint(js)', 30 | 'lint(jsx)', 31 | 'enableHMR' 32 | ] 33 | } 34 | } 35 | }), 36 | separateCSS: (distEnv, outputFile) => { 37 | const ret = { 38 | env: { 39 | start: { 40 | formats: [ 41 | 'css' 42 | ] 43 | } 44 | } 45 | }; 46 | 47 | ret.env[distEnv] = { 48 | actions: [ 49 | `extractCSS(${outputFile})` 50 | ] 51 | }; 52 | 53 | return ret; 54 | }, 55 | extractEntry: (distEnv, vendorsName, vendorsValue) => { 56 | const ret = { 57 | env: { 58 | actions: [ 59 | `generateCommonsChunk($(vendorsName))` 60 | ] 61 | } 62 | }; 63 | 64 | ret[distEnv] = { 65 | entry: { 66 | [vendorsName]: vendorsValue 67 | } 68 | }; 69 | 70 | return ret; 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /src/resolve_paths.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export default function resolvePaths(rootPath, paths) { 4 | const ret = {}; 5 | 6 | Object.keys(paths).forEach((k) => { 7 | const v = paths[k]; 8 | 9 | if (Array.isArray(v)) { 10 | ret[k] = v.map((p) => path.join(rootPath, p)); 11 | } else { 12 | ret[k] = path.join(rootPath, v); 13 | } 14 | }); 15 | 16 | return ret; 17 | } 18 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const assert = require('assert'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const lib = require('./lib'); 7 | 8 | const webpackrc = JSON.parse(fs.readFileSync('./.webpackrc', { 9 | encoding: 'utf-8' 10 | })); 11 | 12 | describe('Evaluate', function () { 13 | const result = lib.evaluate({ 14 | rootPath: __dirname, 15 | actions: lib.actions, 16 | formats: lib.formats, 17 | presets: lib.presets, 18 | webpackrc, 19 | target: 'dist' 20 | }, 21 | { 22 | foo: 'bar' 23 | }, 24 | { 25 | bar: 'baz' 26 | } 27 | ); 28 | 29 | it('should allow passing custom structure', function () { 30 | assert.equal(result.foo, 'bar'); 31 | }); 32 | 33 | it('should allow passing multiple custom structures', function () { 34 | assert.equal(result.bar, 'baz'); 35 | }); 36 | 37 | it('should select correct target', function () { 38 | assert.equal(result.output.filename, 'boilerplate.js'); 39 | }); 40 | 41 | const actionDefinition = { 42 | module: { 43 | preLoaders: [ 44 | { 45 | test: new RegExp('\.foo$'), 46 | loader: 'eslint' 47 | } 48 | ] 49 | } 50 | }; 51 | 52 | function actions() { 53 | return { 54 | lint: () => actionDefinition 55 | }; 56 | } 57 | 58 | it('should evaluate actions', function () { 59 | const res = lib.evaluate({ 60 | rootPath: __dirname, 61 | actions, 62 | webpackrc: { 63 | env: { 64 | dist: { 65 | actions: [ 66 | 'lint' 67 | ] 68 | } 69 | } 70 | }, 71 | target: 'dist' 72 | }); 73 | 74 | assert.deepEqual(res.module, actionDefinition.module); 75 | }); 76 | 77 | // it's the same logic for formats and presets too 78 | // XXX: there needs to be a better way to test through each 79 | it('should evaluate composed actions', function () { 80 | const actionDefinition2 = { 81 | module: { 82 | loaders: [ 83 | { 84 | test: new RegExp('\.js$'), 85 | loader: 'babel' 86 | } 87 | ] 88 | } 89 | }; 90 | 91 | function moreActions() { 92 | return { 93 | lintMore: () => actionDefinition2 94 | }; 95 | } 96 | 97 | const res = lib.evaluate({ 98 | rootPath: __dirname, 99 | actions: [actions, moreActions], 100 | webpackrc: { 101 | env: { 102 | dist: { 103 | actions: [ 104 | 'lint', 105 | 'lintMore' 106 | ] 107 | } 108 | } 109 | }, 110 | target: 'dist' 111 | }); 112 | 113 | assert.deepEqual(res.module.preLoaders, actionDefinition.module.preLoaders); 114 | assert.deepEqual(res.module.loaders, actionDefinition2.module.loaders); 115 | }); 116 | 117 | const formatDefinition = { 118 | resolve: { 119 | extensions: ['.png'] 120 | }, 121 | module: { 122 | loaders: [ 123 | { 124 | test: new RegExp('\.png$'), 125 | loader: 'url?limit=100000&mimetype=image/png' 126 | } 127 | ] 128 | } 129 | }; 130 | 131 | function formats() { 132 | return { 133 | png: () => formatDefinition 134 | }; 135 | } 136 | 137 | it('should evaluate formats', function () { 138 | const res = lib.evaluate({ 139 | rootPath: __dirname, 140 | formats, 141 | webpackrc: { 142 | env: { 143 | dist: { 144 | formats: [ 145 | 'png' 146 | ] 147 | } 148 | } 149 | }, 150 | target: 'dist' 151 | }); 152 | 153 | assert.deepEqual(res.resolve, { 154 | extensions: ['.png', ''] 155 | }); 156 | assert.deepEqual(res.module, formatDefinition.module); 157 | }); 158 | 159 | it('should evaluate presets', function () { 160 | const presetDefinition = { 161 | formats: ['png'], 162 | env: { 163 | start: { 164 | actions: [ 165 | 'lint' 166 | ] 167 | } 168 | } 169 | }; 170 | 171 | function presets() { 172 | return { 173 | demo: () => presetDefinition 174 | }; 175 | } 176 | 177 | const res = lib.evaluate({ 178 | rootPath: __dirname, 179 | actions, 180 | formats, 181 | presets, 182 | webpackrc: { 183 | presets: ['demo'] 184 | }, 185 | target: 'start' 186 | }); 187 | 188 | assert.deepEqual(res.resolve, { 189 | extensions: ['.png', ''] 190 | }); 191 | assert.deepEqual(res.module.preLoaders, actionDefinition.module.preLoaders); 192 | assert.deepEqual(res.module.loaders, formatDefinition.module.loaders); 193 | }); 194 | }); 195 | 196 | describe('Resolve paths', function () { 197 | const resolve = lib._resolvePaths; 198 | 199 | it('should resolve a simple path', function () { 200 | assert.deepEqual(resolve(__dirname, { 201 | foo: 'foo' 202 | }), { 203 | foo: path.join(__dirname, './foo') 204 | }); 205 | }); 206 | 207 | it('should resolve an array of paths', function () { 208 | assert.deepEqual(resolve(__dirname, { 209 | foo: ['foo', 'bar'] 210 | }), { 211 | foo: [ 212 | path.join(__dirname, './foo'), 213 | path.join(__dirname, './bar') 214 | ] 215 | }); 216 | }); 217 | 218 | it('should resolve multiple paths', function () { 219 | assert.deepEqual(resolve(__dirname, { 220 | foo: 'foo', 221 | bar: 'bar' 222 | }), { 223 | foo: path.join(__dirname, './foo'), 224 | bar: path.join(__dirname, './bar') 225 | }); 226 | }); 227 | }); 228 | 229 | describe('Parse', function () { 230 | const parse = lib._parse; 231 | 232 | it('should parse empty', function () { 233 | assert.deepEqual(parse.parseParameters(''), []); 234 | }); 235 | 236 | it('should parse single', function () { 237 | assert.deepEqual(parse.parseParameters('foo'), ['foo']); 238 | }); 239 | 240 | it('should parse multiple', function () { 241 | assert.deepEqual(parse.parseParameters('foo, bar'), ['foo', 'bar']); 242 | }); 243 | 244 | it('should parse arrays', function () { 245 | assert.deepEqual(parse.parseParameters('baz, [foo, bar]'), ['baz', ['foo', 'bar']]); 246 | }); 247 | 248 | // TODO: allow passing empty values? 249 | }); 250 | --------------------------------------------------------------------------------