📦 Configure webpack using functional feature blocks.
JavaScript Other
Latest commit e28eb5a Jan 15, 2017 @andywer committed on GitHub Merge pull request #84 from andywer/bugfix/drop-css-loader-default-ex…
…clude

Drop `node_modules/` exclusion from default css-loader

README.md

webpack-blocks

Functional building blocks for the webpack config. Compose it using feature middlewares like Babel, PostCSS, HMR (Hot Module Replacement), …

Missing anything? Write your own and share them!

Build Status JavaScript Style Guide

"Finally, webpack config done right. (...) Webpack clearly wants to stay low-level. So it makes total sense to outsource configuring it to well designed blocks instead of copy-paste."

Dan Abramov via twitter (Co-author of Redux, Create React App and React Hot Loader)


v0.3 Update

Version 0.3.x of webpack-blocks comes with a bunch of new features, but also with a major breaking change under the hood.

Every block has been updated, but you have to make sure all webpack-blocks packages you use are version >= 0.3.0 or otherwise all packages are < 0.3.0.

On the upside we don't consider it an early release anymore and the next major release you see might very well be v1.0 👌

Read the release notes here.


Installation

npm install --save-dev @webpack-blocks/webpack @webpack-blocks/babel6 ...

Usage

Create a webpack 2 config with Babel support, dev server, HMR and PostCSS autoprefixer:

const { createConfig, defineConstants, env, entryPoint, setOutput, sourceMaps } = require('@webpack-blocks/webpack2')
const babel = require('@webpack-blocks/babel6')
const devServer = require('@webpack-blocks/dev-server2')
const postcss = require('@webpack-blocks/postcss')
const autoprefixer = require('autoprefixer')

module.exports = createConfig([
  entryPoint('./src/main.js'),
  setOutput('./build/bundle.js'),
  babel(),
  postcss([
    autoprefixer({ browsers: ['last 2 versions'] })
  ]),
  defineConstants({
    'process.env.NODE_ENV': process.env.NODE_ENV
  }),
  env('development', [
    devServer(),
    devServer.proxy({
      '/api': { target: 'http://localhost:3000' }
    }),
    sourceMaps()
  ])
])

Wanna use CSS modules? No problem!

const cssModules = require('@webpack-blocks/css-modules')

...

module.exports = createConfig([
  ...
  cssModules()
])

Check out the sample app to see a webpack config in action or read how to create your own blocks.

Available webpack blocks

Missing something? Write and publish your own webpack blocks!

Switching to Webpack 2

One of the nice gimmicks of using webpack-blocks is that you can switch between webpack versions in an instant!

Just use the webpack 2 versions of the following blocks:

@webpack-blocks/webpack -> @webpack-blocks/webpack2
@webpack-blocks/dev-server -> @webpack-blocks/dev-server2
@webpack-blocks/extract-text -> @webpack-blocks/extract-text2

Design principles

  • Extensibility first
  • Uniformity for easy composition
  • Keep everything configurable
  • But provide sane defaults

group() (presets)

You have got some projects with a similar, yet not identical webpack configuration? Seems like you are looking for something preset-ish!

Fortunately, this is also very simple:

const { env, group } = require('@webpack-blocks/webpack')
const babel = require('@webpack-blocks/babel6')
const devServer = require('@webpack-blocks/dev-server')

function myPreset (proxyConfig) {
  return group([
    babel(),
    env('development', [
      devServer(),
      devServer.proxy(proxyConfig)
    ])
  ])
}

The key feature is the group() method which takes a set of blocks and returns a new block that combines all their functionality.

Then use your preset like this:

const { createConfig } = require('@webpack-blocks/webpack')

module.exports = createConfig([
  myPreset({
    '/api': { target: 'http://localhost:3000' }
  }),
  ...   // add more blocks here
])

You might want to know

Can I get rid of the default loaders?

The createConfig() function sets some generic default loaders. This should not be a problem. If does happen to be a problem you can also create a "vanilla" configuration (without the defaults) by using createConfig.vanilla() instead.

How does env() work?

You might wonder how env('development', [ ... ]) works? It just checks the NODE_ENV environment variable and only applies its contained webpack blocks if it matches.

So make sure you set the NODE_ENV accordingly:

// your package.json
"scripts": {
  "build": "NODE_ENV=production webpack --config webpack.config.js",
  "start": "NODE_ENV=development webpack-dev-server --config webpack.config.js"
}

If there is no NODE_ENV set then it will just treat NODE_ENV as if it was development.

What does defineConstants() do?

defineConstants() is just a small convenience wrapper around webpack's DefinePlugin. It is composable and automatically encodes the values. Use it to replace constants in your code by their values at build time.

So having a defineConstants({ 'process.env.FOO': 'foo' }) and a defineConstants({ 'process.env.BAR': 'bar' }) in your config means the resulting webpack config will finally contain a single new webpack.DefinePlugin({ 'process.env.FOO': '"FOO"', 'process.env.BAR': '"BAR"' }), thus replacing any occurence of process.env.FOO and process.env.BAR with the given values.

What does a block look like from the inside?

A webpack block is just a function and requires no dependencies at all (🎉🎉), thus making it easy to write your own blocks and share them with the community.

Take the babel6 webpack block for instance:

/**
 * @param {object} [options]
 * @param {RegExp|Function|string}  [options.exclude]   Directories to exclude.
 * @return {Function}
 */
function babel (options) {
  const { exclude = /\/node_modules\// } = options || {}

  return (context) => ({
    module: {
      loaders: [
        {
          // we use a `MIME type => RegExp` abstraction here in order to have consistent regexs
          test: context.fileType('application/javascript'),
          exclude: Array.isArray(exclude) ? exclude : [ exclude ],
          loaders: [ 'babel-loader?cacheDirectory' ]
        }
      ]
    }
  })
}

Add a README and a package.json and you are ready to ship.

For more details see How to write a block.

I need some custom webpack config snippet!

No problem. If you don't want to write your own webpack block you can just use customConfig():

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { addPlugins, customConfig } = require('@webpack-blocks/webpack')

...

module.exports = createConfig([
  ...
  addPlugins([
    // Add a custom webpack plugin
    new HtmlWebpackPlugin({
      inject: true,
      template: './index.html'
    })
  ]),
  customConfig({
    // Add some custom webpack config snippet
    resolve: {
      extensions: [ '.js', '.es6' ]
    }
  })
])

The object you pass to customConfig() will be merged into the webpack config using webpack-merge like any other webpack block's partial config.

Like what you see?

Support webpack-blocks by giving feedback, publishing new webpack blocks or just by 🌟 starring the project!