├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── STYLE_GUIDE.md ├── docs ├── BLOCK-CREATION.md ├── MIGRATION-GUIDE.md └── TESTING.md ├── lerna.json ├── package.json ├── packages ├── assets │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── css-modules.test.js │ │ ├── css.test.js │ │ ├── file.test.js │ │ └── url.test.js │ ├── lib │ │ ├── css.js │ │ ├── file.js │ │ ├── index.js │ │ └── url.js │ └── package.json ├── babel │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── babel.test.js │ ├── index.js │ └── package.json ├── core │ ├── CHANGELOG.md │ ├── README.md │ ├── lib │ │ ├── __tests__ │ │ │ ├── .eslintrc.json │ │ │ ├── createConfig.test.js │ │ │ ├── env.test.js │ │ │ ├── group.test.js │ │ │ ├── match.test.js │ │ │ └── when.test.js │ │ ├── blockUtils.js │ │ ├── configSetters.js │ │ ├── createConfig.js │ │ ├── env.js │ │ ├── group.js │ │ ├── hooks.js │ │ ├── index.js │ │ ├── match.js │ │ └── when.js │ └── package.json ├── dev-server │ ├── CHANGELOG.md │ ├── README.md │ ├── index.js │ └── package.json ├── eslint │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── eslint.test.js │ ├── index.js │ └── package.json ├── extract-text │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── integration.test.js │ ├── index.js │ └── package.json ├── postcss │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── integration.test.js │ ├── index.js │ └── package.json ├── sample-app │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── webpack-config.test.js.md │ │ │ └── webpack-config.test.js.snap │ │ └── webpack-config.test.js │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── index.dev.js │ │ └── index.js │ ├── tsconfig.json │ └── webpack.config.babel.js ├── sass │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── integration.test.js │ ├── index.js │ └── package.json ├── tslint │ ├── CHANGELOG.md │ ├── README.md │ ├── index.js │ └── package.json ├── typescript │ ├── CHANGELOG.md │ ├── README.md │ ├── index.js │ └── package.json ├── uglify │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── uglify.test.js │ ├── index.js │ └── package.json ├── webpack-blocks │ ├── CHANGELOG.md │ ├── README.md │ ├── index.js │ └── package.json └── webpack │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── __e2e-fixtures__ │ ├── babel-postcss-extract-text │ │ ├── app.js │ │ ├── styles.css │ │ └── webpack.config.js │ ├── minimal │ │ ├── app.js │ │ └── webpack.config.js │ ├── postcss-sass-sourcemaps │ │ ├── styles.sass │ │ └── webpack.config.js │ ├── sass-css-modules │ │ ├── src │ │ │ ├── index.js │ │ │ └── styles.scss │ │ └── webpack.config.js │ ├── sass-extract-text │ │ ├── app.js │ │ ├── styles.sass │ │ └── webpack.config.js │ └── typescript │ │ ├── app.ts │ │ ├── tsconfig.json │ │ ├── tslint.json │ │ └── webpack.config.js │ ├── __tests__ │ ├── .eslintrc.json │ ├── devServer.integration.test.js │ ├── end-to-end.test.js │ ├── entryPoint.test.js │ └── integration.test.js │ ├── index.js │ ├── lib │ ├── __tests__ │ │ ├── defineConstants.test.js │ │ ├── parseVersion.test.js │ │ └── setEnv.test.js │ ├── defineConstants.js │ ├── parseVersion.js │ └── setEnv.js │ └── package.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/**/build/ 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended"], 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-useless-escape": "error" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/**/build/ 3 | package-lock.json 4 | *.log 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/**/build/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 12 7 | - 10 8 | 9 | cache: 10 | yarn: true 11 | directories: 12 | - ".eslintcache" 13 | - "node_modules" 14 | 15 | before_install: 16 | - curl -o- -L https://yarnpkg.com/install.sh | bash 17 | - export PATH="$HOME/.yarn/bin:$PATH" 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Setup 4 | 5 | Fork and clone this repository, then run 6 | 7 | ```bash 8 | yarn install 9 | ``` 10 | 11 | Run tests: 12 | 13 | ```bash 14 | yarn test 15 | ``` 16 | 17 | ## Code 18 | 19 | This repository is a monorepo which means it contains multiple NPM packages. We use 20 | [Yarn workspaces](https://yarnpkg.com/en/docs/workspaces) architecture to install dependencies and 21 | link local packages between each other, and [`lerna`](https://github.com/lerna/lerna) to run 22 | commands for one or more packages from the root directory. 23 | 24 | Running `yarn install` in the repository's root directory will install the dependencies of all 25 | packages and link the packages that depend on each other. 26 | 27 | Running `yarn test` will run all packages' tests. Run `yarn test` inside a package directory to test 28 | this package only. 29 | 30 | Just edit the code like you always do. Be aware that we automatically lint and format code with 31 | [eslint](https://eslint.org/) and [prettier](https://prettier.io/). 32 | 33 | ## Share 34 | 35 | 1. Fork the repository. 36 | 2. Create a new branch from `master`. 37 | 3. Do something awesome. 38 | 4. Add / update tests. 39 | 5. Update changelogs of all packages that you change. Use _Next release_ as a version number. 40 | 6. Push them and open a pull request. 41 | 42 | ## More Information 43 | 44 | - [How to Test Blocks](docs/TESTING.md) 45 | - [How to Write a Webpack Block](docs/BLOCK-CREATION.md) 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andy Wermke 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks 2 | 3 | [![Build Status](https://travis-ci.org/andywer/webpack-blocks.svg?branch=master)](https://travis-ci.org/andywer/webpack-blocks) 4 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 5 | 6 | Functional building blocks for your webpack config: easier way to configure webpack and to share 7 | configuration between projects. 8 | 9 | Ready to use blocks to configure popular tools like _Babel_, _PostCSS_, _Sass_, _TypeScript_, etc., 10 | as well as best practices like extracting CSS — all with just one line of configuration. 11 | 12 | Note: This is the documentation of webpack-blocks v2, compatible with webpack 4. Check out the 13 | [`v1` branch](https://github.com/andywer/webpack-blocks/tree/v1) if you need to be compatible with 14 | webpack 3 or older. 15 | 16 | > "Finally, webpack config done right. (...) Webpack clearly wants to stay low-level. So it makes 17 | > total sense to outsource configuring it to well designed blocks instead of copy-paste." 18 | > 19 | > [Dan Abramov](https://github.com/gaearon) via 20 | > [twitter](https://twitter.com/dan_abramov/status/806249934399881216) (Co-author of Redux, Create 21 | > React App and React Hot Loader) 22 | 23 | ## Table of contents 24 | 25 | 26 | 27 | 28 | 29 | - [Installation](#installation) 30 | - [Example](#example) 31 | - [More examples](#more-examples) 32 | - [Custom blocks](#custom-blocks) 33 | - [Available webpack blocks](#available-webpack-blocks) 34 | - [Helpers](#helpers) 35 | - [Shorthand setters](#shorthand-setters) 36 | - [Third-party blocks](#third-party-blocks) 37 | - [Design principles](#design-principles) 38 | - [FAQ](#faq) 39 | - [Like what you see?](#like-what-you-see) 40 | - [Contributors](#contributors) 41 | - [License](#license) 42 | 43 | 44 | 45 | ## Installation 46 | 47 | ```sh 48 | npm install --save-dev webpack webpack-blocks 49 | # or 50 | yarn add --dev webpack webpack-blocks 51 | ``` 52 | 53 | ## Example 54 | 55 | The following sample shows how to create a webpack config with Babel support, dev server and 56 | Autoprefixer. 57 | 58 | 59 | ```js 60 | const webpack = require('webpack') 61 | const { 62 | createConfig, 63 | match, 64 | 65 | // Feature blocks 66 | babel, 67 | css, 68 | devServer, 69 | file, 70 | postcss, 71 | uglify, 72 | 73 | // Shorthand setters 74 | addPlugins, 75 | setEnv, 76 | entryPoint, 77 | env, 78 | setOutput, 79 | sourceMaps 80 | } = require('webpack-blocks') 81 | const autoprefixer = require('autoprefixer') 82 | const path = require('path') 83 | 84 | module.exports = createConfig([ 85 | entryPoint('./src/main.js'), 86 | setOutput('./build/bundle.js'), 87 | babel(), 88 | match(['*.css', '!*node_modules*'], [ 89 | css(), 90 | postcss({ 91 | plugins: [ 92 | autoprefixer({ browsers: ['last 2 versions'] }) 93 | ] 94 | }) 95 | ]), 96 | match(['*.gif', '*.jpg', '*.jpeg', '*.png', '*.webp'], [ 97 | file() 98 | ]), 99 | setEnv({ 100 | NODE_ENV: process.env.NODE_ENV 101 | }), 102 | env('development', [ 103 | devServer(), 104 | devServer.proxy({ 105 | '/api': { target: 'http://localhost:3000' } 106 | }), 107 | sourceMaps() 108 | ]), 109 | env('production', [ 110 | uglify(), 111 | addPlugins([new webpack.LoaderOptionsPlugin({ minimize: true })]) 112 | ]) 113 | ]) 114 | ``` 115 | 116 | 117 | See shorthand setters and helpers [documentation](packages/webpack#exports). 118 | 119 | All blocks, like `babel` or `postcss` are also available as their own [small packages](./packages), 120 | `webpack-blocks` package wraps these blocks, shorthand setters and helpers as a single dependency 121 | for convenience. 122 | 123 | ## More examples 124 | 125 | CSS modules: 126 | 127 | ```js 128 | const { createConfig, match, css } = require('webpack-blocks') 129 | 130 | // ... 131 | 132 | module.exports = createConfig([ 133 | // ... 134 | match(['*.css', '!*node_modules*'], [ 135 | css.modules() 136 | ] 137 | ]) 138 | ``` 139 | 140 | TypeScript: 141 | 142 | ```js 143 | const { createConfig } = require('webpack-blocks') 144 | const typescript = require('@webpack-blocks/typescript') 145 | 146 | // ... 147 | 148 | module.exports = createConfig([ 149 | // ... 150 | typescript() 151 | ]) 152 | ``` 153 | 154 | ## Custom blocks 155 | 156 | Need a custom block? A simple block looks like this: 157 | 158 | ```js 159 | module.exports = createConfig([ 160 | // ... 161 | myCssLoader(['./styles']) 162 | ]) 163 | 164 | function myCssLoader() { 165 | return (context, { merge }) => 166 | merge({ 167 | module: { 168 | rules: [ 169 | Object.assign( 170 | { 171 | test: /\.css$/, 172 | use: ['style-loader', 'my-css-loader'] 173 | }, 174 | context.match // carries `test`, `exclude` & `include` as set by `match()` 175 | ) 176 | ] 177 | } 178 | }) 179 | } 180 | ``` 181 | 182 | If we use `myCssLoader` in `match()` then `context.match` will be populated with whatever we set in 183 | `match()`. Otherwise there is still the `test: /\.css$/` fallback, so our block will work without 184 | `match()` as well. 185 | 186 | Check out the [sample app](./packages/sample-app) to see a webpack config in action or read 187 | [how to create your own blocks](./docs/BLOCK-CREATION.md). 188 | 189 | ## Available webpack blocks 190 | 191 | - [assets](./packages/assets) 192 | - [babel](./packages/babel) 193 | - [dev-server](./packages/dev-server) 194 | - [elm](https://github.com/webpack-blocks/elm) 195 | - [eslint](./packages/eslint) 196 | - [extract-text](./packages/extract-text) 197 | - [postcss](./packages/postcss) 198 | - [sass](./packages/sass) 199 | - [tslint](./packages/tslint) 200 | - [typescript](./packages/typescript) 201 | - [uglify](./packages/uglify) 202 | 203 | ## [Helpers](./packages/webpack#helpers) 204 | 205 | Helpers allow you to structure your config and define settings for particular environments (like 206 | `production` or `development`) or file types. 207 | 208 | - group 209 | - env 210 | - match 211 | - when 212 | 213 | ## [Shorthand setters](./packages/webpack#shorthand-setters) 214 | 215 | Shorthand setters gives you easier access to common webpack settings, like plugins, entry points and 216 | source maps. 217 | 218 | - addPlugins 219 | - customConfig 220 | - defineConstants 221 | - entryPoint 222 | - performance 223 | - resolve 224 | - setContext 225 | - setDevTool 226 | - setEnv 227 | - setOutput 228 | - sourceMaps 229 | 230 | ## Third-party blocks 231 | 232 | - [webpack-blocks-happypack](https://github.com/diegohaz/webpack-blocks-happypack) — HappyPack 233 | - [webpack-blocks-less](https://github.com/kirill-konshin/webpack-blocks-less) — Less 234 | - [webpack-blocks-purescript](https://github.com/ecliptic/webpack-blocks-purescript) — PureScript 235 | - [webpack-blocks-server-source-map](https://github.com/diegohaz/webpack-blocks-server-source-map) — 236 | source map for server bundle 237 | - [webpack-blocks-split-vendor](https://github.com/diegohaz/webpack-blocks-split-vendor) — vendor 238 | bundle 239 | - [webpack-blocks-ts](https://github.com/foxbunny/webpack-blocks-ts) — TypeScript using ts-loader 240 | instead of awesome-typescript-loader 241 | - [webpack-blocks-vue](https://github.com/foxbunny/webpack-blocks-vue) — Vue 242 | 243 | Missing something? Write and publish your own webpack blocks! 244 | 245 | ## Design principles 246 | 247 | - Extensibility first 248 | - Uniformity for easy composition 249 | - Keep everything configurable 250 | - But provide sane defaults 251 | 252 | ## FAQ 253 | 254 |
255 | How to debug? 256 | 257 | In case the webpack configuration does not work as expected you can debug it using 258 | [q-i](https://www.npmjs.com/package/q-i): 259 | 260 | ```js 261 | const { print } = require('q-i') 262 | 263 | module.exports = createConfig([ 264 | // ... 265 | ]) 266 | 267 | print(module.exports) 268 | ``` 269 | 270 |
271 | 272 |
273 | How does env() work? 274 | 275 | `env('development', [ ... ])` checks the `NODE_ENV` environment variable and only applies its 276 | contained webpack blocks if it matches the given string. 277 | 278 | So make sure you set the NODE_ENV accordingly: 279 | 280 | ```js 281 | // your package.json 282 | "scripts": { 283 | "build": "cross-env NODE_ENV=production webpack", 284 | "start": "cross-env NODE_ENV=development webpack-dev-server" 285 | } 286 | ``` 287 | 288 | If there is no NODE_ENV set then it will treat NODE_ENV as if it was `development`. Use 289 | [cross-env](https://github.com/kentcdodds/cross-env) to make it work on all platforms. 290 | 291 |
292 | 293 |
294 | What does defineConstants() do? 295 | 296 | `defineConstants()` is a small convenience wrapper around webpack's 297 | [DefinePlugin](https://webpack.github.io/docs/list-of-plugins.html#defineplugin). It is composable 298 | and automatically encodes the values. Use it to replace constants in your code by their values at 299 | build time. 300 | 301 | So having a `defineConstants({ 'process.env.FOO': 'foo' })` and a 302 | `defineConstants({ 'process.env.BAR': 'bar' })` in your config means the resulting webpack config 303 | will contain a single 304 | `new webpack.DefinePlugin({ 'process.env.FOO': '"FOO"', 'process.env.BAR': '"BAR"' })`, thus 305 | replacing any occurrence of `process.env.FOO` and `process.env.BAR` with the given values. 306 | 307 | You can also use [setEnv](./packages/webpack#setenvconstants-stringobject-function) method to define 308 | `process.env.*` variables, it’s based on 309 | [webpack.EnvironmentPlugin](https://webpack.js.org/plugins/environment-plugin/): 310 | `setEnv({ FOO: 'foo' })`. 311 | 312 |
313 | 314 |
315 | What does a block look like from the inside? 316 | 317 | A webpack block is _a function and requires no dependencies at all_ (🎉🎉), thus making it easy to 318 | write your own blocks and share them with your team or the community. 319 | 320 | Take the `babel` webpack block for instance: 321 | 322 | ```js 323 | /** 324 | * @param {object} [options] 325 | * @param {RegExp|Function|string} [options.exclude] Directories to exclude. 326 | * @return {Function} 327 | */ 328 | function babel(options = { cacheDirectory: true }) { 329 | return (context, util) => 330 | util.addLoader( 331 | Object.assign( 332 | { 333 | // we use a `MIME type => RegExp` abstraction here in order to have consistent regexs 334 | test: /\.(js|jsx)$/, 335 | exclude: /node_modules/, 336 | use: [{ loader: 'babel-loader', options }] 337 | }, 338 | context.match 339 | ) 340 | ) 341 | } 342 | ``` 343 | 344 | Add a README and a package.json and you are ready to ship. 345 | 346 | For more details see [How to write a block](./docs/BLOCK-CREATION.md). 347 | 348 |
349 | 350 |
351 | I need some custom webpack config snippet! 352 | 353 | No problem. If you don't want to write your own webpack block you can use `customConfig()`: 354 | 355 | ```js 356 | const path = require('path') 357 | const HtmlWebpackPlugin = require('html-webpack-plugin') 358 | const { addPlugins, customConfig } = require('@webpack-blocks/webpack') 359 | 360 | // ... 361 | 362 | module.exports = createConfig([ 363 | // ... 364 | addPlugins([ 365 | // Add a custom webpack plugin 366 | new HtmlWebpackPlugin({ 367 | inject: true, 368 | template: './index.html' 369 | }) 370 | ]), 371 | customConfig({ 372 | // Add some custom webpack config snippet 373 | resolve: { 374 | extensions: ['.js', '.es6'] 375 | } 376 | }) 377 | ]) 378 | ``` 379 | 380 | The object you pass to `customConfig()` will be merged into the webpack config using 381 | [webpack-merge](https://github.com/survivejs/webpack-merge) like any other webpack block's partial 382 | config. 383 | 384 |
385 | 386 |
387 | How to compose blocks? 388 | 389 | Got some projects with similar, yet not identical webpack configurations? Create a “preset”, a 390 | function that returns a `group` of blocks so you can reuse it in multiple projects: 391 | 392 | ```js 393 | const { createConfig, env, group, babel, devServer } = require('webpack-blocks') 394 | 395 | function myPreset(proxyConfig) { 396 | return group([babel(), env('development', [devServer(), devServer.proxy(proxyConfig)])]) 397 | } 398 | 399 | module.exports = createConfig([ 400 | myPreset({ 401 | '/api': { target: 'http://localhost:3000' } 402 | }) 403 | // add more blocks here 404 | ]) 405 | ``` 406 | 407 | The key feature is the `group()` method which takes a set of blocks and returns a new block that 408 | combines all their functionality. 409 | 410 |
411 | 412 | ## Like what you see? 413 | 414 | Support webpack-blocks by giving [feedback](https://github.com/andywer/webpack-blocks/issues), 415 | [contributing to this repository](https://github.com/andywer/webpack-blocks/pulls), publishing new 416 | webpack blocks or just by 🌟 starring the project! 417 | 418 | ## Contributors 419 | 420 | [These awesome people](https://github.com/andywer/webpack-blocks/graphs/contributors) have helped 421 | webpack-blocks by adding features, fixing bugs and refactoring code. You can become one of them! 422 | 423 | ## License 424 | 425 | MIT 426 | -------------------------------------------------------------------------------- /STYLE_GUIDE.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks style guide 2 | 3 | ## Names 4 | 5 | - _webpack-blocks_, always in lower-case letters, even at the beginning of a sentence. 6 | - Do not use _webpack blocks_ to refer to the project. 7 | - Use `webpack-blocks` (in backticks) to refer to the `webpack-blocks` package. 8 | - Use _webpack block_ or just _block_ to refer to a particular block, like `babel` or `postcss`. 9 | - Write block names in backticks: `typescript`, `uglify`. 10 | - Write _webpack_ lower-case letters, unless it starts a sentence. 11 | 12 | ## Casing 13 | 14 | - Use sentence casing everywhere, even in titles. 15 | -------------------------------------------------------------------------------- /docs/BLOCK-CREATION.md: -------------------------------------------------------------------------------- 1 | # How to Write a Webpack Block 2 | 3 | This guide is going to show you how to create a custom webpack block. Don't worry, it's not hard! 4 | 5 | Skip the _Hooks_ section if you are in a hurry. You can create a lot of cool stuff without using 6 | them. 7 | 8 | Read the _Basics_. You can come back and read more if you are stuck or wondering why it works this 9 | way. 10 | 11 | ## Table of Contents 12 | 13 | 14 | 15 | 16 | 17 | - [Basics](#basics) 18 | - [Block utilities](#block-utilities) 19 | - [Context](#context) 20 | - [Hooks](#hooks) 21 | - [Testing](#testing) 22 | - [Publishing](#publishing) 23 | 24 | 25 | 26 | ## Basics 27 | 28 | A webpack block is _just a function_ that returns an update function and _requires no dependencies 29 | at all_. The update function takes a webpack configuration object and returns an updated version of 30 | this webpack configuration. 31 | 32 | Take the `babel` webpack block for instance: 33 | 34 | ```js 35 | /** 36 | * @param {object} [options] 37 | * @param {RegExp|Function|string} [options.exclude] Directories to exclude. 38 | * @return {Function} 39 | */ 40 | function babel(options = { cacheDirectory: true }) { 41 | return (context, util) => prevConfig => ({ 42 | ...prevConfig, 43 | module: { 44 | ...prevConfig.module, 45 | rules: prevConfig.module.rules.concat([ 46 | Object.assign( 47 | { 48 | // we use a `MIME type => RegExp` abstraction here in order to have consistent regexs 49 | // setting `test` & `exclude` defaults here, in case there is no `context.match` data 50 | test: /\.(js|jsx)$/, 51 | exclude: /node_modules/, 52 | use: [{ loader: 'babel-loader', options }] 53 | }, 54 | context.match // carries `test`, `exclude` & `include` as set by `match()` 55 | ) 56 | ]) 57 | } 58 | }) 59 | } 60 | ``` 61 | 62 | Thus it is also pretty easy to unit test and generic enough to share it with the world. 63 | 64 | ## Block utilities 65 | 66 | You might have recognized the second paramter `util` in the last example's block function. It is an 67 | object containing some convenience functions to make common tasks easier. 68 | 69 | #### util.addLoader(loaderDefinition: object) 70 | 71 | Returns an update function that adds the given loader definition to a webpack configuration. Use it 72 | like this: 73 | 74 | ```js 75 | function sampleBlock() { 76 | return (context, { addLoader }) => 77 | addLoader( 78 | Object.assign( 79 | { 80 | test: /\.css$/, 81 | use: ['style-loader', 'css-loader'] 82 | }, 83 | context.match 84 | ) 85 | ) 86 | } 87 | ``` 88 | 89 | #### util.addPlugin(plugin: WebpackPlugin) 90 | 91 | Returns an update function that adds the given plugin instance to a webpack configuration. Use it 92 | like this: 93 | 94 | ```js 95 | function sampleBlock() { 96 | return (context, { addPlugin }) => { 97 | return addPlugin(new webpack.DefinePlugin({ DEBUG: 'true' })) 98 | } 99 | } 100 | ``` 101 | 102 | #### util.merge(configSnippet: object) 103 | 104 | Returns an update function that merges the given configuration snippet into a webpack configuration. 105 | Use it like this: 106 | 107 | ```js 108 | function sampleBlock() { 109 | return (context, { merge }) => 110 | merge({ 111 | resolve: { 112 | extensions: ['ts'] 113 | } 114 | }) 115 | } 116 | ``` 117 | 118 | ## Context 119 | 120 | The context object is a metadata object that is passed to every block. It is meant to contain any 121 | kind of data that is needed for webpack config creation, but not part of the webpack config itself. 122 | 123 | Initially it will contain a `webpack` instance. If you are using [hooks](#hooks) you might want to 124 | put custom metadata into the context and use it in the `post` hook. 125 | 126 | ### context.match 127 | 128 | This is where the file matching patterns reside you set by using `match()` in the configuration. 129 | 130 | If the block is used within a `match()` then `context.match` will contain: 131 | 132 | - `test`: Which files to match, usually a regex or an array of regexs. Always present. 133 | - `exclude`: Condition(s) which paths not to match. Might not be present. 134 | - `include`: Condition(s) to override `exclude`. Might not be present. 135 | 136 | If the block is not used within a `match()` then `context.match` will be undefined. 137 | 138 | ### context.webpack 139 | 140 | The `context.webpack` property is the webpack instance, so you do not have to `require('webpack')` 141 | in your blocks. 142 | 143 | ## Hooks 144 | 145 | Sometimes the simple _a block is a function_ approach is not enough. Let's take the following 146 | example: 147 | 148 | You want to define constants using the `webpack.DefinePlugin`. The DefinePlugin will run over your 149 | source files, look for occurences of the constants you defined and replace them by the value you 150 | set. 151 | 152 | Sounds good. But wait... What if multiple blocks want to define constants using the DefinePlugin 153 | independently? You can write a tiny block `defineConstants` which takes the constants and creates a 154 | webpack snippet containing a configured DefinePlugin. 155 | 156 | The problem is that using this block multiple times will result in multiple instances of the 157 | DefinePlugin! This is not what we want. We would like to define constants multiple times, store them 158 | somewhere and eventually add a single DefinePlugin instance for all those constants to the webpack 159 | config. _And this is where the hooks enter the stage._ 160 | 161 | ### So what is it? 162 | 163 | Every block may have one or multiple `pre` hooks and one or multiple `post` hooks. You can set them 164 | like that: 165 | 166 | ```js 167 | function myBlock() { 168 | const blockFunction = (context, config) => ({ 169 | /* webpack config snippet */ 170 | }) 171 | 172 | return Object.assign(blockFunction, { 173 | pre: preHook, 174 | post: postHook 175 | }) 176 | } 177 | 178 | function preHook(context) { 179 | // Manipulate the `context` here (register a new file type, for example) 180 | } 181 | 182 | function postHook(context, util) { 183 | return config => config // Return your update function here 184 | } 185 | ``` 186 | 187 | Both `pre` and `post` hooks are optional and can either be a function or an array of functions. 188 | 189 | Let's solve our constants definition example. We can now make the `blockFunction` store the 190 | constants in the `context` and make the `postHook` instantiate the DefinePlugin: 191 | 192 | ```js 193 | function defineConstants(constants) { 194 | return Object.assign( 195 | context => { 196 | context.defineConstants = Object.assign({}, context.defineConstants, constants) 197 | return config => config // Don't change the config here yet 198 | }, 199 | { post } 200 | ) 201 | } 202 | 203 | function post(context, { addPlugin }) { 204 | return addPlugin(new webpack.DefinePlugin(context.defineConstants)) 205 | } 206 | ``` 207 | 208 | `createConfig()` will _deduplicate all hooks_, so our post hook is run once only and we end up 209 | having exactly one DefinePlugin instance containing all constant definitions. 210 | 211 | In case you wonder, the `return Object.assign(blockFunction, { pre, post })` syntax is just 212 | syntactic sugar and semantically the same as 213 | `blockFunction.pre = pre; blockFunction.post = post; return blockFunction`. 214 | 215 | ### Lifecycle 216 | 217 | So this is what happens under the hood when `createConfig()` processes the blocks: 218 | 219 | - Run `pre` hooks (of all blocks) 220 | - Run the actual block logic (of all blocks) and merge the config snippets 221 | - Run `post` hooks (of all blocks) and merge the config snippets 222 | 223 | That's it. The resulting merged config object is createConfig's return value. 224 | 225 | ### Best practices and pitfalls 226 | 227 | #### Static pre/post functions 228 | 229 | `createConfig()` filters duplicate hooks, so you can call a block multiple times, but each of its 230 | hooks is only run once. 231 | 232 | To make the deduplication work you have to make sure your hook functions are declared as static 233 | top-level functions as shown in the example. _Do not declare the hook functions inside your block 234 | function_, since this would mean that those hook functions are re-created on every call of the block 235 | and cannot be deduplicated. 236 | 237 | #### Use the context for metadata 238 | 239 | If you have got metadata that you need in a later lifecycle stage, but that is not part of the 240 | actual webpack config object then please store it in the `context` object. 241 | 242 | That is what the context is for and making that metadata part of the webpack snippet you return is 243 | really bad practice, especially since webpack 2 is going to validate the webpack config against a 244 | fixed schema. 245 | 246 | ## Testing 247 | 248 | You definitely want to test your blocks, so you don't ship broken stuff, right? 249 | 250 | Have a look at the [testing docs](./TESTING.md). 251 | 252 | ## Publishing 253 | 254 | So you have written your first webpack block. Now what to do with it? 255 | 256 | 1. Write a small README 257 | 2. Add a `package.json` (add dependencies here, like loaders/plugins your block configures) 258 | 3. Open a pull request on webpack-blocks or `yarn publish` your block 259 | 260 | If you aren't sure whether your block should become part of the official webpack-blocks repository 261 | feel free to just open an issue and ask us! 262 | 263 | Have fun. 264 | -------------------------------------------------------------------------------- /docs/MIGRATION-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Migrating to webpack-blocks v1.0 2 | 3 | This is the migration guide for upgrading from webpack-blocks v0.4 to v1.0. It is a major update 4 | with a couple of breaking changes. 5 | 6 | The first section covers the changes you have to make to your webpack-blocks configuration. The 7 | second section covers additional new features that you can use. The last section covers how to 8 | upgrade your custom blocks. 9 | 10 | ## Table of Contents 11 | 12 | 13 | 14 | 15 | 16 | - [Migrating configuration](#migrating-configuration) 17 | - [Additional features](#additional-features) 18 | - [Migrating custom blocks](#migrating-custom-blocks) 19 | 20 | 21 | 22 | ## Migrating configuration 23 | 24 | ### `match()` 25 | 26 | You can now use `match()` to specify on which files to apply certain loaders. Works with every block 27 | that adds a loader, like `babel`, `css`, `elm`, `postcss`, `sass`. 28 | 29 | The blocks still work without `match()`. **However, blocks dropped the `fileType`, `exclude` & 30 | `include` from their options. Use `match()` for that.** 31 | 32 | ```js 33 | const { createConfig, css, file, match, postcss, url } = require('webpack-blocks') 34 | const path = require('path') 35 | 36 | module.exports = createConfig([ 37 | // Matches *.js and *.jsx outside node_modules/ by default 38 | babel(), 39 | 40 | match(['*.css', '!*node_modules*'], [css(), postcss()]), 41 | 42 | match(['*.gif', '*.jpg', '*.jpeg', '*.png', '*.svg', '*.webp'], [file()]) 43 | ]) 44 | ``` 45 | 46 | ### `@webpack-blocks/assets` package 47 | 48 | The `assets` package contains the `css`, `css.modules`, `file` and `url` blocks which were contained 49 | in `@webpack-blocks/webpack` in v0.4. Use these blocks to configure a `css-loader`, `file-loader` or 50 | `url-loader`. 51 | 52 | See the package's [README](../packages/assets/README.md) for further details. 53 | 54 | ### `webpack-blocks` package 55 | 56 | The new `webpack-blocks` package can be installed using `npm install --save-dev webpack-blocks`. It 57 | is a convenience package, wrapping and exporting most of frequently used blocks, so you don't have 58 | to manage all those small package dependencies by yourself. Contains: 59 | 60 | - assets 61 | - babel 62 | - dev-server 63 | - extract-text 64 | - postcss 65 | - sass 66 | - typescript 67 | - uglify 68 | - webpack 69 | 70 | It also exports all the `@webpack-blocks/webpack` utility functions. See the package's 71 | [README](../packages/webpack-blocks/README.md) for further details. 72 | 73 | ### No more webpack v1 74 | 75 | Support for webpack 1.x has been dropped. If you have not upgraded yet we recommend doing so soon. 76 | 77 | ### webpack v3 is the default 78 | 79 | Most blocks should still work with webpack v2, but the latest `@webpack-blocks/extract-text` 80 | requires webpack v3. 81 | 82 | To use it with webpack v2, install the older beta version. See the package's 83 | [README](../packages/extract-text/README.md) for further details. 84 | 85 | ### webpack as a peer dependency 86 | 87 | Webpack is now a peer dependency of `@webpack-blocks/webpack`. Make sure to explicitly 88 | `npm install --save-dev webpack`. 89 | 90 | ### babel6 is now babel 91 | 92 | The `@webpack-blocks/babel6` package has been renamed to `@webpack-blocks/babel`. You can now choose 93 | which version of babel to use, since `babel-core` is now a peer dependency (see below). 94 | 95 | ### babel-core as a peer dependency 96 | 97 | `babel-core` is now a peer dependency of the `babel` block, so you can control which version of 98 | `babel-core` to use. Make sure to `npm install --save-dev babel-core` in your application. 99 | 100 | ### `webpack` no longer exported 101 | 102 | `webpack` is no longer exported by `@webpack-blocks/webpack`. Use 103 | `const webpack = require('webpack')` instead which is more explicit. 104 | 105 | ### `resolve()` instead of `resolveAliases()` 106 | 107 | The `resolveAliases` utility in `@webpack-blocks/webpack` has been replaced by the more generic 108 | `resolve`. 109 | 110 | ```diff 111 | module.exports = createConfig([ 112 | - resolveAliases({ package: path.resolve('./src/package') }) 113 | + resolve({ 114 | + alias: { package: path.resolve('./src/package') } 115 | + }) 116 | ]) 117 | ``` 118 | 119 | ### Removed `devServer.reactHot()` 120 | 121 | The `devServer.reactHot()` block has never worked too well. We also decided not to ship 122 | framework-specific blocks as core features. 123 | 124 | If you feel like this is a feature you really want to have, create a new fancy React hot loader 125 | block and share it with the world! 126 | 127 | ### Requiring Node.js 6+ now 128 | 129 | Please use node 6 or newer. We don't transpile the webpack-blocks code for legacy node versions 130 | anymore. 131 | 132 | --- 133 | 134 | ## Additional features 135 | 136 | ### `@webpack-blocks/uglify` package 137 | 138 | The `uglify` package is a convenience block for easily configuring JS minification using the 139 | [uglifyjs-webpack-plugin](https://www.npmjs.com/package/uglifyjs-webpack-plugin) which also supports 140 | minifying untranspiled ES2015 code. 141 | 142 | See the package's [README](../packages/uglify/README.md) for further details. 143 | 144 | ### `setEnv()` 145 | 146 | New utility block in `@webpack-blocks/webpack`. Replaces constants in your source code with a values 147 | from `process.env` using the 148 | [webpack.EnvironmentPlugin](https://webpack.js.org/plugins/environment-plugin/). 149 | 150 | Using `setEnv` multiple times results in a single EnvironmentPlugin instance configured to do all 151 | the replacements. 152 | 153 | ```js 154 | module.exports = createConfig([ 155 | setEnv(['NODE_ENV']), 156 | setEnv({ 157 | BABEL_ENV: 'development', // use 'development' unless process.env.BABEL_ENV is defined 158 | PORT: 3000 159 | }) 160 | ]) 161 | ``` 162 | 163 | ### `resolve()` will prepend custom `extensions` (instead of appending them) 164 | 165 | The new `resolve` utility in `@webpack-blocks/webpack` will prepend custom `extensions` you pass to 166 | it, rather than appending them. 167 | 168 | This way extensions you set later in your `createConfig()` will have precedence over extensions you 169 | set earlier (webpack tries to match against the `extensions` from the first to last). 170 | 171 | --- 172 | 173 | ## Migrating custom blocks 174 | 175 | ### A concise migration example 176 | 177 | A simple Babel block written for the old v0.4 API would look like this: 178 | 179 | ```js 180 | function babel() { 181 | return context => ({ 182 | module: { 183 | loaders: [ 184 | { 185 | test: context.fileType('application/javascript'), 186 | loaders: ['babel-loader'] 187 | } 188 | ] 189 | } 190 | }) 191 | } 192 | ``` 193 | 194 | The same block updated for the v1.0 API: 195 | 196 | ```js 197 | function babel() { 198 | return (context, { addLoader }) => 199 | addLoader( 200 | Object.assign( 201 | { 202 | test: /\.(js|jsx)$/, 203 | use: ['babel-loader'] 204 | }, 205 | context.match 206 | ) 207 | ) 208 | } 209 | ``` 210 | 211 | ### New API (#125) 212 | 213 | In webpack-blocks v1.0 a block is now expected to have the signature 214 | 215 | ```diff 216 | - (context) => configDiff 217 | + (context, utils) => prevConfig => updatedConfig 218 | ``` 219 | 220 | This way the block has more freedom about how to apply changes to the configuration. To keep writing 221 | custom blocks simple, the block will receive a `utils` argument. It contains: 222 | 223 | - `utils.addLoader(loaderDefinition: object)` 224 | - `utils.addPlugin(plugin: WebpackPlugin)` 225 | - `utils.merge(configSnippet: object)` 226 | 227 | These utility functions return an updater function `prevConfig => updatedConfig`. For details see 228 | [How to Write a Webpack Block](./BLOCK-CREATION.md). 229 | 230 | ### `context.match` 231 | 232 | A small change in the block is required to support the new `match()`. Under the hood `match()` will 233 | set `context.match` to an object 234 | `{ test: RegExp, include: string|RegExp|string[]|RegExp[], exclude: string|RegExp|string[]|RegExp[] }`. 235 | `test` is always set, `include` and `exclude` are optional. 236 | 237 | The properties of `context.match` match the properties you would pass to a webpack loader to define 238 | the files the loader should be applied to. This way you can just merge `context.match` into your 239 | webpack loader configuration. 240 | 241 | ### `context.fileType` is deprecated 242 | 243 | The `context.fileType()` function is now deprecated. Just use `context.match.test` and provide a 244 | hard-coded RegExp as a default. 245 | 246 | ### See "How to Write a Webpack Block" (`/docs/BLOCK-CREATION.md`) 247 | 248 | For more details you can check out [How to Write a Webpack Block](./BLOCK-CREATION.md). 249 | 250 | ### Added "How to test blocks" (`docs/TESTING.md`) 251 | 252 | There is now a testing best-practices guide as well! See [How to test blocks](./TESTING.md). 253 | -------------------------------------------------------------------------------- /docs/TESTING.md: -------------------------------------------------------------------------------- 1 | # How to test blocks 2 | 3 | Short summary: prefer integration tests instead of unit tests. End-to-end tests are even more 4 | convincing, but require more effort. So focus on integration tests and maybe add some end-to-end 5 | tests for the most critical bits. 6 | 7 | ## Unit tests 8 | 9 | Let's start with unit tests. You may find the following flow chart useful: 10 | 11 | ``` 12 | Is the block just a helper that sets config, but adds no loaders or plugins? 13 | | 14 | +-------------------+-------------------+ 15 | | | 16 | yes no 17 | | | 18 | \/ \/ 19 | Ok, go for the unit test Nope, keep reading 20 | ``` 21 | 22 | It is often a good idea to skip writing unit tests for blocks and focus on integration tests 23 | instead. The moment you need to rewrite your unit test because you moved some block logic into a 24 | post hook you will know why you should have written an integration test instead. 25 | 26 | ## Integration tests 27 | 28 | In this context integration tests are tests that actually run `createConfig()` on the block and 29 | possibly other blocks to check if the created configuration matches the expected output. 30 | 31 | It is also quite simple to write one. Here is a short example: 32 | 33 | 34 | ```js 35 | import test from 'ava' 36 | import { createConfig, match } from 'webpack-blocks' 37 | import myBlock from '../my-block' 38 | 39 | test('my block works with match()', t => { 40 | const output = createConfig([ 41 | match('*.myext', { exclude: /node_modules/ }, [ 42 | myBlock() 43 | ]) 44 | ]) 45 | 46 | t.deepEqual(output, { 47 | module: { 48 | rules: [ 49 | { 50 | test: /^.*\.myext/, 51 | exclude: /node_modules/, 52 | use: ['my-fancy-loader'] 53 | } 54 | ] 55 | } 56 | }) 57 | }) 58 | ``` 59 | 60 | 61 | Even if you feed `createConfig()` nothing more than your block it is still an integration test, 62 | since you also test if your block is actually compatible with `createConfig`. 63 | 64 | The benefit compared to unit testing is that this way you focus on testing the block's output, not 65 | its implementation details, like if the configuration is changed by the block's main code or a post 66 | hook. 67 | 68 | ## End-to-end tests 69 | 70 | Integration testing is a convenient way to quickly find regressions. But the confidence that the 71 | block will actually work in a real-world use case is still quite limited. 72 | 73 | This is where end-to-end tests are useful: they actually run webpack on a minimalistic test project 74 | that uses the block. 75 | 76 | You can have a look at webpack-block's end-to-end tests in 77 | [`../packages/webpack/**tests**/end-to-end.test.js`](packages/webpack/__tests__/end-to-end.test.js) 78 | (the test file) and [`../packages/webpack/**e2e-fixtures**/`](packages/webpack/__e2e-fixtures__/) 79 | (the test projects). 80 | 81 | Those test projects consist of a `webpack.config.js`, an `app.js` and whatever source files are 82 | supposed to be tested. 83 | 84 | The end-to-end test will run webpack and then match the generated build artefacts in `build/` 85 | against their expected values: 86 | 87 | ```js 88 | import test from 'ava' 89 | import fs from 'mz/fs' 90 | import path from 'path' 91 | import webpack from 'webpack' 92 | 93 | const fixturesPath = path.join(__dirname, '..', '__e2e-fixtures__') 94 | 95 | test('building a minimal webpack project works', async t => { 96 | const projectPath = path.join(fixturesPath, 'minimal') 97 | const buildPath = path.join(projectPath, 'build') 98 | 99 | const config = require(path.join(projectPath, 'webpack.config.js')) 100 | await runWebpack(config) 101 | 102 | // run the bundle created by webpack 103 | const bundleExports = require(path.join(buildPath, 'bundle.js')) 104 | 105 | t.is(bundleExports, 'I am the minimal test export') 106 | }) 107 | 108 | function runWebpack(config) { 109 | return new Promise((resolve, reject) => { 110 | webpack(config, (error, stats) => { 111 | if (error) { 112 | reject(error) 113 | } else if (stats.hasErrors()) { 114 | stats.toJson().errors.forEach(error => console.error(error, '\n')) 115 | reject(new Error('Webpack soft error occured. See stderr output.')) 116 | } else { 117 | resolve(stats) 118 | } 119 | }) 120 | }) 121 | } 122 | ``` 123 | 124 | These tests require more effort and run slower than integration tests, but the confidence in blocks 125 | tested like this is pretty high. 126 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "npmClient": "yarn", 6 | "useWorkspaces": true, 7 | "version": "2.1.0" 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "lint": "eslint \"**/*.js\"", 5 | "format": "prettier \"**/*.{js,ts,tsx,md}\" --write --loglevel warn", 6 | "test": "prettier \"**/*.{js,ts,tsx,md}\" --list-different && yarn lint && ava" 7 | }, 8 | "devDependencies": { 9 | "ava": "^2.0.0", 10 | "eslint": "^5.16.0", 11 | "lerna": "^3.15.0", 12 | "prettier": "^1.14.3" 13 | }, 14 | "workspaces": [ 15 | "packages/*" 16 | ], 17 | "prettier": { 18 | "printWidth": 100, 19 | "semi": false, 20 | "singleQuote": true, 21 | "proseWrap": "always" 22 | }, 23 | "resolutions": { 24 | "**/**/fstream": "^1.0.12", 25 | "**/**/js-yaml": "^3.13.1", 26 | "**/node-sass/node-gyp/tar": "^2.2.2" 27 | } 28 | } -------------------------------------------------------------------------------- /packages/assets/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/assets - Changelog 2 | 3 | ## 2.0.0-alpha.1 4 | 5 | - Major version upgrade of `css-loader` to the 6 | [1.0.0](https://github.com/webpack-contrib/css-loader/releases/tag/v1.0.0) 7 | ([#291](https://github.com/andywer/webpack-blocks/pull/291)) 8 | - Major version upgrade of `file-loader` to the 9 | [2.0.0](https://github.com/webpack-contrib/file-loader/releases/tag/v2.0.0) 10 | ([#291](https://github.com/andywer/webpack-blocks/pull/291)) 11 | 12 | ## 2.0.0-alpha 13 | 14 | - Remove deprecated `fileType` API ([#260](https://github.com/andywer/webpack-blocks/issues/260)) 15 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 16 | 17 | ## 1.0.0-rc 18 | 19 | - Added a `styleLoader` option to `css()` and `css.modules()` blocks. With it it's now possible to 20 | pass options to the `style-loader` or remove it from loaders completely. 21 | 22 | ## 1.0.0-beta.2 23 | 24 | - Update dependency versions 25 | 26 | ## 1.0.0-beta 27 | 28 | - Use `match()` instead of passing a file type option 29 | 30 | ## 1.0.0-alpha 31 | 32 | - Initial release 33 | -------------------------------------------------------------------------------- /packages/assets/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - assets 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/assets.svg)](https://www.npmjs.com/package/@webpack-blocks/assets) 5 | 6 | This is the `assets` block providing configuration for the style loader, file loader, URL loader and 7 | friends. 8 | 9 | ## Usage 10 | 11 | 12 | ```js 13 | const { createConfig, match } = require('@webpack-blocks/webpack') 14 | const { css, file, url } = require('@webpack-blocks/assets') 15 | 16 | module.exports = createConfig([ 17 | css(), // or use `match()` to apply it to other files than *.css 18 | 19 | // will copy font files to build directory and link to them 20 | match(['*.eot', '*.ttf', '*.woff', '*.woff2'], [ 21 | file() 22 | ]), 23 | 24 | // will load images up to 10KB as data URL 25 | match(['*.gif', '*.jpg', '*.jpeg', '*.png', '*.svg', '*.webp'], [ 26 | url({ limit: 10000 }) 27 | ]) 28 | ]) 29 | ``` 30 | 31 | 32 | In order to use CSS modules: 33 | 34 | ```js 35 | const { createConfig, match } = require('@webpack-blocks/webpack') 36 | const { css } = require('@webpack-blocks/assets') 37 | 38 | module.exports = createConfig([ 39 | match( 40 | ['*.css', '!*node_modules*'], 41 | [ 42 | css.modules({ 43 | modules: { 44 | localIdentName: '[name]--[local]--[hash:base64:5]' 45 | } 46 | }) 47 | ] 48 | ) 49 | ]) 50 | ``` 51 | 52 | ## API 53 | 54 | ### css(options: ?object) 55 | 56 | Will match `*.css` by default if not used with `match()`. You can pass all 57 | [`css-loader` options](https://github.com/webpack-contrib/css-loader). With `styleLoader` you can 58 | pass options to the [`style-loader`](https://github.com/webpack-contrib/style-loader), setting it to 59 | `false` will remove the `style-loader` from loaders. 60 | 61 | ### css.modules(options: ?object) 62 | 63 | Will match `*.css` by default if not used with `match()`. You can pass all 64 | [`css-loader` options](https://github.com/webpack-contrib/css-loader). 65 | 66 | The difference to `css()` is that it sets the following `css-loader` options by default: 67 | 68 | - `modules` option is enabled 69 | - `importLoaders` defaults to `1` 70 | - `modules.localIdentName` defaults to `'[name]--[local]--[hash:base64:5]'` in development and 71 | `'[hash:base64:10]'` in production 72 | 73 | ### file(options: ?object) 74 | 75 | Must be used with `match()`. You can pass all 76 | [`file-loader` options](https://github.com/webpack-contrib/file-loader). 77 | 78 | ### url(options: ?object) 79 | 80 | Must be used with `match()`. You can pass all 81 | [`url-loader` options](https://github.com/webpack-contrib/url-loader). We strongly recommend setting 82 | a `limit` to prevent huge files to be encoded as a data URL. 83 | 84 | ## webpack-blocks 85 | 86 | Check out the 87 | 88 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 89 | 90 | Released under the terms of the MIT license. 91 | -------------------------------------------------------------------------------- /packages/assets/__tests__/css-modules.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import { css } from '../lib/index' 4 | 5 | test('css.modules() works', t => { 6 | const config = createConfig({}, [css.modules()]) 7 | 8 | t.deepEqual(config.module.rules, [ 9 | { 10 | test: /\.css$/, 11 | use: [ 12 | { 13 | loader: 'style-loader', 14 | options: {} 15 | }, 16 | { 17 | loader: 'css-loader', 18 | options: { 19 | modules: { 20 | localIdentName: '[name]--[local]--[hash:base64:5]' 21 | }, 22 | importLoaders: 1 23 | } 24 | } 25 | ] 26 | } 27 | ]) 28 | }) 29 | 30 | test('css.modules() works with options and match()', t => { 31 | const config = createConfig({}, [ 32 | match('*.pcss', { exclude: /node_modules/ }, [ 33 | css.modules({ 34 | sourceMap: true 35 | }) 36 | ]) 37 | ]) 38 | 39 | t.deepEqual(config.module.rules, [ 40 | { 41 | test: /^.*\.pcss$/, 42 | exclude: /node_modules/, 43 | use: [ 44 | { 45 | loader: 'style-loader', 46 | options: {} 47 | }, 48 | { 49 | loader: 'css-loader', 50 | options: { 51 | modules: { 52 | localIdentName: '[name]--[local]--[hash:base64:5]' 53 | }, 54 | importLoaders: 1, 55 | sourceMap: true 56 | } 57 | } 58 | ] 59 | } 60 | ]) 61 | }) 62 | 63 | test('style-loader can take options', t => { 64 | const config = createConfig({}, [ 65 | css.modules({ 66 | styleLoader: { 67 | esModule: true 68 | } 69 | }) 70 | ]) 71 | 72 | t.deepEqual(config.module.rules, [ 73 | { 74 | test: /\.css$/, 75 | use: [ 76 | { 77 | loader: 'style-loader', 78 | options: { 79 | esModule: true 80 | } 81 | }, 82 | { 83 | loader: 'css-loader', 84 | options: { 85 | modules: { 86 | localIdentName: '[name]--[local]--[hash:base64:5]' 87 | }, 88 | importLoaders: 1 89 | } 90 | } 91 | ] 92 | } 93 | ]) 94 | }) 95 | 96 | test('style-loader can be disabled', t => { 97 | const config = createConfig({}, [ 98 | css.modules({ 99 | styleLoader: false 100 | }) 101 | ]) 102 | 103 | t.deepEqual(config.module.rules, [ 104 | { 105 | test: /\.css$/, 106 | use: [ 107 | { 108 | loader: 'css-loader', 109 | options: { 110 | modules: { 111 | localIdentName: '[name]--[local]--[hash:base64:5]' 112 | }, 113 | importLoaders: 1 114 | } 115 | } 116 | ] 117 | } 118 | ]) 119 | }) 120 | -------------------------------------------------------------------------------- /packages/assets/__tests__/css.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import { css } from '../lib/index' 4 | 5 | test('css() works', t => { 6 | const config = createConfig({}, [css()]) 7 | 8 | t.deepEqual(config.module.rules, [ 9 | { 10 | test: /\.css$/, 11 | use: [ 12 | { 13 | loader: 'style-loader', 14 | options: {} 15 | }, 16 | { 17 | loader: 'css-loader', 18 | options: {} 19 | } 20 | ] 21 | } 22 | ]) 23 | }) 24 | 25 | test('css() works with options and match()', t => { 26 | const config = createConfig({}, [ 27 | match('*.pcss', { exclude: /node_modules/ }, [ 28 | css({ 29 | sourceMaps: true 30 | }) 31 | ]) 32 | ]) 33 | 34 | t.deepEqual(config.module.rules, [ 35 | { 36 | test: /^.*\.pcss$/, 37 | exclude: /node_modules/, 38 | use: [ 39 | { 40 | loader: 'style-loader', 41 | options: {} 42 | }, 43 | { 44 | loader: 'css-loader', 45 | options: { 46 | sourceMaps: true 47 | } 48 | } 49 | ] 50 | } 51 | ]) 52 | }) 53 | 54 | test('style-loader can take options', t => { 55 | const config = createConfig({}, [ 56 | css({ 57 | styleLoader: { 58 | esModule: true 59 | } 60 | }) 61 | ]) 62 | 63 | t.deepEqual(config.module.rules, [ 64 | { 65 | test: /\.css$/, 66 | use: [ 67 | { 68 | loader: 'style-loader', 69 | options: { 70 | esModule: true 71 | } 72 | }, 73 | { 74 | loader: 'css-loader', 75 | options: {} 76 | } 77 | ] 78 | } 79 | ]) 80 | }) 81 | 82 | test('style-loader can be disabled', t => { 83 | const config = createConfig({}, [ 84 | css({ 85 | styleLoader: false 86 | }) 87 | ]) 88 | 89 | t.deepEqual(config.module.rules, [ 90 | { 91 | test: /\.css$/, 92 | use: [ 93 | { 94 | loader: 'css-loader', 95 | options: {} 96 | } 97 | ] 98 | } 99 | ]) 100 | }) 101 | -------------------------------------------------------------------------------- /packages/assets/__tests__/file.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import { file } from '../lib/index' 4 | 5 | test('file() works with match', t => { 6 | const config = createConfig({}, [match('*.jpg', [file()])]) 7 | 8 | t.deepEqual(config.module.rules, [ 9 | { 10 | test: /^.*\.jpg$/, 11 | use: [ 12 | { 13 | loader: 'file-loader', 14 | options: {} 15 | } 16 | ] 17 | } 18 | ]) 19 | }) 20 | 21 | test('file() works with options and match()', t => { 22 | const config = createConfig({}, [ 23 | match('*.gif', { exclude: /node_modules/ }, [ 24 | file({ 25 | emitFile: false 26 | }) 27 | ]) 28 | ]) 29 | 30 | t.deepEqual(config.module.rules, [ 31 | { 32 | test: /^.*\.gif$/, 33 | exclude: /node_modules/, 34 | use: [ 35 | { 36 | loader: 'file-loader', 37 | options: { 38 | emitFile: false 39 | } 40 | } 41 | ] 42 | } 43 | ]) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/assets/__tests__/url.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import { url } from '../lib/index' 4 | 5 | test('url() works with match', t => { 6 | const config = createConfig({}, [match('*.jpg', [url()])]) 7 | 8 | t.deepEqual(config.module.rules, [ 9 | { 10 | test: /^.*\.jpg$/, 11 | use: [ 12 | { 13 | loader: 'url-loader', 14 | options: {} 15 | } 16 | ] 17 | } 18 | ]) 19 | }) 20 | 21 | test('url() works with options and match()', t => { 22 | const config = createConfig({}, [ 23 | match('*.gif', { exclude: /node_modules/ }, [ 24 | url({ 25 | mimetype: 'image/gif' 26 | }) 27 | ]) 28 | ]) 29 | 30 | t.deepEqual(config.module.rules, [ 31 | { 32 | test: /^.*\.gif$/, 33 | exclude: /node_modules/, 34 | use: [ 35 | { 36 | loader: 'url-loader', 37 | options: { 38 | mimetype: 'image/gif' 39 | } 40 | } 41 | ] 42 | } 43 | ]) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/assets/lib/css.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | 3 | module.exports = css 4 | module.exports.modules = cssModules 5 | 6 | /** 7 | * @param {object} [options] You can pass all css-loader options. 8 | * @param {object} [options.styleLoader] style-loader options. If set to 'false' the 'style-loader' won't be added. 9 | * @return {Function} 10 | * @see https://github.com/webpack-contrib/css-loader 11 | */ 12 | function css(options = {}) { 13 | const cssOptions = _.omit(options, ['styleLoader']) 14 | const loaders = [{ loader: 'css-loader', options: cssOptions }] 15 | 16 | if (options.styleLoader !== false) { 17 | loaders.unshift({ loader: 'style-loader', options: options.styleLoader || {} }) 18 | } 19 | 20 | return (context, util) => 21 | util.addLoader( 22 | Object.assign( 23 | { 24 | test: /\.css$/, 25 | use: loaders 26 | }, 27 | context.match 28 | ) 29 | ) 30 | } 31 | 32 | /** 33 | * @param {object} [options] You can pass all css-loader options. 34 | * @param {number} [options.importLoaders] Defaults to 1. 35 | * @param {string} [options.modules.localIdentName] Defaults to '[hash:base64:10]' in production, '[name]--[local]--[hash:base64:5]' in development. 36 | * @param {object} [options.styleLoader] style-loader options. If set to 'false' the 'style-loader' won't be added. 37 | * @return {Function} 38 | * @see https://github.com/webpack-contrib/css-loader 39 | */ 40 | function cssModules(options = {}) { 41 | const defaultCssOptions = { 42 | modules: { 43 | localIdentName: 44 | String(process.env.NODE_ENV) === 'production' 45 | ? '[hash:base64:10]' 46 | : '[name]--[local]--[hash:base64:5]' 47 | }, 48 | importLoaders: 1 49 | } 50 | const cssOptions = _.merge( 51 | defaultCssOptions, 52 | _.omit(options, ['exclude', 'include', 'styleLoader']) 53 | ) 54 | const loaders = [{ loader: 'css-loader', options: cssOptions }] 55 | 56 | if (options.styleLoader !== false) { 57 | loaders.unshift({ loader: 'style-loader', options: options.styleLoader || {} }) 58 | } 59 | 60 | return (context, util) => 61 | util.addLoader( 62 | Object.assign( 63 | { 64 | test: /\.css$/, 65 | use: loaders 66 | }, 67 | context.match 68 | ) 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /packages/assets/lib/file.js: -------------------------------------------------------------------------------- 1 | module.exports = file 2 | 3 | /** 4 | * @param {object} [options] You can pass all file-loader options. 5 | * @return {Function} 6 | * @see https://github.com/webpack-contrib/file-loader 7 | */ 8 | function file(options = {}) { 9 | return (context, util) => { 10 | if (!context.match) { 11 | throw new Error( 12 | `The file() block can only be used in combination with match(). ` + 13 | `Use match() to state on which files to apply the file loader.` 14 | ) 15 | } 16 | 17 | return util.addLoader( 18 | Object.assign( 19 | { 20 | use: [{ loader: 'file-loader', options }] 21 | }, 22 | context.match 23 | ) 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/assets/lib/index.js: -------------------------------------------------------------------------------- 1 | const css = require('./css') 2 | const file = require('./file') 3 | const url = require('./url') 4 | 5 | module.exports = { 6 | css, 7 | file, 8 | url 9 | } 10 | -------------------------------------------------------------------------------- /packages/assets/lib/url.js: -------------------------------------------------------------------------------- 1 | module.exports = url 2 | 3 | /** 4 | * @param {object} [options] You can pass all url-loader options. 5 | * @return {Function} 6 | * @see https://github.com/webpack-contrib/url-loader 7 | */ 8 | function url(options = {}) { 9 | return (context, util) => { 10 | if (!context.match) { 11 | throw new Error( 12 | `The url() block can only be used in combination with match(). ` + 13 | `Use match() to state on which files to apply the url loader.` 14 | ) 15 | } 16 | 17 | return util.addLoader( 18 | Object.assign( 19 | { 20 | use: [{ loader: 'url-loader', options }] 21 | }, 22 | context.match 23 | ) 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/assets", 3 | "version": "2.1.0", 4 | "description": "Webpack block for style loader, file loader, url loader and friends.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "engines": { 9 | "node": ">= 10.13.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "css-loader": "^3.5.2", 19 | "file-loader": "^6.0.0", 20 | "lodash": "^4.17.15", 21 | "style-loader": "^1.1.4", 22 | "url-loader": "^4.1.0" 23 | }, 24 | "devDependencies": { 25 | "@webpack-blocks/core": "^2.1.0", 26 | "webpack": "^4.20.2" 27 | }, 28 | "peerDependencies": { 29 | "@webpack-blocks/core": "^2.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/babel/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/babel - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | - Support for babel 7 ([#295](https://github.com/andywer/webpack-blocks/pull/295)) 7 | 8 | ## 1.0.0-rc 9 | 10 | - Breaking change: Renamed package from `babel6` to `babel` and moved `babel-core` to be a peer 11 | dependency ([#217](https://github.com/andywer/webpack-blocks/issues/217)) 12 | 13 | ## 1.0.0-beta.2 14 | 15 | - Update dependency versions 16 | 17 | ## 1.0.0-beta 18 | 19 | - Use `match()` instead of passing an `exclude` option 20 | - Updated `babel-loader` from 6.x to 7.0, but sticking to babel 6.x 21 | 22 | ## 1.0.0-alpha 23 | 24 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 25 | - Requires node 6+ 26 | 27 | ## 0.4.1 28 | 29 | - Fix default `node_modules/` exclusion, so it works on windows 30 | ([#108](https://github.com/andywer/webpack-blocks/pull/108)) 31 | 32 | ## 0.4.0 33 | 34 | - No changes, just keeping the version numbers in sync 35 | 36 | ## 0.3.0 37 | 38 | - Use `context` 39 | - Allow passing `presets`/`plugins` to block 40 | - May set `include` (white-list file matching), not just `exclude` 41 | 42 | ## 0.1.1 43 | 44 | - `loaders: [ 'babel' ]` => `loaders: [ 'babel-loader' ]` 45 | 46 | ## 0.1.0 47 | 48 | Initial release. 49 | -------------------------------------------------------------------------------- /packages/babel/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - Babel 6+ 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/babel.svg)](https://www.npmjs.com/package/@webpack-blocks/babel) 5 | 6 | This is the `babel` block providing Babel (Version 7+) configuration using the 7 | [babel-loader](https://github.com/babel/babel-loader). 8 | 9 | ## Installation 10 | 11 | ```sh 12 | npm install --save-dev babel-core @webpack-blocks/babel 13 | # or 14 | yarn add --dev babel-core @webpack-blocks/babel 15 | ``` 16 | 17 | ## Usage 18 | 19 | 20 | ```js 21 | const { createConfig, match } = require('@webpack-blocks/webpack') 22 | const babel = require('@webpack-blocks/babel') 23 | 24 | module.exports = createConfig([ 25 | match(['*.js', '!*node_modules*'], [ 26 | babel(/* options */) 27 | ]) 28 | ]) 29 | ``` 30 | 31 | 32 | You can also use the babel block without `match()`. It will by default match `*.js` and `*.jsx` 33 | files while excluding everything in `node_modules/`. 34 | 35 | ## Options 36 | 37 | #### cacheDirectory _(optional)_ 38 | 39 | Uses a cache directory if set to true. Defaults to true. 40 | 41 | #### plugins _(optional)_ 42 | 43 | Array of Babel plugins to use. Babel will read them from `.babelrc` or `package.json` if omitted. 44 | 45 | #### presets _(optional)_ 46 | 47 | Array of Babel presets to use. Babel will read them from `.babelrc` or `package.json` if omitted. 48 | 49 | ## webpack-blocks 50 | 51 | Check out the 52 | 53 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 54 | 55 | Released under the terms of the MIT license. 56 | -------------------------------------------------------------------------------- /packages/babel/__tests__/babel.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import babel from '../index' 4 | 5 | test('Babel default options work', t => { 6 | const config = createConfig({}, [babel()]) 7 | 8 | t.deepEqual(config.module.rules, [ 9 | { 10 | test: /\.(js|jsx)$/, 11 | exclude: /node_modules/, 12 | use: [ 13 | { 14 | loader: 'babel-loader', 15 | options: { 16 | cacheDirectory: true 17 | } 18 | } 19 | ] 20 | } 21 | ]) 22 | }) 23 | 24 | test('Babel options work', t => { 25 | const config = createConfig({}, [ 26 | babel({ 27 | presets: ['es2015'], 28 | plugins: ['bar'] 29 | }) 30 | ]) 31 | 32 | t.deepEqual(config.module.rules, [ 33 | { 34 | test: /\.(js|jsx)$/, 35 | exclude: /node_modules/, 36 | use: [ 37 | { 38 | loader: 'babel-loader', 39 | options: { 40 | cacheDirectory: true, 41 | presets: ['es2015'], 42 | plugins: ['bar'] 43 | } 44 | } 45 | ] 46 | } 47 | ]) 48 | }) 49 | 50 | test('using custom match() works', t => { 51 | const config = createConfig({}, [ 52 | match('*.js', { exclude: null }, [ 53 | babel({ 54 | cacheDirectory: false 55 | }) 56 | ]) 57 | ]) 58 | 59 | t.deepEqual(config.module.rules, [ 60 | { 61 | test: /^.*\.js$/, 62 | exclude: null, 63 | use: [ 64 | { 65 | loader: 'babel-loader', 66 | options: { 67 | cacheDirectory: false 68 | } 69 | } 70 | ] 71 | } 72 | ]) 73 | }) 74 | -------------------------------------------------------------------------------- /packages/babel/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Babel webpack block. 3 | * 4 | * @see https://github.com/babel/babel-loader 5 | */ 6 | 7 | module.exports = babel 8 | 9 | /** 10 | * @param {object} [options] 11 | * @param {bool} [options.cacheDirectory] Use cache directory. Defaults to true. 12 | * @param {string[]} [options.plugins] Babel plugins to use. 13 | * @param {string[]} [options.presets] Babel presets to use. 14 | * @return {Function} 15 | */ 16 | function babel(options = {}) { 17 | options = Object.assign( 18 | { 19 | cacheDirectory: true 20 | }, 21 | options 22 | ) 23 | 24 | const setter = context => prevConfig => { 25 | context.babel = context.babel || {} 26 | 27 | // Merge babel config into the one stored in context 28 | context.babel = Object.assign( 29 | {}, 30 | context.babel, 31 | options, 32 | options.plugins ? { plugins: (context.babel.plugins || []).concat(options.plugins) } : {}, 33 | options.presets ? { presets: (context.babel.presets || []).concat(options.presets) } : {} 34 | ) 35 | return prevConfig 36 | } 37 | 38 | return Object.assign(setter, { post: postConfig }) 39 | } 40 | 41 | function postConfig(context, util) { 42 | const ruleConfig = Object.assign( 43 | { 44 | test: /\.(js|jsx)$/, 45 | exclude: /node_modules/, 46 | use: [{ loader: 'babel-loader', options: context.babel }] 47 | }, 48 | context.match 49 | ) 50 | 51 | return util.addLoader(ruleConfig) 52 | } 53 | -------------------------------------------------------------------------------- /packages/babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/babel", 3 | "version": "2.1.0", 4 | "description": "Webpack block for babel 6+.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "engines": { 9 | "node": ">= 6.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "babel-loader": "^8.1.0" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.2.2", 22 | "@webpack-blocks/core": "^2.1.0", 23 | "webpack": "^4.20.2" 24 | }, 25 | "peerDependencies": { 26 | "@babel/core": "^7.0.0", 27 | "@webpack-blocks/core": "^2.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/core - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Remove deprecated `fileType` API ([#260](https://github.com/andywer/webpack-blocks/issues/260)) 6 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 7 | 8 | ## 1.0.0 9 | 10 | - Added `when()` ([#242](https://github.com/andywer/webpack-blocks/issues/242)) 11 | - Support negations in `match()` to set `exclude` 12 | ([#252](https://github.com/andywer/webpack-blocks/issues/252)) 13 | - Change `match()` to pass all [webpack rule options](https://webpack.js.org/configuration/module/) 14 | to context. ([#250](https://github.com/andywer/webpack-blocks/pull/250)) 15 | 16 | ## 1.0.0-beta.2 17 | 18 | - More useful error message when passing invalid blocks to `createConfig()` 19 | ([#171](https://github.com/andywer/webpack-blocks/issues/171)) 20 | - Less noisy default options 21 | 22 | ## 1.0.0-beta 23 | 24 | - Added `match()` 25 | 26 | ## 1.0.0-alpha 27 | 28 | - New block API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 29 | - Added `text/html` file type 30 | - Dropped transpiling, requires node 6+ 31 | 32 | ## 0.4.0 33 | 34 | - Changed signature of `createConfig()` to allow passing `context.webpackVersion` 35 | - Using `webpack-merge` v2.3 instead of v0.14 36 | - Fixed a bug where the config passed to blocks as parameter might contain duplicate values (see 37 | [#79](https://github.com/andywer/webpack-blocks/pull/79)) 38 | - Added `application/x-typescript` file type 39 | - Added `text/x-less` file type 40 | 41 | ## 0.3.0 42 | 43 | - Breaking API change: Added `context` object 44 | - Implement `group()` for creating presets 45 | 46 | ## 0.1.2 47 | 48 | - Fixes `env()` merging already everything, resulting in duplicate config props (see 49 | [#29](https://github.com/andywer/webpack-blocks/issues/29)) 50 | 51 | ## 0.1.1 52 | 53 | - Ensure `env()` passes webpack config properly to setters. (see 54 | [#2](https://github.com/andywer/webpack-blocks/issues/2)) 55 | 56 | ## 0.1.0 57 | 58 | Initial release. 59 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - core library 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/core.svg)](https://www.npmjs.com/package/@webpack-blocks/core) 5 | 6 | 📦 Functional approach to compose your webpack config. 7 | 8 | This is the core library. Not meant to be used manually. 9 | 10 | ## webpack-blocks 11 | 12 | Check out the 13 | 14 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 15 | 16 | Released under the terms of the MIT license. 17 | -------------------------------------------------------------------------------- /packages/core/lib/__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 9 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/lib/__tests__/createConfig.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import sinon from 'sinon' 3 | import createConfig from '../createConfig' 4 | import blockHelpers from '../blockUtils' 5 | 6 | const defaultConfig = { 7 | distinct1: 'distinct1', 8 | distinct2: 'distinct2', 9 | distinct3: 'distinct3', 10 | shared: 'shared3', 11 | resolve: { 12 | extensions: ['.js', '.json'] 13 | }, 14 | stats: { 15 | children: false, 16 | chunks: false, 17 | modules: false, 18 | reasons: false 19 | }, 20 | module: { 21 | rules: [] 22 | }, 23 | plugins: [] 24 | } 25 | 26 | test('createConfig() invokes blocks (config setters)', t => { 27 | const block1 = () => prevConfig => ({ 28 | ...prevConfig, 29 | distinct1: 'distinct1', 30 | shared: 'shared1' 31 | }) 32 | const block2 = () => prevConfig => ({ 33 | ...prevConfig, 34 | distinct2: 'distinct2', 35 | shared: 'shared2' 36 | }) 37 | const block3 = () => prevConfig => ({ 38 | ...prevConfig, 39 | distinct3: 'distinct3', 40 | shared: 'shared3' 41 | }) 42 | 43 | const resultingConfig = createConfig({}, [block1, block2, block3]) 44 | 45 | t.deepEqual(resultingConfig, defaultConfig) 46 | }) 47 | 48 | test('createConfig() invokes pre hooks', t => { 49 | const block1 = Object.assign(() => config => config, { 50 | pre: sinon.spy(() => {}) 51 | }) 52 | const block2 = Object.assign(() => config => config, { 53 | pre: sinon.spy(() => {}) 54 | }) 55 | const block3 = Object.assign(() => config => config, { 56 | pre: sinon.spy(() => {}) 57 | }) 58 | 59 | createConfig({}, [block1, block2, block3]) 60 | 61 | t.is(block1.pre.callCount, 1) 62 | t.is(block2.pre.callCount, 1) 63 | t.is(block3.pre.callCount, 1) 64 | 65 | t.is(block1.pre.lastCall.args.length, 1) 66 | const context = block1.pre.lastCall.args[0] 67 | t.is(typeof context, 'object') 68 | 69 | t.is(block2.pre.lastCall.args.length, 1) 70 | t.is(block2.pre.lastCall.args[0], context) 71 | 72 | t.is(block3.pre.lastCall.args.length, 1) 73 | t.is(block3.pre.lastCall.args[0], context) 74 | }) 75 | 76 | test('createConfig() invokes post hooks', t => { 77 | const block1 = Object.assign(() => config => config, { 78 | post: sinon.spy(() => prevConfig => ({ 79 | ...prevConfig, 80 | distinct1: 'distinct1', 81 | shared: 'shared1' 82 | })) 83 | }) 84 | const block2 = Object.assign(() => config => config, { 85 | post: sinon.spy(() => prevConfig => ({ 86 | ...prevConfig, 87 | distinct2: 'distinct2', 88 | shared: 'shared2' 89 | })) 90 | }) 91 | const block3 = Object.assign(() => config => config, { 92 | post: sinon.spy(() => prevConfig => ({ 93 | ...prevConfig, 94 | distinct3: 'distinct3', 95 | shared: 'shared3' 96 | })) 97 | }) 98 | 99 | const resultingConfig = createConfig({}, [block1, block2, block3]) 100 | t.deepEqual(resultingConfig, defaultConfig) 101 | 102 | t.is(block1.post.callCount, 1) 103 | t.is(block2.post.callCount, 1) 104 | t.is(block3.post.callCount, 1) 105 | 106 | t.is(block1.post.lastCall.args.length, 2) 107 | t.is(typeof block1.post.lastCall.args[0], 'object') 108 | t.is(block1.post.lastCall.args[1], blockHelpers) 109 | const context = block1.post.lastCall.args[0] 110 | 111 | t.deepEqual(block2.post.lastCall.args, [context, blockHelpers]) 112 | t.deepEqual(block3.post.lastCall.args, [context, blockHelpers]) 113 | }) 114 | 115 | test('createConfig() invokes hooks and setters in the right order', t => { 116 | const block1 = Object.assign(sinon.spy(() => config => config), { 117 | pre: sinon.spy(() => config => config), 118 | post: sinon.spy(() => config => config) 119 | }) 120 | const block2 = Object.assign(sinon.spy(() => config => config), { 121 | pre: [sinon.spy(() => config => config), sinon.spy(() => config => config)], 122 | post: [sinon.spy(() => config => config), sinon.spy(() => config => config)] 123 | }) 124 | const block3 = Object.assign(sinon.spy(() => config => config), { 125 | pre: sinon.spy(() => config => config), 126 | post: sinon.spy(() => config => config) 127 | }) 128 | 129 | createConfig({}, [block1, block2, block3]) 130 | 131 | t.true(block1.pre.called) 132 | t.true(block1.pre.calledBefore(block2.pre[0])) 133 | t.true(block2.pre[0].called) 134 | t.true(block2.pre[0].calledBefore(block2.pre[1])) 135 | t.true(block2.pre[1].called) 136 | t.true(block2.pre[1].calledBefore(block3.pre)) 137 | t.true(block3.pre.called) 138 | t.true(block3.pre.calledBefore(block1)) 139 | 140 | t.true(block1.called) 141 | t.true(block1.calledBefore(block2)) 142 | t.true(block2.called) 143 | t.true(block2.calledBefore(block3)) 144 | t.true(block3.called) 145 | t.true(block3.calledBefore(block1.post)) 146 | 147 | t.true(block1.post.called) 148 | t.true(block1.post.calledBefore(block2.post[0])) 149 | t.true(block2.post[0].called) 150 | t.true(block2.post[0].calledBefore(block2.post[1])) 151 | t.true(block2.post[1].called) 152 | t.true(block2.post[1].calledBefore(block3.post)) 153 | t.true(block3.post.called) 154 | }) 155 | 156 | test('createConfig() ignores duplicate hooks', t => { 157 | const block1 = Object.assign(sinon.spy(() => config => config), { 158 | pre: sinon.spy(() => config => config), 159 | post: sinon.spy(() => config => config) 160 | }) 161 | const block2 = Object.assign(sinon.spy(() => config => config), { 162 | pre: [sinon.spy(() => config => config), block1.pre], 163 | post: [sinon.spy(() => config => config), block1.post] 164 | }) 165 | const block3 = Object.assign(sinon.spy(() => config => config), { 166 | pre: block1.pre, 167 | post: block1.post 168 | }) 169 | 170 | createConfig({}, [block1, block2, block3]) 171 | 172 | t.is(block1.pre.callCount, 1) 173 | t.is(block1.post.callCount, 1) 174 | 175 | t.is(block2.pre[0].callCount, 1) 176 | t.is(block2.post[0].callCount, 1) 177 | 178 | // no need to test block3's pre/post hook spies or block2's 2nd hooks, 179 | // since they equal block1's 180 | }) 181 | -------------------------------------------------------------------------------- /packages/core/lib/__tests__/env.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import sinon from 'sinon' 3 | import env from '../env' 4 | 5 | process.env.NODE_ENV = 'testing' 6 | 7 | test('env() merges correctly', t => { 8 | const envBlock = env(process.env.NODE_ENV, [entryPoint1(), entryPoint2()]) 9 | 10 | t.deepEqual(envBlock(null, {})({}), { 11 | entry: { 12 | foo: './src/foo', 13 | bar: './src/bar' 14 | } 15 | }) 16 | }) 17 | 18 | test('env() respects the NODE_ENV', t => { 19 | const envBlock = env('foo-bar', [entryPoint1(), entryPoint2()]) 20 | 21 | const emptyConfig = { entry: {} } 22 | t.deepEqual(envBlock(null, {})(emptyConfig), emptyConfig) 23 | }) 24 | 25 | test('env() block passes complete config to child blocks', t => { 26 | const spyBlock1 = sinon.spy(() => prevConfig => ({ 27 | ...prevConfig, 28 | entry: { 29 | ...prevConfig.entry, 30 | foo: 'foo' 31 | } 32 | })) 33 | const spyBlock2 = sinon.spy(() => prevConfig => prevConfig) 34 | 35 | const envBlock = env(process.env.NODE_ENV, [spyBlock1, spyBlock2]) 36 | 37 | const createdConfig = envBlock({}, {})({ 38 | entry: { baz: 'baz' } 39 | }) 40 | 41 | t.is(spyBlock1.callCount, 1) 42 | t.is(spyBlock2.callCount, 1) 43 | 44 | t.deepEqual(createdConfig, { 45 | entry: { 46 | baz: 'baz', 47 | foo: 'foo' 48 | } 49 | }) 50 | }) 51 | 52 | function entryPoint1() { 53 | return () => prevConfig => ({ 54 | ...prevConfig, 55 | entry: { 56 | ...prevConfig.entry, 57 | foo: './src/foo' 58 | } 59 | }) 60 | } 61 | 62 | function entryPoint2() { 63 | return () => prevConfig => ({ 64 | ...prevConfig, 65 | entry: { 66 | ...prevConfig.entry, 67 | bar: './src/bar' 68 | } 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/lib/__tests__/group.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import sinon from 'sinon' 3 | import group from '../group' 4 | 5 | test('group() merges the blocks', t => { 6 | const block1 = createTestBlock1() 7 | const block2 = createTestBlock2() 8 | const block3 = createTestBlock3() 9 | 10 | const grouped = group([block1, block2, block3]) 11 | t.is(typeof grouped, 'function') 12 | 13 | const resultingConfig = grouped({}, {})({}) 14 | t.deepEqual(resultingConfig, { 15 | distinct1: 'distinct1', 16 | distinct2: 'distinct2', 17 | distinct3: 'distinct3', 18 | shared: 'shared3' 19 | }) 20 | 21 | t.is(block1.callCount, 1) 22 | t.is(block2.callCount, 1) 23 | t.is(block3.callCount, 1) 24 | }) 25 | 26 | test('group() merges pre hooks', t => { 27 | const block1 = createTestBlock1() 28 | const block2 = createTestBlock2() 29 | const block3 = createTestBlock3() 30 | 31 | const grouped = group([block1, block2, block3]) 32 | 33 | // there are no hooks set on block2 and block3's hook is an array 34 | t.deepEqual(grouped.pre, [block1.pre, block3.pre[0]]) 35 | t.is(block1.pre.callCount, 0) 36 | t.is(block3.pre[0].callCount, 0) 37 | }) 38 | 39 | test('group() merges post hooks', t => { 40 | const block1 = createTestBlock1() 41 | const block2 = createTestBlock2() 42 | const block3 = createTestBlock3() 43 | 44 | const grouped = group([block1, block2, block3]) 45 | 46 | // there are no hooks set on block2 and block3's hook is an array 47 | t.deepEqual(grouped.post, [block1.post, block3.post[0]]) 48 | t.is(block1.post.callCount, 0) 49 | t.is(block3.post[0].callCount, 0) 50 | }) 51 | 52 | // Single-function pre & post hook 53 | function createTestBlock1() { 54 | const setter = () => prevConfig => ({ 55 | ...prevConfig, 56 | distinct1: 'distinct1', 57 | shared: 'shared1' 58 | }) 59 | 60 | const pre = () => {} 61 | const post = () => prevConfig => ({ 62 | ...prevConfig, 63 | post1: 'post1', 64 | postShared: 'shared1' 65 | }) 66 | 67 | return Object.assign(sinon.spy(setter), { 68 | pre: sinon.spy(pre), 69 | post: sinon.spy(post) 70 | }) 71 | } 72 | 73 | // No hooks 74 | function createTestBlock2() { 75 | const setter = () => prevConfig => ({ 76 | ...prevConfig, 77 | distinct2: 'distinct2', 78 | shared: 'shared2' 79 | }) 80 | 81 | return sinon.spy(setter) 82 | } 83 | 84 | // Array-of-functions pre & post hook 85 | function createTestBlock3() { 86 | const setter = () => prevConfig => ({ 87 | ...prevConfig, 88 | distinct3: 'distinct3', 89 | shared: 'shared3' 90 | }) 91 | 92 | const pre = () => {} 93 | const post = () => prevConfig => ({ 94 | ...prevConfig, 95 | post3: 'post3', 96 | postShared: 'shared3' 97 | }) 98 | 99 | return Object.assign(sinon.spy(setter), { 100 | pre: [sinon.spy(pre)], 101 | post: [sinon.spy(post)] 102 | }) 103 | } 104 | -------------------------------------------------------------------------------- /packages/core/lib/__tests__/match.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import createConfig from '../createConfig' 3 | import match from '../match' 4 | 5 | test('match() sets context.match', t => { 6 | const matchedLoaderBlock = context => config => { 7 | t.is(typeof context.match, 'object') 8 | t.deepEqual(Object.keys(context.match), ['test']) 9 | t.true(context.match.test instanceof RegExp) 10 | t.is(context.match.test.toString(), '/^.*\\.css$/') 11 | return config 12 | } 13 | matchedLoaderBlock.pre = context => { 14 | t.is(typeof context.match, 'object') 15 | t.is(context.match.test.toString(), '/^.*\\.css$/') 16 | } 17 | matchedLoaderBlock.post = context => config => { 18 | t.is(typeof context.match, 'object') 19 | t.is(context.match.test.toString(), '/^.*\\.css$/') 20 | return config 21 | } 22 | 23 | const unmatchedLoaderBlock = context => config => { 24 | t.is(typeof context.match, 'undefined') 25 | return config 26 | } 27 | 28 | createConfig({}, [match('*.css', [matchedLoaderBlock]), unmatchedLoaderBlock]) 29 | }) 30 | 31 | test('match() supports options and extended regexps', t => { 32 | const loaderBlock = context => config => { 33 | t.deepEqual(Object.keys(context.match).sort(), ['enforce', 'exclude', 'test']) 34 | t.is(context.match.test.toString(), '/^.*\\.(js|jsx)$/') 35 | t.is(context.match.exclude, 'node_modules') 36 | return config 37 | } 38 | 39 | // TODO: Unfortunately you may not have a space after the comma in the glob 40 | // or the space will become part of the regex... 41 | 42 | createConfig({}, [ 43 | match('*.{js,jsx}', { exclude: 'node_modules', enforce: 'pre' }, [loaderBlock]) 44 | ]) 45 | }) 46 | 47 | test('match() supports negations', t => { 48 | const loaderBlock = context => config => { 49 | t.deepEqual(Object.keys(context.match).sort(), ['exclude', 'test']) 50 | t.is(context.match.test.toString(), '/^.*\\.js$/') 51 | t.is(context.match.exclude.toString(), '/^.*node_modules.*$/') 52 | return config 53 | } 54 | 55 | createConfig({}, [match(['*.js', '!*node_modules*'], [loaderBlock])]) 56 | }) 57 | 58 | test('match() returns derived context that propagates mutations', t => { 59 | const mutatingBlock = context => config => { 60 | context.foo = 'bar' 61 | return config 62 | } 63 | 64 | const readingBlock = context => config => { 65 | t.is(context.foo, 'bar') 66 | return config 67 | } 68 | 69 | createConfig({}, [match(['*.sass', '*.scss'], [mutatingBlock]), readingBlock]) 70 | }) 71 | 72 | test('match() throws if `options` argument has `test`', t => { 73 | t.throws(() => createConfig({}, [match(['*.js'], { test: /\.jsx/ }, [])])) 74 | }) 75 | -------------------------------------------------------------------------------- /packages/core/lib/__tests__/when.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import sinon from 'sinon' 3 | import when from '../when' 4 | 5 | test('when() merges correctly', t => { 6 | const whenBlock = when(true, [entryPoint1(), entryPoint2()]) 7 | 8 | t.deepEqual(whenBlock(null, {})({}), { 9 | entry: { 10 | foo: './src/foo', 11 | bar: './src/bar' 12 | } 13 | }) 14 | }) 15 | 16 | test('when() respects the condition', t => { 17 | const whenBlock = when(false, [entryPoint1(), entryPoint2()]) 18 | 19 | const emptyConfig = { entry: {} } 20 | t.deepEqual(whenBlock(null, {})(emptyConfig), emptyConfig) 21 | }) 22 | 23 | test('when() block passes complete config to child blocks', t => { 24 | const spyBlock1 = sinon.spy(() => prevConfig => ({ 25 | ...prevConfig, 26 | entry: { 27 | ...prevConfig.entry, 28 | foo: 'foo' 29 | } 30 | })) 31 | const spyBlock2 = sinon.spy(() => prevConfig => prevConfig) 32 | 33 | const whenBlock = when(true, [spyBlock1, spyBlock2]) 34 | 35 | const createdConfig = whenBlock({}, {})({ 36 | entry: { baz: 'baz' } 37 | }) 38 | 39 | t.is(spyBlock1.callCount, 1) 40 | t.is(spyBlock2.callCount, 1) 41 | 42 | t.deepEqual(createdConfig, { 43 | entry: { 44 | baz: 'baz', 45 | foo: 'foo' 46 | } 47 | }) 48 | }) 49 | 50 | function entryPoint1() { 51 | return () => prevConfig => ({ 52 | ...prevConfig, 53 | entry: { 54 | ...prevConfig.entry, 55 | foo: './src/foo' 56 | } 57 | }) 58 | } 59 | 60 | function entryPoint2() { 61 | return () => prevConfig => ({ 62 | ...prevConfig, 63 | entry: { 64 | ...prevConfig.entry, 65 | bar: './src/bar' 66 | } 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/lib/blockUtils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const webpackMerge = require('webpack-merge') 3 | 4 | module.exports = { 5 | merge, 6 | addLoader, 7 | addPlugin 8 | } 9 | 10 | function merge(configSnippet) { 11 | return prevConfig => webpackMerge.smart(prevConfig, configSnippet) 12 | } 13 | 14 | function addLoader(loaderDef) { 15 | const cleanedLoaderDef = _.omitBy(loaderDef, _.isUndefined) 16 | return prevConfig => 17 | webpackMerge.smart(prevConfig, { 18 | module: { 19 | rules: [cleanedLoaderDef] 20 | } 21 | }) 22 | } 23 | 24 | function addPlugin(plugin) { 25 | return prevConfig => 26 | Object.assign({}, prevConfig, { plugins: prevConfig.plugins.concat([plugin]) }) 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/lib/configSetters.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const blockUtils = require('./blockUtils') 3 | 4 | module.exports = { 5 | assertConfigSetters, 6 | invokeConfigSetters 7 | } 8 | 9 | /** 10 | * Asserts that given param is an array of functions. 11 | * 12 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 13 | */ 14 | function assertConfigSetters(configSetters) { 15 | if (!Array.isArray(configSetters)) { 16 | throw new Error( 17 | `Expected parameter 'configSetters' to be an array of functions. Instead got ${configSetters}.` 18 | ) 19 | } 20 | if (!configSetters.every(_.isFunction)) { 21 | const invalidElementIndex = configSetters.findIndex(setter => !_.isFunction(setter)) 22 | throw new Error( 23 | `Expected parameter 'configSetters' to be an array of functions. ` + 24 | `Element at index ${invalidElementIndex} is invalid: ${configSetters[invalidElementIndex]}.` 25 | ) 26 | } 27 | } 28 | 29 | function invokeConfigSetters(configSetters, context, baseConfig) { 30 | return configSetters.reduce((config, setter) => { 31 | const updateFunction = setter(context, blockUtils) 32 | return updateFunction(config) 33 | }, baseConfig) 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/lib/createConfig.js: -------------------------------------------------------------------------------- 1 | const { assertConfigSetters, invokeConfigSetters } = require('./configSetters') 2 | const { invokePreHooks, invokePostHooks } = require('./hooks') 3 | 4 | module.exports = createConfig 5 | 6 | /** 7 | * Takes an array of webpack blocks and creates a webpack config out of them. 8 | * Each webpack block is a callback function which will be invoked to return a 9 | * partial webpack config. These partial configs are merged to create the 10 | * final, complete webpack config that will be returned. 11 | * 12 | * @param {object} initialContext The blueprint for the initial context object. 13 | * @param {object} initialContext.webpack Webpack instance 14 | * @param {object} initialContext.webpackVersion Webpack version (`{ major, minor, ... }`) 15 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 16 | * @return {object} Webpack config object. 17 | */ 18 | function createConfig(initialContext, configSetters) { 19 | if (!initialContext) { 20 | throw new Error(`No initial context passed.`) 21 | } 22 | assertConfigSetters(configSetters) 23 | 24 | const baseConfig = { 25 | resolve: { 26 | // Explicitly define default extensions, otherwise blocks will overwrite them instead of extending 27 | extensions: ['.js', '.json'] 28 | }, 29 | // Less noisy than default settings 30 | stats: { 31 | children: false, 32 | chunks: false, 33 | modules: false, 34 | reasons: false 35 | }, 36 | module: { 37 | rules: [] 38 | }, 39 | plugins: [] 40 | } 41 | 42 | invokePreHooks(configSetters, initialContext) 43 | const config = invokeConfigSetters(configSetters, initialContext, baseConfig) 44 | const postProcessedConfig = invokePostHooks(configSetters, initialContext, config) 45 | 46 | return postProcessedConfig 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/lib/env.js: -------------------------------------------------------------------------------- 1 | const { assertConfigSetters } = require('./configSetters') 2 | const group = require('./group') 3 | 4 | module.exports = env 5 | 6 | /** 7 | * Applies an array of webpack blocks only if `process.env.NODE_ENV` matches the 8 | * given `envName`. If no `NODE_ENV` is set, it will be treated as 'development'. 9 | * 10 | * @param {string} envName Environment name like 'development', 'production' or 'testing'. 11 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 12 | * @return {Function} 13 | */ 14 | function env(envName, configSetters) { 15 | assertConfigSetters(configSetters) 16 | 17 | const currentEnv = process.env.NODE_ENV || 'development' 18 | 19 | if (currentEnv !== envName) { 20 | return () => config => config 21 | } else { 22 | return group(configSetters) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/lib/group.js: -------------------------------------------------------------------------------- 1 | const { assertConfigSetters, invokeConfigSetters } = require('./configSetters') 2 | const { getHooks } = require('./hooks') 3 | 4 | module.exports = group 5 | 6 | /** 7 | * Combines an array of blocks to a new joined block. Running this single block 8 | * has the same effect as running all source blocks. 9 | * 10 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 11 | * @return {Function} 12 | */ 13 | function group(configSetters) { 14 | assertConfigSetters(configSetters) 15 | 16 | const pre = getHooks(configSetters, 'pre') 17 | const post = getHooks(configSetters, 'post') 18 | 19 | const groupBlock = context => config => invokeConfigSetters(configSetters, context, config) 20 | 21 | return Object.assign(groupBlock, { pre, post }) 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/lib/hooks.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const { invokeConfigSetters } = require('./configSetters') 3 | 4 | module.exports = { 5 | getHooks, 6 | invokePreHooks, 7 | invokePostHooks 8 | } 9 | 10 | function getHooks(configSetters, type) { 11 | // Get all the blocks' pre/post hooks 12 | const hooks = configSetters.filter(setter => Boolean(setter[type])).map(setter => setter[type]) 13 | 14 | // Flatten the array (since each item might be an array as well) 15 | const flattenedHooks = hooks 16 | .map(hook => (Array.isArray(hook) ? hook : [hook])) 17 | .reduce((allHooks, someHooks) => allHooks.concat(someHooks), []) 18 | 19 | return _.uniq(flattenedHooks) 20 | } 21 | 22 | function invokePreHooks(configSetters, context) { 23 | const preHooks = getHooks(configSetters, 'pre') 24 | preHooks.forEach(hook => hook(context)) 25 | } 26 | 27 | function invokePostHooks(configSetters, context, config) { 28 | const postHooks = getHooks(configSetters, 'post') 29 | return invokeConfigSetters(postHooks, context, config) 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/lib/index.js: -------------------------------------------------------------------------------- 1 | const createConfig = require('./createConfig') 2 | const group = require('./group') 3 | const env = require('./env') 4 | const match = require('./match') 5 | const when = require('./when') 6 | 7 | module.exports = { 8 | createConfig, 9 | group, 10 | env, 11 | match, 12 | when 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/lib/match.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const globToRegex = require('glob-to-regexp') 3 | const { assertConfigSetters, invokeConfigSetters } = require('./configSetters') 4 | const { invokePreHooks, invokePostHooks } = require('./hooks') 5 | 6 | module.exports = match 7 | 8 | const regexify = glob => globToRegex(glob, { extended: true }) 9 | const stripArrayConditionally = array => (array.length === 1 ? array[0] : array) 10 | const toArray = thing => (Array.isArray(thing) ? thing : [thing]) 11 | 12 | /** 13 | * State on which files to apply the loader blocks passed in this call. Works 14 | * like `group()`, but adds the file matching information to the context. 15 | * 16 | * @param {string|RegExp|Function|Array} test A glob like `*.css` or `{*.js, *.jsx}` or something else to use as `loader.test`. 17 | * @param {object} [options] Rule options. See https://webpack.js.org/configuration/module/ 18 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 19 | * @return {Function} 20 | */ 21 | function match(test, options, configSetters) { 22 | if (!configSetters && Array.isArray(options)) { 23 | configSetters = options 24 | options = {} 25 | } 26 | 27 | assertConfigSetters(configSetters) 28 | 29 | if (options.test) { 30 | throw new Error( 31 | `match(): Setting 'test' in options is not supported; use the argument instead.` 32 | ) 33 | } 34 | 35 | const { inclusions, exclusions } = splitPatterns(toArray(test)) 36 | const match = Object.assign({}, options, { 37 | // The `.test` is usually just one pattern, so stripping the array feels more natural 38 | test: stripArrayConditionally(normalizeMatchers(inclusions)) 39 | }) 40 | 41 | if (exclusions.length > 0) { 42 | match.exclude = _.concat( 43 | match.exclude ? toArray(match.exclude) : [], 44 | normalizeMatchers(exclusions) 45 | ) 46 | } 47 | 48 | const groupBlock = context => config => 49 | invokeConfigSetters(configSetters, deriveContextWithMatch(context, match), config) 50 | 51 | return Object.assign(groupBlock, { 52 | pre: context => invokePreHooks(configSetters, deriveContextWithMatch(context, match)), 53 | post: context => config => 54 | invokePostHooks(configSetters, deriveContextWithMatch(context, match), config) 55 | }) 56 | } 57 | 58 | function normalizeMatchers(fileMatchTests) { 59 | return fileMatchTests.map(test => { 60 | if (typeof test === 'string') { 61 | return regexify(test) 62 | } 63 | return test 64 | }) 65 | } 66 | 67 | function splitPatterns(patterns) { 68 | const isNegation = pattern => typeof pattern === 'string' && pattern.startsWith('!') 69 | const stripLeadingExclam = pattern => (pattern.startsWith('!') ? pattern.substr(1) : pattern) 70 | return { 71 | inclusions: patterns.filter(pattern => !isNegation(pattern)), 72 | exclusions: patterns.filter(pattern => isNegation(pattern)).map(stripLeadingExclam) 73 | } 74 | } 75 | 76 | function deriveContextWithMatch(context, match) { 77 | // Use an ES Proxy, so the returned context will include `match` and mutations 78 | // to it will also be applied to the original context. This is important, 79 | // since we have now got a global context object and local ones. 80 | return new Proxy(context, { 81 | has(target, propName) { 82 | return propName === 'match' ? true : propName in target 83 | }, 84 | get(target, propName) { 85 | return propName === 'match' ? match : target[propName] 86 | } 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /packages/core/lib/when.js: -------------------------------------------------------------------------------- 1 | const { assertConfigSetters } = require('./configSetters') 2 | const group = require('./group') 3 | 4 | module.exports = when 5 | 6 | /** 7 | * Applies an array of webpack blocks only if `condition` is true. 8 | * 9 | * @param {boolean} condition Condition to test 10 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 11 | * @return {Function} 12 | */ 13 | function when(condition, configSetters) { 14 | assertConfigSetters(configSetters) 15 | 16 | if (condition) { 17 | return group(configSetters) 18 | } else { 19 | return () => config => config 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/core", 3 | "version": "2.1.0", 4 | "description": "Functional approach to compose your webpack config.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "keywords": [ 9 | "webpack", 10 | "webpack-blocks", 11 | "block", 12 | "feature", 13 | "functional" 14 | ], 15 | "engines": { 16 | "node": ">= 6.0" 17 | }, 18 | "repository": "andywer/webpack-blocks", 19 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 20 | "dependencies": { 21 | "glob-to-regexp": "^0.4.1", 22 | "lodash": "^4.17.15", 23 | "webpack-merge": "^4.2.2" 24 | }, 25 | "devDependencies": { 26 | "sinon": "^6.3.5", 27 | "webpack": "^4.20.2" 28 | }, 29 | "peerDependencies": { 30 | "webpack": "^4.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/dev-server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/dev-server - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | 7 | ## 1.0.0 8 | 9 | - Removed `proxy()` ([#251](https://github.com/andywer/webpack-blocks/pull/251)) 10 | 11 | ## 1.0.0-rc 12 | 13 | - Removed `reactHot()` ([#214](https://github.com/andywer/webpack-blocks/pull/214)) 14 | 15 | ## 1.0.0-beta.3 16 | 17 | - Update `webpack-dev-server` to v2.6.1, now compatible with webpack v3 18 | ([#179](https://github.com/andywer/webpack-blocks/issues/179)) 19 | - Less noisy default options 20 | 21 | ## 1.0.0-beta.2 22 | 23 | - Make `webpack` a peer dependency, make compatible with webpack v3 24 | ([#174](https://github.com/andywer/webpack-blocks/pull/174)) 25 | 26 | ## 1.0.0-beta 27 | 28 | - Make `reactHot` support `match()` 29 | 30 | ## 1.0.0-alpha 31 | 32 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 33 | - Requires node 6+ 34 | 35 | ## 0.4.0 36 | 37 | - Make `dev-server` composable (calling it multiple times in a single config works as expected; see 38 | [#78](https://github.com/andywer/webpack-blocks/pull/78)) 39 | 40 | ## 0.3.0 41 | 42 | Initial non-beta release. Aligned with the v0.3 release changes. 43 | 44 | ## 0.1.0-beta 45 | 46 | Initial beta release. 47 | -------------------------------------------------------------------------------- /packages/dev-server/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - Webpack Dev Server 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/dev-server.svg)](https://www.npmjs.com/package/@webpack-blocks/dev-server) 5 | 6 | This is the `dev-server` block providing webpack dev server configuration. 7 | 8 | ## Usage 9 | 10 | ```js 11 | const { createConfig, env } = require('@webpack-blocks/webpack') 12 | const devServer = require('@webpack-blocks/dev-server') 13 | 14 | module.exports = createConfig([ 15 | // use only if `NODE_ENV === 'development'`: 16 | env('development', [ 17 | devServer({ 18 | // Show full-screen overlay in the browser on compiler errors or warnings 19 | overlay: true, 20 | 21 | // If you need to proxy API requests: 22 | proxy: { 23 | '/api': { target: 'http://localhost:3000' } 24 | } 25 | }) 26 | ]) 27 | ]) 28 | ``` 29 | 30 | Don’t forget to enable hot reload in your app. If you’re using React it may look like this: 31 | 32 | ```js 33 | import React from 'react' 34 | import ReactDOM from 'react-dom' 35 | 36 | function render() { 37 | ReactDOM.render(

Hello, webpack-blocks!

, document.getElementById('root')) 38 | } 39 | 40 | render() 41 | 42 | if (module.hot) { 43 | module.hot.accept(render) 44 | } 45 | ``` 46 | 47 | ## webpack-blocks 48 | 49 | Check out the 50 | 51 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 52 | 53 | Released under the terms of the MIT license. 54 | -------------------------------------------------------------------------------- /packages/dev-server/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack dev-server block. 3 | * 4 | * @see https://github.com/webpack/webpack-dev-server 5 | */ 6 | 7 | module.exports = devServer 8 | 9 | /** 10 | * @param {object} [options] See https://webpack.js.org/configuration/dev-server/ 11 | * @param {string|string[]} [entry] 12 | * @return {Function} 13 | */ 14 | function devServer(options = {}, entry = []) { 15 | if (options && (typeof options === 'string' || Array.isArray(options))) { 16 | entry = options 17 | options = {} 18 | } 19 | 20 | if (!Array.isArray(entry)) { 21 | entry = entry ? [entry] : [] 22 | } 23 | 24 | const setter = context => prevConfig => { 25 | context.devServer = context.devServer || { entry: [], options: {} } 26 | context.devServer.entry = context.devServer.entry.concat(entry) 27 | context.devServer.options = Object.assign({}, context.devServer.options, options) 28 | 29 | return prevConfig 30 | } 31 | 32 | return Object.assign(setter, { post: postConfig }) 33 | } 34 | 35 | function postConfig(context, util) { 36 | const entryPointsToAdd = context.devServer.entry 37 | 38 | return prevConfig => { 39 | return util.merge({ 40 | devServer: Object.assign( 41 | { 42 | hot: true, 43 | hotOnly: true, 44 | historyApiFallback: true, 45 | inline: true, 46 | // Disable verbose logging in browser’s console, only print errors 47 | clientLogLevel: 'error', 48 | // Do not print chunks list on every compilation, only print errors 49 | stats: 'errors-only' 50 | }, 51 | context.devServer.options 52 | ), 53 | entry: addDevEntryToAll(prevConfig.entry || {}, entryPointsToAdd), 54 | plugins: [new context.webpack.HotModuleReplacementPlugin()] 55 | })(prevConfig) 56 | } 57 | } 58 | 59 | function addDevEntryToAll(presentEntryPoints, devServerEntry) { 60 | const newEntryPoints = {} 61 | 62 | Object.keys(presentEntryPoints).forEach(chunkName => { 63 | // It's fine to just set the `devServerEntry`, instead of concat()-ing the present ones. 64 | // Will be concat()-ed by webpack-merge (see `createConfig()`) 65 | newEntryPoints[chunkName] = devServerEntry 66 | }) 67 | 68 | return newEntryPoints 69 | } 70 | -------------------------------------------------------------------------------- /packages/dev-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/dev-server", 3 | "version": "2.1.0", 4 | "description": "Webpack block for the webpack dev server.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "engines": { 9 | "node": ">= 6.11.5" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "webpack": "^4.42.1", 19 | "webpack-dev-server": "^3.10.3" 20 | }, 21 | "peerDependencies": { 22 | "@webpack-blocks/core": "^2.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/eslint/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/eslint - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | 7 | ## 1.0.0-rc 8 | 9 | Initial release. 10 | -------------------------------------------------------------------------------- /packages/eslint/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - ESLint 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/eslint.svg)](https://www.npmjs.com/package/@webpack-blocks/eslint) 5 | 6 | This is the `eslint` block providing JavaScript linting support for webpack. Uses `eslint` via 7 | `eslint-loader`. 8 | 9 | ## Usage 10 | 11 | 12 | ```js 13 | const { createConfig } = require('@webpack-blocks/webpack') 14 | const eslint = require('@webpack-blocks/eslint') 15 | 16 | module.exports = createConfig([ 17 | eslint(/* eslint options */) 18 | ]) 19 | ``` 20 | 21 | 22 | Use `match()` to explicitly specify the files to lint. 23 | 24 | ## Options 25 | 26 | You can pass any `eslint-loader` options as an object to the `eslint` block. See 27 | [eslint-loader options](https://github.com/webpack-contrib/eslint-loader#options). 28 | 29 | ## webpack-blocks 30 | 31 | Check out the 32 | 33 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 34 | 35 | Released under the terms of the MIT license. 36 | -------------------------------------------------------------------------------- /packages/eslint/__tests__/eslint.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import eslint from '../index' 4 | 5 | test('ESLint default options work', t => { 6 | const config = createConfig({}, [eslint()]) 7 | 8 | t.deepEqual(config.module.rules, [ 9 | { 10 | test: /\.(js|jsx)$/, 11 | exclude: /node_modules/, 12 | enforce: 'pre', 13 | use: [{ loader: 'eslint-loader', options: {} }] 14 | } 15 | ]) 16 | }) 17 | 18 | test('ESLint options work', t => { 19 | const config = createConfig({}, [eslint({ fix: true })]) 20 | 21 | t.deepEqual(config.module.rules, [ 22 | { 23 | test: /\.(js|jsx)$/, 24 | exclude: /node_modules/, 25 | enforce: 'pre', 26 | use: [ 27 | { 28 | loader: 'eslint-loader', 29 | options: { fix: true } 30 | } 31 | ] 32 | } 33 | ]) 34 | }) 35 | 36 | test('using custom match() works', t => { 37 | const config = createConfig({}, [match('*.js', { exclude: null }, [eslint({ fix: true })])]) 38 | 39 | t.deepEqual(config.module.rules, [ 40 | { 41 | test: /^.*\.js$/, 42 | exclude: null, 43 | enforce: 'pre', 44 | use: [ 45 | { 46 | loader: 'eslint-loader', 47 | options: { fix: true } 48 | } 49 | ] 50 | } 51 | ]) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/eslint/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ESLint webpack block. 3 | * 4 | * @see https://github.com/webpack-contrib/eslint-loader 5 | */ 6 | 7 | module.exports = eslint 8 | 9 | /** 10 | * @param {object} [options] See https://github.com/webpack-contrib/eslint-loader#options 11 | * @return {Function} 12 | */ 13 | function eslint(options = {}) { 14 | return (context, util) => 15 | util.addLoader( 16 | Object.assign( 17 | { 18 | test: /\.(js|jsx)$/, 19 | exclude: /node_modules/, 20 | enforce: 'pre', 21 | use: [{ loader: 'eslint-loader', options }] 22 | }, 23 | context.match 24 | ) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /packages/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/eslint", 3 | "version": "2.1.0", 4 | "description": "Webpack block for JavaScript linting.", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": "Dmytro Meleshko ", 8 | "engines": { 9 | "node": ">= 10.13.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "eslint-loader": "^4.0.0" 19 | }, 20 | "devDependencies": { 21 | "@webpack-blocks/core": "^2.1.0", 22 | "webpack": "^4.20.2" 23 | }, 24 | "peerDependencies": { 25 | "@webpack-blocks/core": "^2.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/extract-text/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/extract-text - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Remove deprecated `fileType` API ([#260](https://github.com/andywer/webpack-blocks/issues/260)) 6 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 7 | 8 | ## 1.0.0-rc 9 | 10 | - Breaking change: drop webpack 2 support, update extract-text-webpack-plugin to v3 11 | 12 | ## 1.0.0-beta.2 13 | 14 | - Update dependency version 15 | 16 | ## 1.0.0-beta 17 | 18 | - Use `match()` instead of `fileType` option 19 | 20 | ## 1.0.0-alpha 21 | 22 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 23 | - Requires node 6+ 24 | 25 | ## 0.4.0 26 | 27 | - Using updated `@webpack-blocks/webpack-common` to work with updated `webpack-merge` 28 | 29 | ## 0.3.0 30 | 31 | Initial non-beta release. Aligned with the v0.3 release changes. 32 | 33 | ## 0.1.0-beta 34 | 35 | Initial beta release. 36 | -------------------------------------------------------------------------------- /packages/extract-text/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - Extract text 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/extract-text.svg)](https://www.npmjs.com/package/@webpack-blocks/extract-text) 5 | 6 | Use this block to extract text (for example CSS) from the bundle into a separate file. Uses 7 | [Extract Text Plugin](https://github.com/webpack/extract-text-webpack-plugin). 8 | 9 | ## Installation 10 | 11 | ```sh 12 | # for webpack v3 13 | npm install --save-dev @webpack-blocks/extract-text 14 | # for webpack v2 install the last v1.0 beta version 15 | npm install --save-dev @webpack-blocks/extract-text@^1.0.0-beta.2 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```js 21 | const { createConfig } = require('@webpack-blocks/webpack') 22 | const extractText = require('@webpack-blocks/extract-text') 23 | 24 | module.exports = createConfig([ 25 | /* ... */ 26 | extractText('path/to/output.file') 27 | ]) 28 | ``` 29 | 30 | Most likely you’ll use it to extract styles: 31 | 32 | ```js 33 | const { createConfig, match, env } = require('@webpack-blocks/webpack') 34 | const { css } = require('@webpack-blocks/assets') 35 | const extractText = require('@webpack-blocks/extract-text') 36 | 37 | module.exports = createConfig([ 38 | match('*.css', [ 39 | css(), 40 | // Filename defaults to 'css/[name].[contenthash:8].css' 41 | // Extract styles only in production mode 42 | // to keep styles hot reload in development 43 | env('production', [extractText()]) 44 | ]) 45 | ]) 46 | ``` 47 | 48 | ## webpack-blocks 49 | 50 | Check out the 51 | 52 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 53 | 54 | Released under the terms of the MIT license. 55 | -------------------------------------------------------------------------------- /packages/extract-text/__tests__/integration.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createConfig, match } from '@webpack-blocks/core' 3 | import ExtractTextPlugin from 'extract-text-webpack-plugin' 4 | import extractText from '../index' 5 | 6 | test('works with css() without match()', t => { 7 | const config = createConfig({}, [css(), extractText('css/[name].css')]) 8 | 9 | const id = config.plugins[0].id 10 | const plugin = Object.assign(new ExtractTextPlugin('css/[name].css'), { id }) 11 | 12 | t.deepEqual(config.plugins, [plugin]) 13 | t.deepEqual(config.module.rules, [ 14 | { 15 | test: /\.css$/, 16 | use: plugin.extract({ 17 | fallback: ['style-loader'], 18 | use: ['css-loader'] 19 | }) 20 | } 21 | ]) 22 | }) 23 | 24 | test('works with css() and match()', t => { 25 | const config = createConfig({}, [ 26 | match('*.css', { exclude: /node_modules/ }, [css(), extractText()]) 27 | ]) 28 | 29 | const id = config.plugins[0].id 30 | const plugin = Object.assign(new ExtractTextPlugin('css/[name].[contenthash:8].css'), { id }) 31 | 32 | t.deepEqual(config.plugins, [plugin]) 33 | t.deepEqual(config.module.rules, [ 34 | { 35 | test: /^.*\.css$/, 36 | exclude: /node_modules/, 37 | use: plugin.extract({ 38 | fallback: ['style-loader'], 39 | use: ['css-loader'] 40 | }) 41 | } 42 | ]) 43 | }) 44 | 45 | test('works with sass()', t => { 46 | const config = createConfig({}, [match('*.scss', [sass(), extractText('css/styles.css')])]) 47 | 48 | const id = config.plugins[0].id 49 | const plugin = Object.assign(new ExtractTextPlugin('css/styles.css'), { id }) 50 | 51 | t.deepEqual(config.plugins, [plugin]) 52 | t.deepEqual(config.module.rules, [ 53 | { 54 | test: /^.*\.scss$/, 55 | use: plugin.extract({ 56 | fallback: ['style-loader'], 57 | use: ['css-loader', 'sass-loader'] 58 | }) 59 | } 60 | ]) 61 | }) 62 | 63 | test('works with html()', t => { 64 | const config = createConfig({}, [match('*.html', [html(), extractText('build/layout.html')])]) 65 | 66 | const id = config.plugins[0].id 67 | const plugin = Object.assign(new ExtractTextPlugin('build/layout.html'), { id }) 68 | 69 | t.deepEqual(config.plugins, [plugin]) 70 | t.deepEqual(config.module.rules, [ 71 | { 72 | test: /^.*\.html$/, 73 | use: plugin.extract({ 74 | fallback: [], 75 | use: ['html-loader'] 76 | }) 77 | } 78 | ]) 79 | }) 80 | 81 | test('fails properly if nothing to extract can be found', t => { 82 | t.throws(() => createConfig({}, [extractText()]), /No loaders found to extract contents from/) 83 | }) 84 | 85 | function css() { 86 | return (context, util) => 87 | util.addLoader( 88 | Object.assign( 89 | { 90 | test: /\.css$/, 91 | use: ['style-loader', 'css-loader'] 92 | }, 93 | context.match 94 | ) 95 | ) 96 | } 97 | 98 | function sass() { 99 | return (context, util) => 100 | util.addLoader( 101 | Object.assign( 102 | { 103 | test: /\.(sass|scss)$/, 104 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }, { loader: 'sass-loader' }] 105 | }, 106 | context.match 107 | ) 108 | ) 109 | } 110 | 111 | function html() { 112 | return (context, util) => 113 | util.addLoader( 114 | Object.assign( 115 | { 116 | test: /\.html$/, 117 | use: ['html-loader'] 118 | }, 119 | context.match 120 | ) 121 | ) 122 | } 123 | -------------------------------------------------------------------------------- /packages/extract-text/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extract-text webpack block. 3 | * 4 | * @see https://github.com/webpack/extract-text-webpack-plugin 5 | */ 6 | 7 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | 9 | module.exports = extractText 10 | 11 | /** 12 | * @param {string} outputFilePattern 13 | * @return {Function} 14 | */ 15 | function extractText(outputFilePattern = 'css/[name].[contenthash:8].css') { 16 | const plugin = new ExtractTextPlugin(outputFilePattern) 17 | 18 | const postHook = (context, util) => prevConfig => { 19 | let nextConfig = prevConfig 20 | 21 | // Only apply to loaders in the same `match()` group or css loaders if there is no `match()` 22 | const ruleToMatch = context.match || { test: /\.css$/ } 23 | const matchingLoaderRules = getMatchingLoaderRules(ruleToMatch, prevConfig) 24 | 25 | if (matchingLoaderRules.length === 0) { 26 | throw new Error( 27 | `extractText(): No loaders found to extract contents from. Looking for loaders matching ${ 28 | ruleToMatch.test 29 | }` 30 | ) 31 | } 32 | 33 | const [fallbackLoaders, nonFallbackLoaders] = splitFallbackRule(matchingLoaderRules) 34 | 35 | const newLoaderDef = Object.assign({}, ruleToMatch, { 36 | use: plugin.extract({ 37 | fallback: fallbackLoaders, 38 | use: nonFallbackLoaders 39 | }) 40 | }) 41 | 42 | for (const ruleToRemove of matchingLoaderRules) { 43 | nextConfig = removeLoaderRule(ruleToRemove)(nextConfig) 44 | } 45 | 46 | nextConfig = util.addPlugin(plugin)(nextConfig) 47 | nextConfig = util.addLoader(newLoaderDef)(nextConfig) 48 | 49 | return nextConfig 50 | } 51 | 52 | return Object.assign(() => prevConfig => prevConfig, { post: postHook }) 53 | } 54 | 55 | function getMatchingLoaderRules(ruleToMatch, webpackConfig) { 56 | return webpackConfig.module.rules.filter( 57 | rule => 58 | isLoaderConditionMatching(rule.test, ruleToMatch.test) && 59 | isLoaderConditionMatching(rule.exclude, ruleToMatch.exclude) && 60 | isLoaderConditionMatching(rule.include, ruleToMatch.include) 61 | ) 62 | } 63 | 64 | function splitFallbackRule(rules) { 65 | const leadingStyleLoaderInAllRules = rules.every(rule => { 66 | return ( 67 | rule.use.length > 0 && 68 | rule.use[0] && 69 | (rule.use[0] === 'style-loader' || rule.use[0].loader === 'style-loader') 70 | ) 71 | }) 72 | 73 | if (leadingStyleLoaderInAllRules) { 74 | const trimmedRules = rules.map(rule => Object.assign({}, rule, { use: rule.use.slice(1) })) 75 | return [['style-loader'], getUseEntriesFromRules(trimmedRules)] 76 | } else { 77 | return [[], getUseEntriesFromRules(rules)] 78 | } 79 | } 80 | 81 | function getUseEntriesFromRules(rules) { 82 | const normalizeUseEntry = use => (typeof use === 'string' ? { loader: use } : use) 83 | 84 | return rules.reduce((useEntries, rule) => useEntries.concat(rule.use.map(normalizeUseEntry)), []) 85 | } 86 | 87 | /** 88 | * @param {object} rule Remove all loaders that match this loader rule. 89 | * @return {Function} 90 | */ 91 | function removeLoaderRule(rule) { 92 | return prevConfig => { 93 | const newRules = prevConfig.module.rules.filter( 94 | prevRule => 95 | !( 96 | isLoaderConditionMatching(prevRule.test, rule.test) && 97 | isLoaderConditionMatching(prevRule.include, rule.include) && 98 | isLoaderConditionMatching(prevRule.exclude, rule.exclude) 99 | ) 100 | ) 101 | 102 | return Object.assign({}, prevConfig, { 103 | module: Object.assign({}, prevConfig.module, { 104 | rules: newRules 105 | }) 106 | }) 107 | } 108 | } 109 | 110 | function isLoaderConditionMatching(test1, test2) { 111 | if (test1 === test2) { 112 | return true 113 | } else if (typeof test1 !== typeof test2) { 114 | return false 115 | } else if (test1 instanceof RegExp && test2 instanceof RegExp) { 116 | return test1 === test2 || String(test1) === String(test2) 117 | } else if (Array.isArray(test1) && Array.isArray(test2)) { 118 | return areArraysMatching(test1, test2) 119 | } 120 | return false 121 | } 122 | 123 | function areArraysMatching(array1, array2) { 124 | if (array1.length !== array2.length) { 125 | return false 126 | } 127 | 128 | return array1.every( 129 | item1 => 130 | array2.indexOf(item1) >= 0 || 131 | (item1 instanceof RegExp && 132 | array2.find(item2 => item2 instanceof RegExp && String(item1) === String(item2))) 133 | ) 134 | } 135 | -------------------------------------------------------------------------------- /packages/extract-text/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/extract-text", 3 | "version": "2.1.0", 4 | "description": "Webpack block for the Extract Text Plugin.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "engines": { 9 | "node": ">= 6.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "extract-text-webpack-plugin": "^4.0.0-beta.0" 19 | }, 20 | "devDependencies": { 21 | "@webpack-blocks/core": "^2.1.0", 22 | "webpack": "^4.20.2" 23 | }, 24 | "peerDependencies": { 25 | "@webpack-blocks/core": "^2.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/postcss/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/postcss - Changelog 2 | 3 | ## 2.0.0-alpha.1 4 | 5 | - Major version upgrade of `postcss-loader` to the 6 | [3.0.0](https://github.com/postcss/postcss-loader/releases/tag/v3.0.0) 7 | ([#291](https://github.com/andywer/webpack-blocks/pull/291)) 8 | - Deprecate `minimize` option after 9 | [the css-loader removed it](https://github.com/webpack-contrib/css-loader/releases/tag/v1.0.0) 10 | ([#291](https://github.com/andywer/webpack-blocks/pull/291)) 11 | - Remove `css-loader` and `style-loader` from the `use` option 12 | ([#293](https://github.com/andywer/webpack-blocks/pull/293)) 13 | 14 | ## 2.0.0-alpha 15 | 16 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 17 | 18 | ## 1.0.0-rc 19 | 20 | - Breaking change: Remove `plugins` argument 21 | - Add `minimize` option 22 | 23 | ## 1.0.0-beta 24 | 25 | - Use `match()` instead of `exclude`/`include` option 26 | 27 | ## 1.0.0-alpha 28 | 29 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 30 | - Requires node 6+ 31 | 32 | ## 0.4.3 33 | 34 | - Bug fix: Fix loading of `postcss.config.js` 35 | ([#137](https://github.com/andywer/webpack-blocks/pull/137)) 36 | 37 | ## 0.4.2 38 | 39 | - Bug fix: Fix `postcss`/`sass` crash when using webpack 2 and source maps 40 | ([#116](https://github.com/andywer/webpack-blocks/issues/116)) 41 | 42 | ## 0.4.1 43 | 44 | - Made `plugins` parameter optional ([#112](https://github.com/andywer/webpack-blocks/issues/112)) 45 | 46 | ## 0.4.0 47 | 48 | - Breaking change: Dropped the default `node_modules/` exclusion 49 | 50 | ## 0.3.2 51 | 52 | - Bug fix: PostCSS plugin configuration now works with webpack 2 53 | ([#68](https://github.com/andywer/webpack-blocks/issues/68)) 54 | 55 | ## 0.3.1 56 | 57 | - Supporting custom PostCSS options now (`parser`, `stringifier`, `syntax`) 58 | 59 | ## 0.3.0 60 | 61 | - Adapted to new API: Using `context` now 62 | 63 | ## 0.2.0 64 | 65 | - Fix loader order issue 66 | - Upgraded to postcss-loader v1.2.0 which supports reading the `postcss.config.js` file 67 | 68 | ## 0.1.1 69 | 70 | - `loaders: [ 'postcss' ]` => `loaders: [ 'postcss-loader' ]` 71 | 72 | ## 0.1.0 73 | 74 | Initial release. 75 | -------------------------------------------------------------------------------- /packages/postcss/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - PostCSS 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/postcss.svg)](https://www.npmjs.com/package/@webpack-blocks/postcss) 5 | 6 | This is the `postcss` block providing [PostCSS](http://postcss.org/) configuration. 7 | 8 | ## Usage 9 | 10 | 11 | ```js 12 | const { createConfig, match } = require('@webpack-blocks/webpack') 13 | const { css } = require('@webpack-blocks/assets') 14 | const postcss = require('@webpack-blocks/postcss') 15 | 16 | module.exports = createConfig([ 17 | match(['*.css', '!*node_modules*'], [ 18 | css(), 19 | postcss() 20 | ]) 21 | ]) 22 | ``` 23 | 24 | 25 | **NOTE**: the postcss block handles only the postcss transformation, you must use the css block if 26 | you want to require the css in your browser. 27 | 28 | We recommend you to configure PostCSS using the `postcss.config.js` file (see 29 | [PostCSS loader usage](https://github.com/postcss/postcss-loader#usage)): 30 | 31 | ```js 32 | // postcss.config.js 33 | const autoprefixer = require('autoprefixer') 34 | 35 | module.exports = { 36 | plugins: [autoprefixer({ browsers: ['last 2 versions'] })] 37 | } 38 | ``` 39 | 40 | But you can pass configuration directly to the `postcss` block: 41 | 42 | ```js 43 | const { createConfig, match } = require('@webpack-blocks/webpack') 44 | const { css } = require('@webpack-blocks/assets') 45 | const postcss = require('@webpack-blocks/postcss') 46 | const autoprefixer = require('autoprefixer') 47 | 48 | module.exports = createConfig([ 49 | css(), 50 | postcss({ 51 | plugins: [autoprefixer({ browsers: ['last 2 versions'] })] 52 | /* Other PostCSS options */ 53 | }) 54 | ]) 55 | ``` 56 | 57 | ## Options 58 | 59 | ### PostCSS options 60 | 61 | #### parser _(optional)_ 62 | 63 | Package name of a custom PostCSS parser to use. Pass for instance `'sugarss'` to be able to write 64 | indent-based CSS. 65 | 66 | #### stringifier _(optional)_ 67 | 68 | Package name of a custom PostCSS stringifier to use. 69 | 70 | #### syntax _(optional)_ 71 | 72 | Package name of a custom PostCSS syntax to use. The package must export a `parse` and a `stringify` 73 | function. 74 | 75 | #### plugins _(optional)_ 76 | 77 | Array of PostCSS plugins. 78 | 79 | ## webpack-blocks 80 | 81 | Check out the 82 | 83 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 84 | 85 | Released under the terms of the MIT license. 86 | -------------------------------------------------------------------------------- /packages/postcss/__tests__/integration.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { css } from '@webpack-blocks/assets' 3 | import { createConfig, match } from '@webpack-blocks/core' 4 | import postcss from '../index' 5 | 6 | test('Postcss works with defaults, without match()', t => { 7 | const config = createConfig({}, [postcss()]) 8 | 9 | t.deepEqual(config.module.rules, [ 10 | { 11 | test: /\.css$/, 12 | use: [ 13 | { 14 | loader: 'postcss-loader', 15 | options: {} 16 | } 17 | ] 18 | } 19 | ]) 20 | }) 21 | 22 | test('Postcss works with css() & match()', t => { 23 | const config = createConfig({}, [match('*.css', { exclude: /node_modules/ }, [css(), postcss()])]) 24 | 25 | t.deepEqual(config.module.rules, [ 26 | { 27 | test: /^.*\.css$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'style-loader', 32 | options: {} 33 | }, 34 | { 35 | loader: 'css-loader', 36 | options: {} 37 | }, 38 | { 39 | loader: 'postcss-loader', 40 | options: {} 41 | } 42 | ] 43 | } 44 | ]) 45 | }) 46 | 47 | test('Postcss allows inline plugin config and custom options', t => { 48 | const fakePostcssPlugin = { id: 'fakePostcssPlugin' } 49 | 50 | const config = createConfig({}, [ 51 | postcss({ 52 | plugins: [fakePostcssPlugin], 53 | parser: 'sugarss' 54 | }) 55 | ]) 56 | 57 | t.deepEqual(config.module.rules, [ 58 | { 59 | test: /\.css$/, 60 | use: [ 61 | { 62 | loader: 'postcss-loader', 63 | options: { 64 | plugins: [fakePostcssPlugin], 65 | parser: 'sugarss' 66 | } 67 | } 68 | ] 69 | } 70 | ]) 71 | }) 72 | -------------------------------------------------------------------------------- /packages/postcss/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PostCSS webpack block. 3 | * 4 | * @see https://github.com/postcss/postcss-loader 5 | */ 6 | 7 | module.exports = postcss 8 | 9 | /** 10 | * @param {object} [options] https://github.com/postcss/postcss-loader#options 11 | * @param {string} [options.parser] Package name of custom PostCSS parser to use. 12 | * @param {string} [options.stringifier] Package name of custom PostCSS stringifier to use. 13 | * @param {string} [options.syntax] Package name of custom PostCSS parser/stringifier to use. 14 | * @param {PostCSSPlugin[]} [options.plugins] PostCSS plugins, will read `postcss.config.js` file if not supplied. 15 | * @return {Function} 16 | */ 17 | function postcss(options = {}) { 18 | if (Array.isArray(options)) { 19 | throw Error( 20 | 'Passing PostCSS plugins as a first argument is not supported anymore, use options.plugins instead' 21 | ) 22 | } 23 | 24 | return (context, util) => prevConfig => { 25 | const ruleDef = Object.assign( 26 | { 27 | test: /\.css$/, 28 | use: [ 29 | { 30 | loader: 'postcss-loader', 31 | options 32 | } 33 | ] 34 | }, 35 | context.match 36 | ) 37 | 38 | let nextConfig = util.addLoader(ruleDef)(prevConfig) 39 | 40 | return nextConfig 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/postcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/postcss", 3 | "version": "2.1.0", 4 | "description": "Webpack block for PostCSS.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "engines": { 9 | "node": ">= 6.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "lodash": "^4.17.15", 19 | "postcss-loader": "^3.0.0" 20 | }, 21 | "devDependencies": { 22 | "@webpack-blocks/assets": "^2.1.0", 23 | "@webpack-blocks/core": "^2.1.0" 24 | }, 25 | "peerDependencies": { 26 | "@webpack-blocks/core": "^2.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/sample-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "@babel/preset-env", { "modules": false } ], 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ "react-hot-loader/babel" ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/sample-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:react/recommended" 4 | ], 5 | "env": { 6 | "browser": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": 8 10 | }, 11 | "settings": { 12 | "react": { 13 | "version": "15.6" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/sample-app/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - Test app 2 | 3 | A very basic React app featuring Babel and HMR. Performs all the nifty optimizations on the 4 | production build. Check out how clean a [webpack config](./webpack.config.babel.js) can look like. 5 | 6 | ## Installation 7 | 8 | ```sh 9 | git clone https://github.com/andywer/webpack-blocks.git 10 | yarn install 11 | cd packages/sample-app 12 | ``` 13 | 14 | ## Usage 15 | 16 | To run the webpack dev server: 17 | 18 | ```sh 19 | yarn start 20 | ``` 21 | 22 | To create a production build: 23 | 24 | ```sh 25 | yarn build 26 | ``` 27 | -------------------------------------------------------------------------------- /packages/sample-app/__tests__/__snapshots__/webpack-config.test.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `packages/sample-app/__tests__/webpack-config.test.js` 2 | 3 | The actual snapshot is saved in `webpack-config.test.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## it exports the development config 8 | 9 | > Snapshot 1 10 | 11 | { 12 | devServer: { 13 | clientLogLevel: 'error', 14 | historyApiFallback: true, 15 | hot: true, 16 | hotOnly: true, 17 | inline: true, 18 | proxy: { 19 | '/api/*': { 20 | target: 'http://localhost:4000', 21 | }, 22 | }, 23 | stats: 'errors-only', 24 | }, 25 | devtool: 'cheap-module-eval-source-map', 26 | entry: { 27 | main: [ 28 | './src/index.dev.js', 29 | ], 30 | }, 31 | mode: 'development', 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.(ts|tsx)$/, 36 | use: [ 37 | { 38 | loader: 'awesome-typescript-loader', 39 | options: { 40 | configFileName: '', 41 | }, 42 | }, 43 | ], 44 | }, 45 | { 46 | test: /\.css$/, 47 | use: [ 48 | { 49 | loader: 'style-loader', 50 | options: {}, 51 | }, 52 | { 53 | loader: 'css-loader', 54 | options: { 55 | importLoaders: 1, 56 | modules: { 57 | localIdentName: '[name]--[local]--[hash:base64:5]', 58 | }, 59 | }, 60 | }, 61 | ], 62 | }, 63 | { 64 | exclude: /node_modules/, 65 | test: /\.(js|jsx)$/, 66 | use: [ 67 | { 68 | loader: 'babel-loader', 69 | options: { 70 | cacheDirectory: true, 71 | }, 72 | }, 73 | ], 74 | }, 75 | ], 76 | }, 77 | performance: { 78 | maxAssetSize: 1500000, 79 | maxEntrypointSize: 1500000, 80 | }, 81 | plugins: [ 82 | 'CheckerPlugin', 83 | 'PathPlugin', 84 | 'HtmlWebpackPlugin', 85 | 'HotModuleReplacementPlugin', 86 | ], 87 | resolve: { 88 | extensions: [ 89 | '.js', 90 | '.json', 91 | '.ts', 92 | '.tsx', 93 | ], 94 | }, 95 | stats: { 96 | children: false, 97 | chunks: false, 98 | modules: false, 99 | reasons: false, 100 | }, 101 | } 102 | 103 | ## it exports the production config 104 | 105 | > Snapshot 1 106 | 107 | { 108 | entry: { 109 | main: [ 110 | './src/index.js', 111 | ], 112 | }, 113 | mode: 'production', 114 | module: { 115 | rules: [ 116 | { 117 | test: /\.(ts|tsx)$/, 118 | use: [ 119 | { 120 | loader: 'awesome-typescript-loader', 121 | options: { 122 | configFileName: '', 123 | }, 124 | }, 125 | ], 126 | }, 127 | { 128 | exclude: /node_modules/, 129 | test: /\.(js|jsx)$/, 130 | use: [ 131 | { 132 | loader: 'babel-loader', 133 | options: { 134 | cacheDirectory: true, 135 | }, 136 | }, 137 | ], 138 | }, 139 | { 140 | test: /\.css$/, 141 | use: [ 142 | { 143 | loader: 'loader.js', 144 | options: { 145 | id: 2, 146 | omit: 1, 147 | remove: true, 148 | }, 149 | }, 150 | { 151 | loader: 'style-loader', 152 | }, 153 | { 154 | loader: 'css-loader', 155 | options: { 156 | importLoaders: 1, 157 | modules: { 158 | localIdentName: '[hash:base64:10]', 159 | }, 160 | }, 161 | }, 162 | { 163 | loader: 'postcss-loader', 164 | options: { 165 | plugins: [ 166 | Function { 167 | postcssPlugin: 'cssnano', 168 | postcssVersion: '7.0.5', 169 | }, 170 | ], 171 | }, 172 | }, 173 | ], 174 | }, 175 | ], 176 | }, 177 | output: { 178 | filename: 'bundle.js', 179 | path: 'build', 180 | }, 181 | plugins: [ 182 | 'CheckerPlugin', 183 | 'PathPlugin', 184 | 'HtmlWebpackPlugin', 185 | 'ExtractTextPlugin', 186 | ], 187 | resolve: { 188 | extensions: [ 189 | '.js', 190 | '.json', 191 | '.ts', 192 | '.tsx', 193 | ], 194 | }, 195 | stats: { 196 | children: false, 197 | chunks: false, 198 | modules: false, 199 | reasons: false, 200 | }, 201 | } 202 | -------------------------------------------------------------------------------- /packages/sample-app/__tests__/__snapshots__/webpack-config.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andywer/webpack-blocks/5f0605b8c00b3c15929b035ed70e45ee9c84934a/packages/sample-app/__tests__/__snapshots__/webpack-config.test.js.snap -------------------------------------------------------------------------------- /packages/sample-app/__tests__/webpack-config.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import rimraf from 'rimraf' 4 | import execa from 'execa' 5 | import test from 'ava' 6 | 7 | const configFile = require.resolve('../webpack.config.babel') 8 | 9 | test('it exports the development config', t => { 10 | const { NODE_ENV } = process.env 11 | process.env.NODE_ENV = 'development' 12 | 13 | const originalConfig = requireConfig() 14 | const module = Object.assign({}, originalConfig.module, { 15 | rules: originalConfig.module.rules.map(rule => { 16 | if (rule.test.toString() === /\.(ts|tsx)$/.toString()) { 17 | return Object.assign({}, rule, { 18 | use: rule.use.map(use => 19 | Object.assign({}, use, { 20 | options: { configFileName: '' } 21 | }) 22 | ) 23 | }) 24 | } 25 | 26 | return rule 27 | }) 28 | }) 29 | const config = Object.assign({}, originalConfig, { 30 | module, 31 | plugins: originalConfig.plugins.map(p => p.constructor.name) 32 | }) 33 | t.snapshot(config) 34 | 35 | process.env.NODE_ENV = NODE_ENV 36 | }) 37 | 38 | test('it exports the production config', t => { 39 | const { NODE_ENV } = process.env 40 | process.env.NODE_ENV = 'production' 41 | 42 | delete require.cache['../webpack.config.babel.js'] 43 | const originalConfig = requireConfig() 44 | const module = Object.assign({}, originalConfig.module, { 45 | rules: originalConfig.module.rules.map(rule => { 46 | if (rule.test.toString() === /\.css$/.toString()) { 47 | return Object.assign({}, rule, { 48 | use: rule.use.map(use => 49 | Object.assign({}, use, { 50 | loader: path.basename(use.loader) 51 | }) 52 | ) 53 | }) 54 | } 55 | 56 | if (rule.test.toString() === /\.(ts|tsx)$/.toString()) { 57 | return Object.assign({}, rule, { 58 | use: rule.use.map(use => 59 | Object.assign({}, use, { 60 | options: { configFileName: '' } 61 | }) 62 | ) 63 | }) 64 | } 65 | 66 | return rule 67 | }) 68 | }) 69 | const output = Object.assign({}, originalConfig.output, { 70 | path: path.basename(originalConfig.output.path) 71 | }) 72 | const config = Object.assign({}, originalConfig, { 73 | plugins: originalConfig.plugins.map(p => p.constructor.name), 74 | module, 75 | output 76 | }) 77 | t.snapshot(config) 78 | 79 | process.env.NODE_ENV = NODE_ENV 80 | }) 81 | 82 | test('it builds', async t => { 83 | const buildLocation = path.resolve(__dirname, '../build') 84 | rimraf(buildLocation, () => {}) 85 | await execa('yarn', ['build'], { cwd: path.resolve(__dirname, '..') }) 86 | 87 | testHtml() 88 | testCss() 89 | testJs() 90 | 91 | function testHtml() { 92 | const html = fs.readFileSync(path.resolve(buildLocation, 'index.html'), { encoding: 'utf8' }) 93 | const re = // 94 | 95 | t.true(re.test(html)) 96 | t.true(html.includes('')) 97 | } 98 | 99 | function testJs() { 100 | const js = fs.readFileSync(path.resolve(buildLocation, 'bundle.js'), { encoding: 'utf8' }) 101 | t.true(js.includes('No content here. We only test the build process 😉')) 102 | } 103 | 104 | function testCss() { 105 | const files = fs.readdirSync(path.resolve(buildLocation, 'css'), { encoding: 'utf8' }) 106 | t.is(files.length, 1) 107 | 108 | const css = fs.readFileSync(path.resolve(buildLocation, 'css', files[0]), { encoding: 'utf8' }) 109 | t.true(css.startsWith('.')) 110 | t.true(css.endsWith('{margin:30px auto;text-align:center}')) 111 | } 112 | }) 113 | 114 | function requireConfig() { 115 | if (require.cache[configFile]) { 116 | delete require.cache[configFile] 117 | } 118 | return require(configFile) 119 | } 120 | -------------------------------------------------------------------------------- /packages/sample-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo 7 | 8 | 9 |
10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/sample-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-app", 3 | "private": true, 4 | "version": "2.1.0", 5 | "scripts": { 6 | "build": "cross-env NODE_ENV=production webpack --config webpack.config.babel.js", 7 | "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.babel.js" 8 | }, 9 | "engines": { 10 | "node": ">= 6.0" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.1.2", 14 | "@babel/preset-env": "^7.1.0", 15 | "@babel/preset-react": "^7.0.0", 16 | "@types/node": "^10.11.6", 17 | "@types/react": "^16.4.16", 18 | "cross-env": "^5.2.0", 19 | "cssnano": "^4.1.4", 20 | "eslint-plugin-react": "^7.11.1", 21 | "execa": "^1.0.0", 22 | "html-webpack-plugin": "^3.2.0", 23 | "react": "^15.6.2", 24 | "react-dom": "^15.6.2", 25 | "react-hot-loader": "^3.1.3", 26 | "rimraf": "^2.6.2", 27 | "typescript": "^3.1.2", 28 | "webpack": "^4.20.2", 29 | "webpack-blocks": "^2.1.0", 30 | "webpack-cli": "^3.1.2", 31 | "webpack-dev-server": "^3.1.9" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/sample-app/src/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | margin: 30px auto; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /packages/sample-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | const styles = require('./App.css') 3 | 4 | const App: (() => JSX.Element) = () => ( 5 |
No content here. We only test the build process 😉
6 | ) 7 | 8 | export default App 9 | -------------------------------------------------------------------------------- /packages/sample-app/src/index.dev.js: -------------------------------------------------------------------------------- 1 | import 'react-hot-loader/patch' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import { AppContainer } from 'react-hot-loader' 6 | import App from './App' 7 | 8 | const mountPoint = document.getElementById('root') 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | mountPoint 14 | ) 15 | 16 | if (module.hot) { 17 | module.hot.accept('./App', () => { 18 | const NewApp = require('./App').default 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | mountPoint 25 | ) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /packages/sample-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | const mountPoint = document.getElementById('root') 6 | ReactDOM.render( 7 |
8 | 9 |
, 10 | mountPoint 11 | ) 12 | -------------------------------------------------------------------------------- /packages/sample-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "target": "es2015", 9 | "jsx": "react", 10 | "typeRoots": ["../../node_modules/@types"] 11 | }, 12 | "compileOnSave": false 13 | } 14 | -------------------------------------------------------------------------------- /packages/sample-app/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | const { 2 | createConfig, 3 | 4 | // Feature blocks 5 | addPlugins, 6 | entryPoint, 7 | env, 8 | group, 9 | performance, 10 | setMode, 11 | setOutput, 12 | sourceMaps, 13 | 14 | // Shorthand setters 15 | babel, 16 | css, 17 | devServer, 18 | extractText, 19 | typescript, 20 | postcss 21 | } = require('webpack-blocks') 22 | 23 | const HtmlWebpackPlugin = require('html-webpack-plugin') 24 | const path = require('path') 25 | const cssnano = require('cssnano') 26 | 27 | const developmentConfig = () => 28 | group([ 29 | sourceMaps(), 30 | devServer({ 31 | proxy: { 32 | '/api/*': { target: 'http://localhost:4000' } 33 | } 34 | }), 35 | performance({ 36 | // Increase performance budget thresholds for development mode 37 | maxAssetSize: 1500000, 38 | maxEntrypointSize: 1500000 39 | }), 40 | css.modules() 41 | ]) 42 | 43 | const productionConfig = () => 44 | group([ 45 | css.modules(), 46 | postcss({ 47 | plugins: [cssnano()] 48 | }), 49 | extractText('css/[name].[contenthash:hex:8].css') 50 | ]) 51 | 52 | module.exports = createConfig([ 53 | setMode(process.env.NODE_ENV || 'development'), 54 | babel(), 55 | typescript({ configFileName: path.resolve(__dirname, './tsconfig.json') }), 56 | addPlugins([ 57 | new HtmlWebpackPlugin({ 58 | inject: true, 59 | template: './index.html' 60 | }) 61 | ]), 62 | env('development', [entryPoint('./src/index.dev.js'), developmentConfig()]), 63 | env('production', [ 64 | entryPoint('./src/index.js'), 65 | setOutput('./build/bundle.js'), 66 | productionConfig() 67 | ]) 68 | ]) 69 | -------------------------------------------------------------------------------- /packages/sass/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/sass - Changelog 2 | 3 | ## 2.0.0-alpha.1 4 | 5 | - Major version upgrade of `css-loader` to the 6 | [1.0.0](https://github.com/webpack-contrib/css-loader/releases/tag/v1.0.0) 7 | ([#291](https://github.com/andywer/webpack-blocks/pull/291)) 8 | - Deprecate `minimize` option after 9 | [the css-loader removed it](https://github.com/webpack-contrib/css-loader/releases/tag/v1.0.0) 10 | ([#291](https://github.com/andywer/webpack-blocks/pull/291)) 11 | - Remove `css-loader` and `style-loader` from the `use` option 12 | ([#294](https://github.com/andywer/webpack-blocks/pull/294)) 13 | 14 | ## 2.0.0-alpha 15 | 16 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 17 | 18 | ## 1.0.0-rc 19 | 20 | - Add `minimize` option 21 | 22 | ## 1.0.0-beta.2 23 | 24 | - Update dependency versions 25 | 26 | ## 1.0.0-beta 27 | 28 | - Make compatible with `match()` 29 | 30 | ## 1.0.0-alpha 31 | 32 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 33 | - Requires node 6+ 34 | 35 | ## 0.4.0 36 | 37 | - No changes, just keeping the version numbers in sync 38 | 39 | ## 0.3.0 40 | 41 | - Adapted to new API: Using `context` now 42 | 43 | ## 0.1.2 44 | 45 | - Allow setting custom `node-sass` options (see 46 | [#15](https://github.com/andywer/webpack-blocks/issues/15)) 47 | 48 | ## 0.1.1 49 | 50 | - `loaders: [ 'style', 'css', 'sass' ]` => 51 | `loaders: [ 'style-loader', 'css-loader', 'sass-loader' ]` 52 | 53 | ## 0.1.0 54 | 55 | Initial release. 56 | -------------------------------------------------------------------------------- /packages/sass/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - Sass 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/sass.svg)](https://www.npmjs.com/package/@webpack-blocks/sass) 5 | 6 | This is the `sass` block providing Sass support for webpack. Uses `node-sass` via `sass-loader`. 7 | 8 | ## Usage 9 | 10 | 11 | ```js 12 | const { createConfig, match } = require('@webpack-blocks/webpack') 13 | const { css } = require('@webpack-blocks/assets') 14 | const sass = require('@webpack-blocks/sass') 15 | 16 | module.exports = createConfig([ 17 | match(['*.scss', '!*node_modules*'], [ 18 | css(), 19 | sass({ sassOptions: {/* node-sass options */} }) 20 | ]) 21 | ]) 22 | ``` 23 | 24 | 25 | **NOTE**: Use match() here to apply the css() block to `.scss` files. 26 | 27 | ## Options 28 | 29 | You can pass any [sass-loader](https://github.com/webpack-contrib/sass-loader) as an object to the 30 | `sass` block. For example you can pass 31 | [node-sass options](https://github.com/sass/node-sass#options) in the `sassOptions` property. 32 | 33 | ## Examples 34 | 35 | ### Extract text plugin 36 | 37 | Use the `extract-text` block to extract the compiled SASS/SCSS styles into a separate CSS file: 38 | 39 | 40 | ```js 41 | const { createConfig, match, env } = require('@webpack-blocks/webpack') 42 | const { css } = require('@webpack-blocks/assets') 43 | const sass = require('@webpack-blocks/sass') 44 | const extractText = require('@webpack-blocks/extract-text') 45 | 46 | module.exports = createConfig([ 47 | match('*.scss', [ 48 | css(), 49 | sass(), 50 | env('production', [extractText()]) 51 | ]) 52 | ]) 53 | ``` 54 | 55 | 56 | Make sure you use the `extract-text` block _after_ the `sass` block. 57 | 58 | ### CSS Modules 59 | 60 | You can use SASS/SCSS in combination with CSS modules. 61 | 62 | 63 | ```js 64 | const { createConfig, match } = require('@webpack-blocks/webpack') 65 | const { css } = require('@webpack-blocks/assets') 66 | const sass = require('@webpack-blocks/sass') 67 | 68 | module.exports = createConfig([ 69 | match('*.scss', [ 70 | css.modules(), 71 | sass() 72 | ]) 73 | ]) 74 | ``` 75 | 76 | 77 | ### PostCSS 78 | 79 | You can use the SASS block together with PostCSS (using the `postcss` block) and its plugins, like 80 | the [Autoprefixer](https://github.com/postcss/autoprefixer), or 81 | [cssnano](https://github.com/cssnano/cssnano) if you want css minification. 82 | 83 | 84 | ```js 85 | const { createConfig, match } = require('@webpack-blocks/webpack') 86 | const { css } = require('@webpack-blocks/assets') 87 | const sass = require('@webpack-blocks/sass') 88 | const postcss = require('@webpack-blocks/postcss') 89 | const autoprefixer = require('autoprefixer') 90 | const cssnano = require('cssnano') 91 | 92 | module.exports = createConfig([ 93 | match('*.scss', [ 94 | css(), 95 | sass(), 96 | postcss([autoprefixer(), cssnano()]) 97 | ]) 98 | ]) 99 | ``` 100 | 101 | 102 | ## webpack-blocks 103 | 104 | Check out the 105 | 106 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 107 | 108 | Released under the terms of the MIT license. 109 | -------------------------------------------------------------------------------- /packages/sass/__tests__/integration.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { css } from '@webpack-blocks/assets' 3 | import { createConfig, match } from '@webpack-blocks/core' 4 | import sass from '../index' 5 | 6 | test('Sass works with defaults, inside match()', t => { 7 | const config = createConfig({}, [match('*.sass', [sass()])]) 8 | 9 | t.deepEqual(config.module.rules, [ 10 | { 11 | test: /^.*\.sass$/, 12 | use: [ 13 | { 14 | loader: 'sass-loader', 15 | options: {} 16 | } 17 | ] 18 | } 19 | ]) 20 | }) 21 | 22 | test('Sass works with css()', t => { 23 | const config = createConfig({}, [match('*.sass', { exclude: /node_modules/ }, [css(), sass()])]) 24 | 25 | t.deepEqual(config.module.rules, [ 26 | { 27 | test: /^.*\.sass$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'style-loader', 32 | options: {} 33 | }, 34 | { 35 | loader: 'css-loader', 36 | options: {} 37 | }, 38 | { 39 | loader: 'sass-loader', 40 | options: {} 41 | } 42 | ] 43 | } 44 | ]) 45 | }) 46 | 47 | test('Sass should pass sourceMap option to sass-loader', t => { 48 | const config = createConfig({}, [match('*.sass', [sass({ sourceMap: true })])]) 49 | 50 | t.deepEqual(config.module.rules, [ 51 | { 52 | test: /^.*\.sass$/, 53 | use: [ 54 | { 55 | loader: 'sass-loader', 56 | options: { 57 | sourceMap: true 58 | } 59 | } 60 | ] 61 | } 62 | ]) 63 | }) 64 | -------------------------------------------------------------------------------- /packages/sass/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SASS webpack block. 3 | * 4 | * @see https://github.com/jtangelder/sass-loader 5 | */ 6 | 7 | module.exports = sass 8 | 9 | /** 10 | * @param {object} [options] See https://github.com/sass/node-sass#options 11 | * @param {string[]} [options.sassOptions.includePaths] 12 | * @param {bool} [options.sassOptions.indentedSyntax] 13 | * @param {string} [options.sassOptions.outputStyle] 14 | * @param {bool} [options.sourceMap] 15 | * @return {Function} 16 | */ 17 | function sass(options = {}) { 18 | return (context, util) => { 19 | if (!context.match) { 20 | throw Error( 21 | 'You cannot use the sass() block outside a match() block. You also need to use the css() block on sass files.' 22 | ) 23 | } 24 | 25 | return util.addLoader( 26 | Object.assign( 27 | { 28 | test: /\.(sass|scss)$/, 29 | use: [ 30 | { 31 | loader: 'sass-loader', 32 | options 33 | } 34 | ] 35 | }, 36 | context.match 37 | ) 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/sass/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/sass", 3 | "version": "2.1.0", 4 | "description": "Webpack block for SASS.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Andy Wermke ", 8 | "engines": { 9 | "node": ">= 8.9.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "css-loader": "^3.5.2", 19 | "lodash": "^4.17.15", 20 | "node-sass": "^4.13.1", 21 | "sass-loader": "^8.0.2", 22 | "style-loader": "^1.1.4" 23 | }, 24 | "devDependencies": { 25 | "@webpack-blocks/assets": "^2.1.0", 26 | "@webpack-blocks/core": "^2.1.0", 27 | "webpack": "^4.20.2" 28 | }, 29 | "peerDependencies": { 30 | "@webpack-blocks/core": "^2.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/tslint/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/tslint - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | 7 | ## 1.0.0-beta.2 8 | 9 | - Update dependency versions 10 | 11 | ## 1.0.0-beta 12 | 13 | - Make compatible with `match()` 14 | 15 | ## 1.0.0-alpha 16 | 17 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 18 | - Requires node 6+ 19 | 20 | ## 0.4.0 21 | 22 | - No changes, just keeping the version numbers in sync 23 | 24 | ## 0.1.0 25 | 26 | Initial release. 27 | -------------------------------------------------------------------------------- /packages/tslint/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - TSLint 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/tslint.svg)](https://www.npmjs.com/package/@webpack-blocks/tslint) 5 | 6 | This is the `tslint` block providing TypeScript linting support for webpack. Uses `tslint` via 7 | `tslint-loader`. 8 | 9 | ## Usage 10 | 11 | 12 | ```js 13 | const { createConfig } = require('@webpack-blocks/webpack') 14 | const tslint = require('@webpack-blocks/tslint') 15 | 16 | module.exports = createConfig([ 17 | tslint(/* tslint options */) 18 | ]) 19 | ``` 20 | 21 | 22 | Use `match()` to explicitly specify the files to lint. 23 | 24 | ## Options 25 | 26 | You can pass random `ts-loader` options as an object to the `tslint` block. See 27 | [tslint-loader options](https://github.com/wbuchwalter/tslint-loader#usage). 28 | 29 | ## webpack-blocks 30 | 31 | Check out the 32 | 33 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 34 | 35 | Released under the terms of the MIT license. 36 | -------------------------------------------------------------------------------- /packages/tslint/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tslint webpack block. 3 | * 4 | * @see https://github.com/wbuchwalter/tslint-loader 5 | */ 6 | 7 | module.exports = tslint 8 | 9 | /** 10 | * @param {object} [options] See https://github.com/wbuchwalter/tslint-loader#usage 11 | * @return {Function} 12 | */ 13 | function tslint(options = {}) { 14 | return (context, util) => prevConfig => { 15 | let nextConfig = util.addLoader( 16 | Object.assign( 17 | { 18 | test: /\.(ts|tsx)$/, 19 | use: ['tslint-loader'], 20 | enforce: 'pre' 21 | }, 22 | context.match 23 | ) 24 | )(prevConfig) 25 | 26 | nextConfig = util.addPlugin( 27 | new context.webpack.LoaderOptionsPlugin({ 28 | options: { 29 | tslint: options 30 | } 31 | }) 32 | )(nextConfig) 33 | 34 | return nextConfig 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/tslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/tslint", 3 | "version": "2.0.1", 4 | "description": "Webpack block for typescript linting.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Jan van Brügge ", 8 | "engines": { 9 | "node": ">= 6.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "tslint": "^5.17.0", 19 | "tslint-loader": "^3.6.0", 20 | "typescript": "^3.1.2" 21 | }, 22 | "peerDependencies": { 23 | "@webpack-blocks/core": "^2.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/typescript/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/typescript - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | 7 | ## 1.0.0-beta.2 8 | 9 | - Update dependency versions 10 | 11 | ## 1.0.0-beta 12 | 13 | - Make compatible with `match()` 14 | 15 | ## 1.0.0-alpha 16 | 17 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 18 | - Requires node 6+ 19 | 20 | ## 0.4.1 21 | 22 | - Allow passing custom loader options (see 23 | [#141](https://github.com/andywer/webpack-blocks/pull/141)) 24 | 25 | ## 0.4.0 26 | 27 | - No changes, just keeping the version numbers in sync 28 | 29 | ## 0.1.0 30 | 31 | Initial release. 32 | -------------------------------------------------------------------------------- /packages/typescript/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - TypeScript 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/typescript.svg)](https://www.npmjs.com/package/@webpack-blocks/typescript) 5 | 6 | This is the `typescript` block providing TypeScript support for webpack. Uses 7 | `awesome-typescript-loader`. 8 | 9 | ## Usage 10 | 11 | 12 | ```js 13 | const { createConfig } = require('@webpack-blocks/webpack') 14 | const typescript = require('@webpack-blocks/typescript') 15 | 16 | module.exports = createConfig([ 17 | typescript(/* options, optional */) 18 | ]) 19 | ``` 20 | 21 | 22 | Use `match()` to explicitly specify the files to load using the TypeScript loader. 23 | 24 | ## Options 25 | 26 | Uses the default tsconfig.json in the root directory (See 27 | [here](https://github.com/s-panferov/awesome-typescript-loader#tsconfigjson)). You can pass 28 | [awesome-typescript-loader options](https://github.com/s-panferov/awesome-typescript-loader#loader-options) 29 | to the block. 30 | 31 | ## webpack-blocks 32 | 33 | Check out the 34 | 35 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 36 | 37 | Released under the terms of the MIT license. 38 | -------------------------------------------------------------------------------- /packages/typescript/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Typescript webpack block. 3 | * 4 | * @see https://github.com/s-panferov/awesome-typescript-loader 5 | */ 6 | 7 | const { CheckerPlugin, TsConfigPathsPlugin } = require('awesome-typescript-loader') 8 | 9 | module.exports = typescript 10 | 11 | /** 12 | * @param {object} [options] See https://github.com/s-panferov/awesome-typescript-loader#loader-options 13 | * @return {Function} 14 | */ 15 | function typescript(options = {}) { 16 | return (context, util) => 17 | util.merge({ 18 | resolve: { 19 | extensions: ['.ts', '.tsx'] 20 | }, 21 | module: { 22 | rules: [ 23 | Object.assign( 24 | { 25 | test: /\.(ts|tsx)$/, 26 | use: [ 27 | { 28 | loader: 'awesome-typescript-loader', 29 | options 30 | } 31 | ] 32 | }, 33 | context.match 34 | ) 35 | ] 36 | }, 37 | plugins: [ 38 | new CheckerPlugin(), 39 | new TsConfigPathsPlugin({ tsconfig: options.configFileName, compiler: options.compiler }) // This hooks into webpacks module resolution, configure via tsconfig.json 40 | ] 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /packages/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/typescript", 3 | "version": "2.0.1", 4 | "description": "Webpack block for Typescript.", 5 | "main": "lib/index", 6 | "license": "MIT", 7 | "author": "Jan van Brügge ", 8 | "engines": { 9 | "node": ">= 6.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks" 14 | ], 15 | "repository": "andywer/webpack-blocks", 16 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 17 | "dependencies": { 18 | "awesome-typescript-loader": "^5.2.1", 19 | "typescript": "^3.1.2" 20 | }, 21 | "peerDependencies": { 22 | "@webpack-blocks/core": "^2.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/uglify/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/uglify - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | 7 | ## 1.1.0 8 | 9 | - Use `uglifyjs-webpack-plugin` v1.0 (not the beta) 10 | ([#231](https://github.com/andywer/webpack-blocks/issues/231)) 11 | 12 | ## 1.0.0 13 | 14 | Initial release. 15 | -------------------------------------------------------------------------------- /packages/uglify/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - UglifyJS 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/uglify.svg)](https://www.npmjs.com/package/@webpack-blocks/uglify) 5 | 6 | This block provides UglifyJS webpack plugin configuration for 7 | [webpack-blocks](https://github.com/andywer/webpack-blocks). 8 | 9 | Based on [uglifyjs-webpack-plugin](https://github.com/webpack-contrib/uglifyjs-webpack-plugin) (not 10 | `webpack.optimize.UglifyJsPlugin`) which uses UglifyJS v3 (uglify-es) that supports ECMAScript 2015. 11 | 12 | **This block will only work in the `production` mode.** 13 | 14 | ## Usage 15 | 16 | 17 | ```js 18 | const { createConfig, env, uglify } = require('webpack-blocks') 19 | 20 | module.exports = createConfig([ 21 | uglify(/* options */) 22 | ]) 23 | ``` 24 | 25 | 26 | ## Options 27 | 28 | You can pass any `uglifyjs-webpack-plugin` and UglifyJS options, see the 29 | [`uglifyjs-webpack-plugin` docs for details](https://github.com/webpack-contrib/uglifyjs-webpack-plugin#options). 30 | 31 | ## Generated webpack config 32 | 33 | By default generates this configuration: 34 | 35 | ```js 36 | { 37 | optimization: { 38 | minimizer: [ 39 | new UglifyJSPlugin({ 40 | parallel: true, 41 | cache: true, 42 | uglifyOptions: { 43 | compress: { 44 | warnings: false 45 | } 46 | } 47 | }) 48 | ] 49 | } 50 | } 51 | ``` 52 | 53 | ## webpack-blocks 54 | 55 | Check out the 56 | 57 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 58 | 59 | Released under the terms of the MIT license. 60 | -------------------------------------------------------------------------------- /packages/uglify/__tests__/uglify.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin' 3 | import { createConfig } from '@webpack-blocks/core' 4 | import uglify from '../index' 5 | 6 | test('Uglify default options work', t => { 7 | const config = createConfig({}, [uglify()]) 8 | 9 | t.true(config.optimization.minimizer[0] instanceof UglifyJSPlugin) 10 | }) 11 | 12 | test('Uglify options work', t => { 13 | const config = createConfig({}, [ 14 | uglify({ 15 | parallel: 42 16 | }) 17 | ]) 18 | 19 | t.truthy(config.optimization.minimizer[0].options.parallel) 20 | t.truthy(config.optimization.minimizer[0].options.cache) 21 | t.deepEqual(config.optimization.minimizer[0].options.parallel, 42) 22 | t.deepEqual(config.optimization.minimizer[0].options.cache, true) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/uglify/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UglifyJS webpack block. 3 | * 4 | * @see https://github.com/webpack-contrib/uglifyjs-webpack-plugin 5 | */ 6 | 7 | const webpackMerge = require('webpack-merge') 8 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin') 9 | 10 | module.exports = uglify 11 | 12 | /** 13 | * @param {object} [options] UglifyJS options 14 | * @return {Function} 15 | */ 16 | function uglify(options = {}) { 17 | options = webpackMerge( 18 | { 19 | parallel: true, 20 | cache: true, 21 | uglifyOptions: { 22 | compress: { 23 | warnings: false 24 | } 25 | } 26 | }, 27 | options 28 | ) 29 | 30 | const postHook = (context, util) => { 31 | const plugin = new UglifyJSPlugin(options) 32 | return util.merge({ 33 | optimization: { 34 | minimizer: [plugin] 35 | } 36 | }) 37 | } 38 | 39 | return Object.assign(() => prevConfig => prevConfig, { post: postHook }) 40 | } 41 | -------------------------------------------------------------------------------- /packages/uglify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/uglify", 3 | "version": "2.1.0", 4 | "description": "Webpack block for UglifyJS", 5 | "main": "index", 6 | "license": "MIT", 7 | "author": "Artem Sapegin ", 8 | "engines": { 9 | "node": ">= 6.0" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "webpack-blocks", 14 | "uglify", 15 | "uglifyjs" 16 | ], 17 | "repository": "andywer/webpack-blocks", 18 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 19 | "dependencies": { 20 | "uglifyjs-webpack-plugin": "^1.3.0", 21 | "webpack-merge": "^4.2.2" 22 | }, 23 | "devDependencies": { 24 | "@webpack-blocks/core": "^2.1.0", 25 | "webpack": "^4.20.2" 26 | }, 27 | "peerDependencies": { 28 | "@webpack-blocks/core": "^2.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/webpack-blocks/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/webpack - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 6 | 7 | ## 1.0.0-rc.2 8 | 9 | - Updated `@webpack-blocks/uglify` to `v1.1.0` 10 | 11 | ## 1.0.0-rc 12 | 13 | - Renamed `babel6` to `babel` 14 | - The new version of `extract-text` requires webpack v3 15 | 16 | ## 1.0.0-beta.4 17 | 18 | - Add `@webpack-blocks/uglify` 19 | 20 | ## 1.0.0-beta.3 21 | 22 | - Updated `@webpack-blocks/dev-server` to `v1.0.0-beta.2` 23 | 24 | ## 1.0.0-beta.2 25 | 26 | - Updated `@webpack-blocks/webpack` to `v1.0.0-beta.2` 27 | 28 | ## 1.0.0-beta 29 | 30 | - Updated dependency versions only 31 | 32 | ## 1.0.0-alpha 33 | 34 | - Initial release 35 | -------------------------------------------------------------------------------- /packages/webpack-blocks/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - the convenience package 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/webpack-blocks.svg)](https://www.npmjs.com/package/webpack-blocks) 5 | 6 | This is the webpack-blocks convenience package. It wraps all the most commonly used blocks, so you 7 | can install just this single package and have webpack-blocks and your favorite blocks set up. 8 | 9 | ## Usage 10 | 11 | Here is a small sample configuration. Instead of requiring from `@webpack-blocks/webpack`, 12 | `@webpack-blocks/babel` and others you just need a single `require()` and a single dependency in 13 | your `package.json`. 14 | 15 | Of course you can still separately define or install custom blocks and use them as you want. 16 | 17 | ```js 18 | const webpack = require('webpack') 19 | const { 20 | babel, 21 | createConfig, 22 | css, 23 | defineConstants, 24 | entryPoint, 25 | env, 26 | extractText, 27 | match, 28 | setOutput, 29 | uglify, 30 | postcss 31 | } = require('webpack-blocks') 32 | const cssnano = require('cssnano') 33 | 34 | module.exports = createConfig([ 35 | entryPoint('./src/main.js'), 36 | setOutput('./build/bundle.js'), 37 | babel(), 38 | defineConstants({ 39 | 'process.env.NODE_ENV': process.env.NODE_ENV 40 | }), 41 | match('*.css', { exclude: /node_modules/ }, [ 42 | css(), 43 | env('production', [ 44 | extractText(), 45 | postcss({ 46 | plugins: [cssnano()] 47 | }) 48 | ]) 49 | ]), 50 | env('production', [uglify()]) 51 | ]) 52 | ``` 53 | 54 | ## Included blocks 55 | 56 | - [assets](https://github.com/andywer/webpack-blocks/tree/master/packages/assets) 57 | - [babel](https://github.com/andywer/webpack-blocks/tree/master/packages/babel) 58 | - [dev-server](https://github.com/andywer/webpack-blocks/tree/master/packages/dev-server) 59 | - [extract-text](https://github.com/andywer/webpack-blocks/tree/master/packages/extract-text) 60 | - [postcss](https://github.com/andywer/webpack-blocks/tree/master/packages/postcss) 61 | - [sass](https://github.com/andywer/webpack-blocks/tree/master/packages/sass) 62 | - [typescript](https://github.com/andywer/webpack-blocks/tree/master/packages/typescript) 63 | - [uglify](https://github.com/andywer/webpack-blocks/tree/master/packages/uglify) 64 | - [webpack](https://github.com/andywer/webpack-blocks/tree/master/packages/webpack) 65 | 66 | ## webpack-blocks 67 | 68 | Check out the 69 | 70 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 71 | 72 | Released under the terms of the MIT license. 73 | -------------------------------------------------------------------------------- /packages/webpack-blocks/index.js: -------------------------------------------------------------------------------- 1 | const assets = require('@webpack-blocks/assets') 2 | const babel = require('@webpack-blocks/babel') 3 | const devServer = require('@webpack-blocks/dev-server') 4 | const extractText = require('@webpack-blocks/extract-text') 5 | const postcss = require('@webpack-blocks/postcss') 6 | const sass = require('@webpack-blocks/sass') 7 | const typescript = require('@webpack-blocks/typescript') 8 | const uglify = require('@webpack-blocks/uglify') 9 | const webpack = require('@webpack-blocks/webpack') 10 | 11 | module.exports = Object.assign({}, assets, webpack, { 12 | babel, 13 | devServer, 14 | extractText, 15 | postcss, 16 | sass, 17 | typescript, 18 | uglify 19 | }) 20 | -------------------------------------------------------------------------------- /packages/webpack-blocks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-blocks", 3 | "version": "2.1.0", 4 | "description": "Convenience package that includes all the most common webpack blocks.", 5 | "license": "MIT", 6 | "author": "Andy Wermke ", 7 | "engines": { 8 | "node": ">= 6.0" 9 | }, 10 | "keywords": [ 11 | "webpack", 12 | "webpack-blocks" 13 | ], 14 | "repository": "andywer/webpack-blocks", 15 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 16 | "dependencies": { 17 | "@webpack-blocks/assets": "^2.1.0", 18 | "@webpack-blocks/babel": "^2.1.0", 19 | "@webpack-blocks/dev-server": "^2.1.0", 20 | "@webpack-blocks/extract-text": "^2.1.0", 21 | "@webpack-blocks/postcss": "^2.1.0", 22 | "@webpack-blocks/sass": "^2.1.0", 23 | "@webpack-blocks/typescript": "^2.0.1", 24 | "@webpack-blocks/uglify": "^2.1.0", 25 | "@webpack-blocks/webpack": "^2.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/webpack/.npmignore: -------------------------------------------------------------------------------- 1 | __e2e-fixtures__/ 2 | __tests__/ 3 | -------------------------------------------------------------------------------- /packages/webpack/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @webpack-blocks/webpack - Changelog 2 | 3 | ## 2.0.0-alpha 4 | 5 | - Remove deprecated `fileType` API ([#260](https://github.com/andywer/webpack-blocks/issues/260)) 6 | - Remove deprecated `resolveAliases` block 7 | ([#267](https://github.com/andywer/webpack-blocks/issues/267)) 8 | - Support for webpack 4 ([#261](https://github.com/andywer/webpack-blocks/pull/261)) 9 | - Added `setMode()` and `optimization()` blocks 10 | ([#274](https://github.com/andywer/webpack-blocks/issues/274)) 11 | 12 | ## 1.0.0 13 | 14 | - Added `when()` ([#242](https://github.com/andywer/webpack-blocks/issues/242)) 15 | 16 | ## 1.0.0-rc 17 | 18 | - Added `setEnv()` ([#206](https://github.com/andywer/webpack-blocks/pull/206)) 19 | 20 | ## 1.0.0-beta.3 21 | 22 | - Make resolve() prepend custom extensions 23 | ([#177](https://github.com/andywer/webpack-blocks/issues/177)) 24 | - Let core `createConfig()` validate the passed setters 25 | ([#171](https://github.com/andywer/webpack-blocks/issues/171)) 26 | 27 | ## 1.0.0-beta.2 28 | 29 | - Made webpack a peer dependency ([#169](https://github.com/andywer/webpack-blocks/issues/169)) 30 | - Made compatible with webpack v3 ([#169](https://github.com/andywer/webpack-blocks/issues/169)) 31 | 32 | ## 1.0.0-beta 33 | 34 | - Added `match()` 35 | - Added `resolve()`, deprecate `resolveAliases()` 36 | 37 | ## 1.0.0-alpha 38 | 39 | - Updated for new core API ([#125](https://github.com/andywer/webpack-blocks/issues/125)) 40 | - Fail with meaningful message if `createConfig()` is called with invalid param 41 | ([#110](https://github.com/andywer/webpack-blocks/issues/110)) 42 | - Added `text/html` file type (in `@webpack-blocks/core`) 43 | - Requires node 6+ 44 | 45 | ## 0.4.0 46 | 47 | - Provide `createConfig.vanilla()` (see [#80](https://github.com/andywer/webpack-blocks/issues/80)) 48 | - Added `webpackVersion` to context 49 | - Breaking change: Removed `node_modules/` exclusion from default css-loader 50 | - Using `webpack-merge` v2.3 instead of v0.14 51 | - Using `webpack` v2.2 instead of its RC 52 | 53 | ## 0.3.1 54 | 55 | - Remove the `json-loader` config & depedency, since webpack 2 comes with a default json-loader 56 | config (#63) 57 | 58 | ## 0.3.0 59 | 60 | Initial non-beta release. Aligned with the v0.3 release changes. 61 | 62 | - Provide `group()` for creating presets 63 | - Provide `performance()` for webpack performance budgeting 64 | - Updated dependency: Using webpack 2 release candidate 65 | 66 | ## 0.1.0-beta 67 | 68 | Initial beta release. 69 | -------------------------------------------------------------------------------- /packages/webpack/README.md: -------------------------------------------------------------------------------- 1 | # webpack-blocks - webpack base configuration 2 | 3 | [![Gitter chat](https://badges.gitter.im/webpack-blocks.svg)](https://gitter.im/webpack-blocks) 4 | [![NPM Version](https://img.shields.io/npm/v/@webpack-blocks/webpack.svg)](https://www.npmjs.com/package/@webpack-blocks/webpack) 5 | 6 | This is the `webpack` block providing webpack core functionality. Also provides all 7 | `@webpack-blocks/core` exports for convenience. 8 | 9 | ## Usage 10 | 11 | ```js 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | const { 14 | addPlugins, 15 | createConfig, 16 | entryPoint, 17 | env, 18 | setMode, 19 | setOutput, 20 | sourceMaps 21 | } = require('@webpack-blocks/webpack') 22 | const { css } = require('@webpack-blocks/assets') 23 | 24 | module.exports = createConfig([ 25 | setMode(process.env.NODE_ENV || 'development'), 26 | entryPoint('./src/main.js'), 27 | setOutput('./build/bundle.js'), 28 | css(), 29 | addPlugins([ 30 | new HtmlWebpackPlugin({ 31 | inject: true, 32 | template: './index.html' 33 | }) 34 | ]), 35 | env('development', [ 36 | // will only enable source maps if `NODE_ENV === 'development'` 37 | sourceMaps() 38 | ]) 39 | ]) 40 | ``` 41 | 42 | ## Exports 43 | 44 | #### createConfig(configSetter: Function[]): object 45 | 46 | Takes an array of config setters (the functions returned by invoked webpack blocks), invokes them 47 | and returns the resulting webpack config object. 48 | 49 | ### Helpers 50 | 51 | #### group(configSetters: Function[]): Function 52 | 53 | Combines an array of blocks to a new joined block. Running this single block has the same effect as 54 | running all source blocks. 55 | 56 | #### env(envName: string, configSetters: Function[]): Function 57 | 58 | Applies an array of webpack blocks only if `process.env.NODE_ENV` matches the given `envName`. If no 59 | `NODE_ENV` is set, it will be treated as 'development'. 60 | 61 | Use like this: 62 | 63 | 64 | ```js 65 | module.exports = createConfig([ 66 | css(), 67 | env('production', [extractText()]) 68 | ]) 69 | ``` 70 | 71 | 72 | #### match(test: string|RegExp|Array, options: ?object, configSetters: Function[]): Function 73 | 74 | State on which files to apply the loader blocks passed in this call. Works like `group()`, but adds 75 | the file matching information to the context that can be used by the child blocks. The options 76 | parameter is optional. 77 | 78 | Use like this: 79 | 80 | 81 | ```js 82 | module.exports = createConfig([ 83 | match(['*.scss', '!*node_modules*'], [ 84 | sass(), 85 | extractText('css/[name].css') 86 | ]) 87 | ]) 88 | ``` 89 | 90 | 91 | To match multiple file patterns you can pass a pattern array: 92 | 93 | 94 | ```js 95 | module.exports = createConfig([ 96 | match(['*.sass', '*.scss'], [ 97 | /* blocks */ 98 | ]) 99 | ]) 100 | ``` 101 | 102 | 103 | #### when(condition: boolean, configSetters: Function[]): Function 104 | 105 | Applies an array of webpack blocks only if `condition` is true (or truthy). 106 | 107 | Use like this: 108 | 109 | 110 | ```js 111 | module.exports = createConfig([ 112 | when(process.env.CI, [reportBuildStatsPlugin()]) 113 | ]) 114 | ``` 115 | 116 | 117 | ### Shorthand setters 118 | 119 | #### addPlugins(plugins: WebpackPlugin[]) 120 | 121 | Add custom [plugins](https://webpack.github.io/docs/configuration.html#plugins) to the webpack 122 | configuration. 123 | 124 | Example usage: 125 | 126 | ```js 127 | addPlugins([new HtmlWebpackPlugin()]) 128 | ``` 129 | 130 | #### customConfig(webpackConfigSnippet: object) 131 | 132 | Add some custom configuration to the webpack configuration. The object you pass will be merged into 133 | the webpack configuration object. 134 | 135 | #### defineConstants(constants: object): Function 136 | 137 | Replaces constants in your source code with a value (`process.env.NODE_ENV` for example) using the 138 | [webpack.DefinePlugin](https://webpack.github.io/docs/list-of-plugins.html#defineplugin). Pass an 139 | object containing your constant definitions: `{ [constantName: string]: }`. 140 | 141 | Every constant's value is `JSON.stringify()`-ed first, so you don't have to remember. 142 | 143 | Using `defineConstants` multiple times results in a single DefinePlugin instance configured to do 144 | all the replacements. 145 | 146 | #### setEnv(constants: string[]|object): Function 147 | 148 | Replaces constants in your source code with a values from `process.env` using the 149 | [webpack.EnvironmentPlugin](https://webpack.js.org/plugins/environment-plugin/). 150 | 151 | Using `setEnv` multiple times results in a single EnvironmentPlugin instance configured to do all 152 | the replacements. 153 | 154 | ```js 155 | module.exports = createConfig([ 156 | setEnv(['NODE_ENV']), 157 | setEnv({ 158 | BABEL_ENV: 'development', // use 'development' unless process.env.BABEL_ENV is defined 159 | PORT: 3000 160 | }) 161 | ]) 162 | ``` 163 | 164 | #### entryPoint(entryPoint: string|string[]|object) 165 | 166 | Adds one or multiple entry points. If the parameter is not an object the entry point(s) will be 167 | added to the default chunk named `main`. This way we make sure that the resulting 168 | [https://webpack.github.io/docs/configuration.html#entry](entry) configuration property will always 169 | be an object. 170 | 171 | #### optimization(optimizationOptions: object) 172 | 173 | Set the optimization [settings](https://webpack.js.org/configuration/optimization/). 174 | 175 | #### performance(perfBudgetOptions: object) 176 | 177 | Set a performance budget. Performance budgets are custom limits (like max bundle size) you can set 178 | to make webpack warn you or throw an error if the application exceeds those limits. 179 | 180 | Options object: 181 | 182 | ```js 183 | { 184 | maxAssetSize: number, // File size limit in bytes 185 | maxEntrypointSize: number, // Total size (of an entry point) limit in bytes 186 | hints: string // "warning" or "error" 187 | } 188 | ``` 189 | 190 | #### resolve(config: object) 191 | 192 | Sets [resolve](https://webpack.js.org/configuration/resolve/). Use it to manually override module 193 | resolution. 194 | 195 | Example: 196 | 197 | ```js 198 | resolve({ 199 | // resolve `import 'Utilities'` to correct path 200 | alias: { Utilities: path.resolve(__dirname, 'src/utilities/') }, 201 | extensions: ['.js', '.json'], 202 | modules: [path.resolve(__dirname, 'src'), 'node_modules'] 203 | }) 204 | ``` 205 | 206 | #### setContext(path: string) 207 | 208 | Sets the webpack [context](https://webpack.github.io/docs/configuration.html#context). Not to be 209 | confused with the webpack-block's `context` object. 210 | 211 | #### setDevTool(devtool: string) 212 | 213 | Use it to manually set the webpack 214 | [devtool](https://webpack.github.io/docs/configuration.html#devtool) property, like `'eval'`, 215 | `'source-map'` and such. 216 | 217 | #### setMode(mode: string) 218 | 219 | Sets the webpack [mode](https://webpack.js.org/concepts/mode). 220 | 221 | #### setOutput(output: string|object) 222 | 223 | Sets the webpack [output](https://webpack.github.io/docs/configuration.html#output) property. Use it 224 | to tell webpack where to save the output bundle(s). 225 | 226 | You can either pass an object that complies to the format described in the 227 | [webpack docs](https://webpack.github.io/docs/configuration.html#output) or just pass the 228 | destination file path. 229 | 230 | Instead of passing the destination file path you can also 231 | 232 | - Just pass a filename (not the complete path): The directory will default to `./build/`. 233 | - Just pass the path to a directory (trailing `/`): The filename will default to `bundle.js`. 234 | 235 | #### sourceMaps(devtool: ?string) 236 | 237 | Just a convenience wrapper to enable sourcemaps in an easier-to-read fashion than `setDevTool()`. 238 | Will set webpack's `devtool` to `'cheap-module-eval-source-map'` if no explicit `devtool` is passed 239 | as parameter. 240 | 241 | ## webpack-blocks 242 | 243 | Check out the 244 | 245 | 👉 [Main documentation](https://github.com/andywer/webpack-blocks) 246 | 247 | Released under the terms of the MIT license. 248 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/babel-postcss-extract-text/app.js: -------------------------------------------------------------------------------- 1 | require('./styles.css') 2 | 3 | module.exports = process.env.TEST 4 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/babel-postcss-extract-text/styles.css: -------------------------------------------------------------------------------- 1 | $MARGIN: 40px; 2 | 3 | .app { 4 | margin: $MARGIN; 5 | } 6 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/babel-postcss-extract-text/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | createConfig, 3 | defineConstants, 4 | entryPoint, 5 | match, 6 | performance, 7 | setDevTool, 8 | setMode, 9 | setOutput 10 | } = require('../../index') 11 | 12 | const babel = require('@webpack-blocks/babel') 13 | const postcss = require('@webpack-blocks/postcss') 14 | const extractText = require('@webpack-blocks/extract-text') 15 | const { css } = require('@webpack-blocks/assets') 16 | const path = require('path') 17 | const postcssVars = require('postcss-simple-vars') 18 | 19 | module.exports = createConfig([ 20 | setMode('development'), 21 | setDevTool(false), 22 | entryPoint(path.join(__dirname, 'app.js')), 23 | setOutput(path.join(__dirname, 'build/bundle.js')), 24 | babel(), 25 | match('*.css', [ 26 | css(), 27 | postcss({ 28 | plugins: [postcssVars] 29 | }), 30 | extractText('styles.css') 31 | ]), 32 | performance({ 33 | maxAssetSize: 100000, 34 | maxEntrypointSize: 500000, 35 | hints: 'error' 36 | }), 37 | defineConstants({ 38 | 'process.env.TEST': 'This is the injected process.env.TEST!' 39 | }) 40 | ]) 41 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/minimal/app.js: -------------------------------------------------------------------------------- 1 | module.exports = 'I am the minimal test export' 2 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/minimal/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { createConfig, customConfig, entryPoint, setMode, setOutput } = require('../../index') 2 | 3 | const path = require('path') 4 | 5 | module.exports = createConfig([ 6 | setMode('development'), 7 | entryPoint(path.join(__dirname, 'app.js')), 8 | setOutput(path.join(__dirname, 'build/bundle.js')), 9 | customConfig({ 10 | output: { 11 | // Created bundle will be a module instead of a stand-alone self-running bundle 12 | // So we can require() it and check if it works 13 | libraryTarget: 'commonjs2' 14 | } 15 | }) 16 | ]) 17 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/postcss-sass-sourcemaps/styles.sass: -------------------------------------------------------------------------------- 1 | $PADDING: 25px 2 | 3 | body 4 | padding: $PADDING 5 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/postcss-sass-sourcemaps/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { createConfig, entryPoint, setMode, setOutput } = require('../../index') 2 | 3 | const path = require('path') 4 | const { match } = require('@webpack-blocks/webpack') 5 | const { css } = require('@webpack-blocks/assets') 6 | const postcss = require('@webpack-blocks/postcss') 7 | const sass = require('@webpack-blocks/sass') 8 | const postcssVars = require('postcss-simple-vars') 9 | 10 | module.exports = createConfig([ 11 | setMode('development'), 12 | entryPoint(path.join(__dirname, 'styles.sass')), 13 | setOutput(path.join(__dirname, 'build/bundle.js')), 14 | match('*.sass', [ 15 | css({ sourceMap: true }), 16 | postcss({ 17 | plugins: [postcssVars] 18 | }), 19 | sass({ indentSyntax: true, sourceMap: true }) 20 | ]) 21 | ]) 22 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/sass-css-modules/src/index.js: -------------------------------------------------------------------------------- 1 | import { myClass } from './styles.scss' 2 | 3 | export default myClass 4 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/sass-css-modules/src/styles.scss: -------------------------------------------------------------------------------- 1 | $HOVER_COLOR: #ff0000; 2 | $MARGIN: 10px; 3 | 4 | .myClass { 5 | margin: $MARGIN; 6 | 7 | &:hover { 8 | color: $HOVER_COLOR; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/sass-css-modules/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { createConfig, entryPoint, match, setMode, setOutput } = require('../../index') 2 | const { css } = require('@webpack-blocks/assets') 3 | const extractText = require('@webpack-blocks/extract-text') 4 | const sass = require('@webpack-blocks/sass') 5 | const path = require('path') 6 | 7 | module.exports = createConfig([ 8 | setMode('development'), 9 | entryPoint(path.join(__dirname, 'src/index.js')), 10 | setOutput(path.join(__dirname, 'build/bundle.js')), 11 | match('*.scss', [css.modules(), sass(), extractText('./styles.css')]) 12 | ]) 13 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/sass-extract-text/app.js: -------------------------------------------------------------------------------- 1 | require('./styles.sass') 2 | 3 | module.exports = process.env.TEST 4 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/sass-extract-text/styles.sass: -------------------------------------------------------------------------------- 1 | $PADDING: 25px 2 | 3 | body 4 | padding: $PADDING 5 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/sass-extract-text/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { createConfig, entryPoint, match, setMode, setOutput } = require('../../index') 2 | 3 | const path = require('path') 4 | const { css } = require('@webpack-blocks/assets') 5 | const extractText = require('@webpack-blocks/extract-text') 6 | const sass = require('@webpack-blocks/sass') 7 | 8 | module.exports = createConfig([ 9 | setMode('development'), 10 | entryPoint(path.join(__dirname, 'app.js')), 11 | setOutput(path.join(__dirname, 'build/bundle.js')), 12 | match('*.sass', [css(), sass({ indentSyntax: true }), extractText('styles.css')]) 13 | ]) 14 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/typescript/app.ts: -------------------------------------------------------------------------------- 1 | function add(a: number, b: number) { 2 | return a + b 3 | } 4 | 5 | module.exports = process.env.TEST 6 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "target": "es2015", 9 | "jsx": "react", 10 | "typeRoots": ["../../../../node_modules/@types"] 11 | }, 12 | "compileOnSave": false, 13 | "files": [ 14 | "app.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/typescript/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "curly": true, 5 | "semicolon": [true, "never"], 6 | "typedef-whitespace": [ 7 | true, 8 | { 9 | "call-signature": "space", 10 | "index-signature": "nospace", 11 | "parameter": "onespace", 12 | "property-declaration": "onespace", 13 | "variable-declaration": "onespace" 14 | }, 15 | { 16 | "call-signature": "onespace", 17 | "index-signature": "onespace", 18 | "parameter": "onespace", 19 | "property-declaration": "onespace", 20 | "variable-declaration": "onespace" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/webpack/__e2e-fixtures__/typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | createConfig, 3 | defineConstants, 4 | entryPoint, 5 | performance, 6 | setDevTool, 7 | setMode, 8 | setOutput 9 | } = require('../../index') 10 | 11 | const typescript = require('@webpack-blocks/typescript') 12 | const tslint = require('@webpack-blocks/tslint') 13 | const path = require('path') 14 | 15 | module.exports = createConfig([ 16 | setDevTool(false), 17 | setMode('development'), 18 | entryPoint(path.join(__dirname, 'app.ts')), 19 | setOutput(path.join(__dirname, 'build/bundle.js')), 20 | typescript({ configFileName: path.resolve(__dirname, './tsconfig.json') }), 21 | tslint({ 22 | emitErrors: true, 23 | configuration: { 24 | rules: { 25 | 'typedef-whitespace': false 26 | } 27 | } 28 | }), 29 | performance({ 30 | maxAssetSize: 100000, 31 | maxEntrypointSize: 500000, 32 | hints: 'error' 33 | }), 34 | defineConstants({ 35 | 'process.env.TEST': 'This is the injected process.env.TEST!' 36 | }) 37 | ]) 38 | -------------------------------------------------------------------------------- /packages/webpack/__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 8 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/webpack/__tests__/devServer.integration.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const { createConfig } = require('@webpack-blocks/core') 3 | const devServer = require('@webpack-blocks/dev-server') 4 | const { entryPoint } = require('../index') 5 | 6 | const webpack = { 7 | HotModuleReplacementPlugin: class HotModuleReplacementPlugin {}, 8 | NamedModulesPlugin: class NamedModulesPlugin {} 9 | } 10 | 11 | const commonConfig = { 12 | stats: { 13 | children: false, 14 | chunks: false, 15 | modules: false, 16 | reasons: false 17 | }, 18 | module: { 19 | rules: [] 20 | }, 21 | plugins: [new webpack.HotModuleReplacementPlugin()], 22 | resolve: { 23 | extensions: ['.js', '.json'] 24 | } 25 | } 26 | 27 | test('devServer() without options provides expected defaults', t => { 28 | const config = createConfig({ webpack }, [ 29 | entryPoint({ 30 | main: ['./test.js'] 31 | }), 32 | devServer() 33 | ]) 34 | 35 | t.deepEqual( 36 | config, 37 | Object.assign(commonConfig, { 38 | entry: { 39 | main: ['./test.js'] 40 | }, 41 | devServer: { 42 | hot: true, 43 | hotOnly: true, 44 | historyApiFallback: true, 45 | inline: true, 46 | clientLogLevel: 'error', 47 | stats: 'errors-only' 48 | } 49 | }) 50 | ) 51 | t.true(config.plugins[0] instanceof webpack.HotModuleReplacementPlugin) 52 | }) 53 | 54 | test('devServer() uses custom options and can be composed', t => { 55 | const config = createConfig({ webpack }, [ 56 | entryPoint({ 57 | main: ['./test.js'], 58 | second: ['./second.js'] 59 | }), 60 | devServer({ 61 | inline: false 62 | }), 63 | devServer('some-entry-point') 64 | ]) 65 | 66 | t.deepEqual( 67 | config, 68 | Object.assign(commonConfig, { 69 | entry: { 70 | main: ['./test.js', 'some-entry-point'], 71 | second: ['./second.js', 'some-entry-point'] 72 | }, 73 | devServer: { 74 | hot: true, 75 | hotOnly: true, 76 | historyApiFallback: true, 77 | inline: false, 78 | clientLogLevel: 'error', 79 | stats: 'errors-only' 80 | } 81 | }) 82 | ) 83 | t.true(config.plugins[0] instanceof webpack.HotModuleReplacementPlugin) 84 | }) 85 | 86 | // Regression test for https://github.com/andywer/webpack-blocks/issues/76 87 | test('devServer block extends multiple entry points correctly', t => { 88 | const config = createConfig({ webpack }, [ 89 | entryPoint({ 90 | a: './a', 91 | b: ['./b'] 92 | }), 93 | devServer() 94 | ]) 95 | 96 | t.deepEqual(config.entry, { 97 | a: ['./a'], 98 | b: ['./b'] 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /packages/webpack/__tests__/end-to-end.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import fs from 'mz/fs' 3 | import { JSDOM } from 'jsdom' 4 | import path from 'path' 5 | import webpack from 'webpack' 6 | 7 | const fixturesPath = path.join(__dirname, '../__e2e-fixtures__') 8 | 9 | test('building a minimal webpack project works', async t => { 10 | const projectPath = path.join(fixturesPath, 'minimal') 11 | const buildPath = path.join(projectPath, 'build') 12 | 13 | const config = require(path.join(projectPath, 'webpack.config.js')) 14 | await runWebpack(config) 15 | 16 | const bundleExports = require(path.join(buildPath, 'bundle.js')) 17 | t.is(bundleExports, 'I am the minimal test export') 18 | }) 19 | 20 | test('building the babel/postcss/extract-text project works', async t => { 21 | const projectPath = path.join(fixturesPath, 'babel-postcss-extract-text') 22 | const buildPath = path.join(projectPath, 'build') 23 | 24 | const config = require(path.join(projectPath, 'webpack.config.js')) 25 | await runWebpack(config) 26 | 27 | require(path.join(buildPath, 'bundle.js')) 28 | 29 | // Check if bundle contains injected process.env.TEST 30 | const bundleContents = await fs.readFile(path.join(buildPath, 'bundle.js'), { encoding: 'utf8' }) 31 | t.true(bundleContents.indexOf('module.exports = "This is the injected process.env.TEST!"') > -1) 32 | 33 | // Check if CSS file contains correct content 34 | const styleContents = await fs.readFile(path.join(buildPath, 'styles.css'), { encoding: 'utf8' }) 35 | t.true(removeWhitespaces(styleContents).indexOf(removeWhitespaces('.app { margin: 40px; }')) > -1) 36 | }) 37 | 38 | test('building the sass/extract-text project works', async t => { 39 | const projectPath = path.join(fixturesPath, 'sass-extract-text') 40 | const buildPath = path.join(projectPath, 'build') 41 | 42 | const config = require(path.join(projectPath, 'webpack.config.js')) 43 | await runWebpack(config) 44 | 45 | global.window = new JSDOM('') 46 | global.document = global.window.document 47 | require(path.join(buildPath, 'bundle.js')) 48 | 49 | // Check if CSS file contains correct content 50 | const styleContents = await fs.readFile(path.join(buildPath, 'styles.css'), { encoding: 'utf8' }) 51 | t.true( 52 | removeWhitespaces(styleContents).indexOf(removeWhitespaces('body { padding: 25px; }')) > -1 53 | ) 54 | }) 55 | 56 | test('building the typescript project works', async t => { 57 | const projectPath = path.join(fixturesPath, 'typescript') 58 | const buildPath = path.join(projectPath, 'build') 59 | 60 | const config = require(path.join(projectPath, 'webpack.config.js')) 61 | await runWebpack(config) 62 | 63 | global.window = new JSDOM('') 64 | global.document = global.window.document 65 | require(path.join(buildPath, 'bundle.js')) 66 | 67 | // Check if bundle contains injected process.env.TEST 68 | const bundleContents = await fs.readFile(path.join(buildPath, 'bundle.js'), { encoding: 'utf8' }) 69 | t.true(bundleContents.indexOf('module.exports = "This is the injected process.env.TEST!"') > -1) 70 | }) 71 | 72 | test('the postcss/sass/source-maps project build does not fail', async t => { 73 | // Regression test for https://github.com/andywer/webpack-blocks/issues/116 74 | 75 | const projectPath = path.join(fixturesPath, 'postcss-sass-sourcemaps') 76 | 77 | const config = require(path.join(projectPath, 'webpack.config.js')) 78 | await runWebpack(config) 79 | 80 | t.pass() 81 | }) 82 | 83 | test('building the sass/css-modules project works', async t => { 84 | const projectPath = path.join(fixturesPath, 'sass-css-modules') 85 | const buildPath = path.join(projectPath, 'build') 86 | 87 | const config = require(path.join(projectPath, 'webpack.config.js')) 88 | await runWebpack(config) 89 | 90 | global.window = new JSDOM('') 91 | global.document = global.window.document 92 | require(path.join(buildPath, 'bundle.js')) 93 | 94 | // Check if CSS file contains correct content 95 | const styleContents = await fs.readFile(path.join(buildPath, 'styles.css'), { encoding: 'utf8' }) 96 | t.truthy(removeWhitespaces(styleContents).match(/\.styles--myClass--[0-9a-zA-Z]+{margin:10px;}/)) 97 | t.truthy( 98 | removeWhitespaces(styleContents).match(/\.styles--myClass--[0-9a-zA-Z]+:hover{color:#ff0000;}/) 99 | ) 100 | }) 101 | 102 | function runWebpack(config) { 103 | return new Promise((resolve, reject) => { 104 | webpack(config, (error, stats) => { 105 | if (error) { 106 | reject(error) 107 | } else if (stats.hasErrors()) { 108 | // eslint-disable-next-line no-console 109 | stats.toJson().errors.forEach(error => console.error(error, '\n')) 110 | reject(new Error('Webpack soft error occured. See stderr output.')) 111 | } else { 112 | resolve(stats) 113 | } 114 | }) 115 | }) 116 | } 117 | 118 | function removeWhitespaces(string) { 119 | return string.replace(/\s/g, '') 120 | } 121 | -------------------------------------------------------------------------------- /packages/webpack/__tests__/entryPoint.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const sinon = require('sinon') 3 | const { entryPoint } = require('../index') 4 | 5 | test('entryPoint() should normalize string to object entry', t => { 6 | const merge = sinon.spy(() => prevConfig => prevConfig) 7 | 8 | entryPoint('./test.js')(null, { merge })({}) 9 | 10 | t.is(merge.callCount, 1) 11 | t.deepEqual(merge.lastCall.args, [ 12 | { 13 | entry: { 14 | main: ['./test.js'] 15 | } 16 | } 17 | ]) 18 | }) 19 | 20 | test('entryPoint() should normalize string array to object entry', t => { 21 | const merge = sinon.spy(() => prevConfig => prevConfig) 22 | 23 | entryPoint(['./test.js', './test2.js'])(null, { merge })({}) 24 | 25 | t.is(merge.callCount, 1) 26 | t.deepEqual(merge.lastCall.args, [ 27 | { 28 | entry: { 29 | main: ['./test.js', './test2.js'] 30 | } 31 | } 32 | ]) 33 | }) 34 | 35 | test("entryPoint() should normalize an entry object's values", t => { 36 | const merge = sinon.spy(() => prevConfig => prevConfig) 37 | 38 | entryPoint({ 39 | main: './app.js', 40 | test: ['./test.js'] 41 | })(null, { merge })({}) 42 | 43 | t.is(merge.callCount, 1) 44 | t.deepEqual(merge.lastCall.args, [ 45 | { 46 | entry: { 47 | main: ['./app.js'], 48 | test: ['./test.js'] 49 | } 50 | } 51 | ]) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/webpack/__tests__/integration.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import path from 'path' 3 | import { 4 | createConfig, 5 | entryPoint, 6 | match, 7 | setOutput, 8 | sourceMaps, 9 | resolve, 10 | setMode, 11 | optimization 12 | } from '../index' 13 | import { css, file, url } from '@webpack-blocks/assets' 14 | import babel from '@webpack-blocks/babel' 15 | import devServer from '@webpack-blocks/dev-server' 16 | import postcss from '@webpack-blocks/postcss' 17 | import sass from '@webpack-blocks/sass' 18 | 19 | test('complete webpack config creation', t => { 20 | const images = ['*.gif', '*.jpg', '*.jpeg', '*.png', '*.webp'] 21 | const fonts = ['*.eot', '*.ttf', '*.woff', '*.woff2'] 22 | 23 | const webpackConfig = createConfig([ 24 | setMode('development'), 25 | entryPoint('./src/main.js'), 26 | setOutput('./build/bundle.js'), 27 | babel(), 28 | sourceMaps(), 29 | devServer({ 30 | proxy: { 31 | '/api/*': { target: 'http://localhost:8080' } 32 | } 33 | }), 34 | match('*.scss', [ 35 | css.modules({ 36 | modules: { 37 | localIdentName: '[name]--[local]--[hash:base64:5]' 38 | } 39 | }), 40 | postcss(), 41 | sass() 42 | ]), 43 | match(images, { exclude: /node_modules/ }, [ 44 | url({ 45 | limit: 10000 46 | }) 47 | ]), 48 | match(fonts, [file()]), 49 | optimization({ 50 | splitChunks: 'all' 51 | }) 52 | ]) 53 | 54 | t.is(webpackConfig.module.rules.length, 4) 55 | t.deepEqual(webpackConfig.module.rules[0], { 56 | test: /^.*\.scss$/, 57 | use: [ 58 | { 59 | loader: 'style-loader', 60 | options: {} 61 | }, 62 | { 63 | loader: 'css-loader', 64 | options: { 65 | modules: { 66 | localIdentName: '[name]--[local]--[hash:base64:5]' 67 | }, 68 | importLoaders: 1 69 | } 70 | }, 71 | { 72 | loader: 'postcss-loader', 73 | options: {} 74 | }, 75 | { 76 | loader: 'sass-loader', 77 | options: {} 78 | } 79 | ] 80 | }) 81 | t.deepEqual(webpackConfig.module.rules[1], { 82 | test: [/^.*\.gif$/, /^.*\.jpg$/, /^.*\.jpeg$/, /^.*\.png$/, /^.*\.webp$/], 83 | exclude: /node_modules/, 84 | use: [ 85 | { 86 | loader: 'url-loader', 87 | options: { 88 | limit: 10000 89 | } 90 | } 91 | ] 92 | }) 93 | t.deepEqual(webpackConfig.module.rules[2], { 94 | test: [/^.*\.eot$/, /^.*\.ttf$/, /^.*\.woff$/, /^.*\.woff2$/], 95 | use: [ 96 | { 97 | loader: 'file-loader', 98 | options: {} 99 | } 100 | ] 101 | }) 102 | t.deepEqual(webpackConfig.module.rules[3], { 103 | test: /\.(js|jsx)$/, 104 | exclude: /node_modules/, 105 | use: [ 106 | { 107 | loader: 'babel-loader', 108 | options: { 109 | cacheDirectory: true 110 | } 111 | } 112 | ] 113 | }) 114 | 115 | t.is(webpackConfig.mode, 'development') 116 | 117 | t.deepEqual(webpackConfig.entry, { main: ['./src/main.js'] }) 118 | 119 | t.deepEqual(webpackConfig.devServer, { 120 | hot: true, 121 | hotOnly: true, 122 | historyApiFallback: true, 123 | inline: true, 124 | clientLogLevel: 'error', 125 | stats: 'errors-only', 126 | proxy: { 127 | '/api/*': { target: 'http://localhost:8080' } 128 | } 129 | }) 130 | 131 | t.deepEqual(webpackConfig.output, { 132 | filename: 'bundle.js', 133 | path: path.resolve('./build') 134 | }) 135 | 136 | t.is(webpackConfig.devtool, 'cheap-module-eval-source-map') 137 | 138 | t.deepEqual(webpackConfig.optimization, { 139 | splitChunks: 'all' 140 | }) 141 | 142 | t.deepEqual(Object.keys(webpackConfig).sort(), [ 143 | 'devServer', 144 | 'devtool', 145 | 'entry', 146 | 'mode', 147 | 'module', 148 | 'optimization', 149 | 'output', 150 | 'plugins', 151 | 'resolve', 152 | 'stats' 153 | ]) 154 | }) 155 | 156 | test('createConfig() creates a minimal configuration', t => { 157 | const webpackConfig = createConfig([entryPoint('./src/main.js'), setOutput('./build/bundle.js')]) 158 | 159 | t.deepEqual(webpackConfig, { 160 | entry: { 161 | main: ['./src/main.js'] 162 | }, 163 | module: { 164 | rules: [] 165 | }, 166 | output: { 167 | filename: 'bundle.js', 168 | path: path.resolve('./build') 169 | }, 170 | stats: { 171 | children: false, 172 | chunks: false, 173 | modules: false, 174 | reasons: false 175 | }, 176 | plugins: [], 177 | resolve: { 178 | extensions: ['.js', '.json'] 179 | } 180 | }) 181 | }) 182 | 183 | test('context contains necessary properties', t => { 184 | t.plan(5) 185 | 186 | createConfig([ 187 | context => { 188 | // context.webpack 189 | t.is(typeof context.webpack, 'function') 190 | t.is(typeof context.webpack.EnvironmentPlugin, 'function') 191 | 192 | // context.webpackVersion 193 | t.is(typeof context.webpackVersion, 'object') 194 | t.is(typeof context.webpackVersion.major, 'number') 195 | t.is(typeof context.webpackVersion.minor, 'number') 196 | 197 | return prevConfig => prevConfig 198 | } 199 | ]) 200 | }) 201 | 202 | test('prepends custom extension to default ones', t => { 203 | const expectedExtensionOrder = ['.custom.js', '.js', '.json'] 204 | 205 | const webpackConfig = createConfig([resolve({ extensions: ['.custom.js'] })]) 206 | 207 | const actualExtensions = webpackConfig.resolve.extensions 208 | 209 | t.deepEqual(actualExtensions, expectedExtensionOrder) 210 | }) 211 | -------------------------------------------------------------------------------- /packages/webpack/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack base config block. 3 | * 4 | * @see https://webpack.github.io/docs/configuration.html 5 | */ 6 | 7 | const core = require('@webpack-blocks/core') 8 | const webpack = require('webpack') 9 | const webpackMerge = require('webpack-merge') 10 | const path = require('path') 11 | const parseVersion = require('./lib/parseVersion') 12 | 13 | const webpackVersion = parseVersion(require('webpack/package.json').version) 14 | 15 | exports.env = core.env 16 | exports.group = core.group 17 | exports.match = core.match 18 | exports.when = core.when 19 | 20 | exports.createConfig = createConfig 21 | 22 | exports.setMode = setMode 23 | exports.addPlugins = addPlugins 24 | exports.customConfig = customConfig 25 | exports.defineConstants = require('./lib/defineConstants') 26 | exports.setEnv = require('./lib/setEnv') 27 | exports.entryPoint = entryPoint 28 | exports.performance = performance 29 | exports.optimization = optimization 30 | exports.resolve = resolve 31 | exports.setContext = setContext 32 | exports.setDevTool = setDevTool 33 | exports.setOutput = setOutput 34 | exports.sourceMaps = sourceMaps 35 | 36 | /** 37 | * Takes an array of webpack blocks and creates a webpack config out of them. 38 | * Each webpack block is a callback function which will be invoked to return a 39 | * partial webpack config. These partial configs are merged to create the 40 | * final, complete webpack config that will be returned. 41 | * 42 | * Wraps @webpack-blocks/core's `createConfig`. 43 | * 44 | * @param {Function[]} configSetters Array of functions as returned by webpack blocks. 45 | * @return {object} Webpack config object. 46 | */ 47 | function createConfig(configSetters) { 48 | return core.createConfig({ webpack, webpackVersion }, [createEmptyConfig].concat(configSetters)) 49 | } 50 | 51 | function createEmptyConfig(context, util) { 52 | return util.merge({ 53 | module: { 54 | rules: [] 55 | }, 56 | plugins: [] 57 | }) 58 | } 59 | 60 | /** 61 | * @see https://webpack.js.org/concepts/mode 62 | */ 63 | function setMode(mode) { 64 | return (context, util) => { 65 | context.mode = mode 66 | return util.merge({ mode }) 67 | } 68 | } 69 | 70 | /** 71 | * Adds one or multiple entry points. If the parameter is not an object the 72 | * entry point(s) will be added to the default chunk named `main`. 73 | * 74 | * @param {object|string[]|string} entry 75 | * @see https://webpack.github.io/docs/configuration.html#entry 76 | */ 77 | function entryPoint(entry) { 78 | return (context, util) => 79 | util.merge({ 80 | entry: normalizeEntry(entry) 81 | }) 82 | } 83 | 84 | function normalizeEntry(entry) { 85 | if (Array.isArray(entry)) { 86 | return { 87 | main: entry 88 | } 89 | } else if (typeof entry === 'string') { 90 | return { 91 | main: [entry] 92 | } 93 | } else if (typeof entry === 'object') { 94 | Object.keys(entry).forEach(entryName => { 95 | if (!Array.isArray(entry[entryName])) { 96 | entry[entryName] = [entry[entryName]] 97 | } 98 | }) 99 | return entry 100 | } else { 101 | throw new Error(`Expected entry point to be object, array or string. Instead got: ${entry}`) 102 | } 103 | } 104 | 105 | /** 106 | * @see https://webpack.github.io/docs/configuration.html#plugins 107 | */ 108 | function addPlugins(plugins) { 109 | return (context, util) => util.merge({ plugins }) 110 | } 111 | 112 | function customConfig(wpConfig) { 113 | return (context, util) => util.merge(wpConfig) 114 | } 115 | 116 | /** 117 | * @param {object} performanceBudget 118 | * @param {number} performanceBudget.maxAssetSize 119 | * @param {number} performanceBudget.maxEntrypointSize 120 | * @param {string} performanceBudget.hints 'warning' or 'error' 121 | */ 122 | function performance(performanceBudget) { 123 | return (context, util) => 124 | util.merge({ 125 | performance: performanceBudget 126 | }) 127 | } 128 | 129 | /** 130 | * @param {object} optimizationOptions 131 | * @see https://webpack.js.org/configuration/optimization/ 132 | */ 133 | function optimization(optimizationOptions) { 134 | return (context, util) => 135 | util.merge({ 136 | optimization: optimizationOptions 137 | }) 138 | } 139 | 140 | /** 141 | * @see https://webpack.js.org/configuration/resolve/ 142 | */ 143 | function resolve(config) { 144 | const strategy = { 'resolve.extensions': 'prepend' } 145 | const merge = webpackMerge.smartStrategy(strategy) 146 | 147 | return () => prevConfig => 148 | merge(prevConfig, { 149 | resolve: config 150 | }) 151 | } 152 | 153 | /** 154 | * @see https://webpack.github.io/docs/configuration.html#context 155 | */ 156 | function setContext(contextPath) { 157 | return (context, util) => 158 | util.merge({ 159 | context: path.resolve(contextPath) 160 | }) 161 | } 162 | 163 | /** 164 | * @see https://webpack.github.io/docs/configuration.html#devtool 165 | */ 166 | function setDevTool(devtool) { 167 | return (context, util) => util.merge({ devtool }) 168 | } 169 | 170 | /** 171 | * @see https://webpack.github.io/docs/configuration.html#output 172 | */ 173 | function setOutput(output) { 174 | if (typeof output === 'string') { 175 | output = { 176 | filename: path.basename(output) || 'bundle.js', 177 | path: path.resolve(path.dirname(output) || './build') 178 | } 179 | } 180 | 181 | return (context, util) => util.merge({ output }) 182 | } 183 | 184 | /** 185 | * Just a convenience wrapper to enable sourcemaps in an easier-to-read fashion 186 | * than `setDevTool()`. 187 | * @TODO: Only sets the javascript sourcemaps now. Would be nice to make loaders 188 | * enable their specific sourcemaps when `sourceMaps()` is used. 189 | * 190 | * @param {string} [devtool] 191 | * @return {Function} 192 | */ 193 | function sourceMaps(devtool = 'cheap-module-eval-source-map') { 194 | return (context, util) => { 195 | context.sourceMaps = true 196 | 197 | return util.merge({ devtool }) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /packages/webpack/lib/__tests__/defineConstants.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const { createConfig } = require('@webpack-blocks/core') 3 | const defineConstants = require('../defineConstants') 4 | 5 | const webpack = { 6 | DefinePlugin: function DefinePluginMock(definitions) { 7 | this.definitions = definitions 8 | } 9 | } 10 | 11 | test('defineConstants() creates a single DefinePlugin instance only', t => { 12 | const config = createConfig({ webpack }, [ 13 | defineConstants({ a: 'a' }), 14 | defineConstants({ b: 'b' }) 15 | ]) 16 | 17 | t.is(Object.keys(config.plugins).length, 1) 18 | t.deepEqual(Object.keys(config.plugins[0].definitions), ['a', 'b']) 19 | }) 20 | 21 | test('defineConstants() encodes the values', t => { 22 | const config = createConfig({ webpack }, [ 23 | defineConstants({ 24 | foo: 'foo', 25 | bar: { 26 | baz: 3 27 | } 28 | }) 29 | ]) 30 | 31 | t.is(Object.keys(config.plugins).length, 1) 32 | t.deepEqual(config.plugins[0].definitions, { 33 | foo: '"foo"', 34 | bar: '{\n "baz": 3\n}' 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/webpack/lib/__tests__/parseVersion.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const parseVersion = require('../parseVersion') 3 | 4 | test('parseVersion() works', t => { 5 | t.deepEqual(parseVersion('1.2.3'), { 6 | major: 1, 7 | minor: 2, 8 | patch: 3, 9 | prerelease: '', 10 | raw: '1.2.3' 11 | }) 12 | 13 | t.deepEqual(parseVersion('0.12.0-beta'), { 14 | major: 0, 15 | minor: 12, 16 | patch: 0, 17 | prerelease: 'beta', 18 | raw: '0.12.0-beta' 19 | }) 20 | 21 | t.deepEqual(parseVersion('2.2.0-rc.1'), { 22 | major: 2, 23 | minor: 2, 24 | patch: 0, 25 | prerelease: 'rc.1', 26 | raw: '2.2.0-rc.1' 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/webpack/lib/__tests__/setEnv.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const { createConfig } = require('@webpack-blocks/core') 3 | const setEnv = require('../setEnv') 4 | 5 | const webpack = { 6 | EnvironmentPlugin: function EnvironmentPluginMock(definitions) { 7 | this.definitions = definitions 8 | } 9 | } 10 | 11 | test('setEnv() creates a single EnvironmentPlugin instance only', t => { 12 | const config = createConfig({ webpack }, [setEnv(['a']), setEnv({ b: 'foo' })]) 13 | 14 | t.is(Object.keys(config.plugins).length, 1) 15 | t.deepEqual(config.plugins[0].definitions, { a: undefined, b: 'foo' }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/webpack/lib/defineConstants.js: -------------------------------------------------------------------------------- 1 | module.exports = defineConstants 2 | 3 | /** 4 | * Replaces constants in your source code with a value (`process.env.NODE_ENV` 5 | * for example) using the `webpack.DefinePlugin`. Every constant's value is 6 | * `JSON.stringify()`-ed first, so you don't have to remember. 7 | * 8 | * Using `defineConstants` multiple times results in a single 9 | * DefinePlugin instance configured to do all the replacements. 10 | * 11 | * @param {object} constants { [constantName: string]: * } 12 | * @return {Function} 13 | */ 14 | function defineConstants(constants) { 15 | const setter = context => prevConfig => { 16 | context.defineConstants = Object.assign({}, context.defineConstants, constants) 17 | return prevConfig 18 | } 19 | 20 | return Object.assign(setter, { post: addDefinePlugin }) 21 | } 22 | 23 | function addDefinePlugin(context, util) { 24 | const stringify = value => JSON.stringify(value, null, 2) 25 | const stringifiedConstants = mapProps(context.defineConstants, stringify) 26 | 27 | return util.addPlugin(new context.webpack.DefinePlugin(stringifiedConstants)) 28 | } 29 | 30 | function mapProps(object, valueMapper) { 31 | return Object.keys(object) 32 | .map(propKey => ({ [propKey]: valueMapper(object[propKey]) })) 33 | .reduce((newObject, partial) => Object.assign(newObject, partial), {}) 34 | } 35 | -------------------------------------------------------------------------------- /packages/webpack/lib/parseVersion.js: -------------------------------------------------------------------------------- 1 | module.exports = parseVersion 2 | 3 | /** 4 | * Parse semver-compliant version string. 5 | * 6 | * @param {string} versionString 7 | * @return {object} { major: number, minor: number, patch: number, prerelease: string, raw: string } 8 | */ 9 | function parseVersion(versionString) { 10 | const [release, prerelease] = versionString.split('-') 11 | const splitRelease = release.split('.').map(number => parseInt(number, 10)) 12 | 13 | return { 14 | major: splitRelease[0], 15 | minor: splitRelease[1], 16 | patch: splitRelease[2], 17 | prerelease: prerelease || '', 18 | raw: versionString 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/webpack/lib/setEnv.js: -------------------------------------------------------------------------------- 1 | module.exports = setEnv 2 | 3 | /** 4 | * Replaces constants in your source code with values from `process.env` 5 | * using the `webpack.EnvironmentPlugin`. 6 | * 7 | * Using `setEnv` multiple times results in a single 8 | * EnvironmentPlugin instance configured to do all the replacements. 9 | * 10 | * @param {string[]|object} constants 11 | * @return {Function} 12 | */ 13 | function setEnv(constants) { 14 | const setter = context => prevConfig => { 15 | context.setEnv = Object.assign({}, context.setEnv, toObject(constants)) 16 | return prevConfig 17 | } 18 | 19 | return Object.assign(setter, { post: addEnvironmentPlugin }) 20 | } 21 | 22 | function addEnvironmentPlugin(context, util) { 23 | return util.addPlugin(new context.webpack.EnvironmentPlugin(context.setEnv)) 24 | } 25 | 26 | function toObject(constants) { 27 | if (Array.isArray(constants)) { 28 | return constants.reduce((result, constant) => { 29 | // Setting the default (fallback) value to `undefined` 30 | // to make sure the EnvironmentPlugin will throw a warning 31 | // in case if `process.env[constant]` was not defined too. 32 | result[constant] = undefined 33 | return result 34 | }, {}) 35 | } 36 | 37 | return constants 38 | } 39 | -------------------------------------------------------------------------------- /packages/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-blocks/webpack", 3 | "version": "2.1.0", 4 | "description": "Webpack block for the webpack 2.x base configuration.", 5 | "license": "MIT", 6 | "author": "Andy Wermke ", 7 | "engines": { 8 | "node": ">= 6.0" 9 | }, 10 | "keywords": [ 11 | "webpack", 12 | "webpack-blocks" 13 | ], 14 | "repository": "andywer/webpack-blocks", 15 | "bugs": "https://github.com/andywer/webpack-blocks/issues", 16 | "dependencies": { 17 | "@webpack-blocks/core": "^2.1.0", 18 | "webpack-merge": "^4.2.2" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^10.11.6", 22 | "@webpack-blocks/assets": "^2.1.0", 23 | "@webpack-blocks/babel": "^2.1.0", 24 | "@webpack-blocks/dev-server": "^2.1.0", 25 | "@webpack-blocks/extract-text": "^2.1.0", 26 | "@webpack-blocks/postcss": "^2.1.0", 27 | "@webpack-blocks/sass": "^2.1.0", 28 | "@webpack-blocks/tslint": "^2.0.1", 29 | "@webpack-blocks/typescript": "^2.0.1", 30 | "awesome-typescript-loader": "^5.2.1", 31 | "babel-loader": "^8.0.4", 32 | "css-loader": "^1.0.0", 33 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 34 | "jsdom": "^16.5.0", 35 | "mz": "^2.7.0", 36 | "node-sass": "^4.12.0", 37 | "postcss-loader": "^3.0.0", 38 | "postcss-simple-vars": "^5.0.1", 39 | "sass-loader": "^7.1.0", 40 | "sinon": "^6.3.5", 41 | "style-loader": "^0.23.1", 42 | "tslint": "^5.11.0", 43 | "tslint-loader": "^3.6.0", 44 | "typescript": "^3.1.2", 45 | "webpack": "^4.20.2" 46 | }, 47 | "peerDependencies": { 48 | "webpack": "^4.0.0" 49 | } 50 | } 51 | --------------------------------------------------------------------------------