Skip to content

Commit

Permalink
Config cleanup
Browse files Browse the repository at this point in the history
- Unexpected properties in config are now flagged up as errors
- Added basic type validation for all config properties
- Config report now indicates if CLI config overrides were used
- Added missing documentation for webpack.copy config
- Added babel.config()
- Deprecated webpack.compat.sinon
- Simplified configuration of locales in webpack.compat config
- Split user and plugin config code out into modules under src/config/
  • Loading branch information
insin committed Dec 11, 2017
1 parent c0a5950 commit 6f918ba
Show file tree
Hide file tree
Showing 31 changed files with 1,498 additions and 780 deletions.
21 changes: 19 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# Unreleased (in `master`)

## Breaking Changes

- Validation of the configuration object provided by `nwb.config.js` files has been expanded, so previously valid config files may now be invalid:
- Unexpected properties in top-level configuration or in `babel`, `karma`, `npm` and `webpack` configuration (i.e. anything that's not documented in the [Configuration docs](https://github.com/insin/nwb/blob/master/docs/Configuration.md)) are now treated as errors.
- Basic type checking is now performed for all documented configuration properties.
- After upgrading, run `nwb check-config` to check your configuration file.

## `nwb.config.js` Config Changes

- Deprecated the `webpack.compat.sinon` flag for Sinon 1.x compatibility settings, as subsequent major versions since July 2017 support Webpack out of the box.

## Added

- You can now provide a [`karma.config()` function](https://github.com/insin/nwb/blob/master/docs/Configuration.md#config-function-1) which will be given the generated Karma config to do whatever it wants with [[#408](https://github.com/insin/nwb/issues/408)]
- You can now provide a [`babel.config()` function](https://github.com/insin/nwb/blob/master/docs/Configuration.md#config-function) which will be given the generated Babel config to do whatever it wants with.
- You can now provide a [`karma.config()` function](https://github.com/insin/nwb/blob/master/docs/Configuration.md#config-function-2) which will be given the generated Karma config to do whatever it wants with [[#408](https://github.com/insin/nwb/issues/408)]

## Changed

- Simplified configuration of locales in [`webpack.compat` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#compat-object)

## Dependencies

Expand All @@ -17,7 +33,8 @@

## Docs

- Added more headings to the [Commands docs]() to make them easier to browse, and to make feature flags such as `--copy-files` for component builds more visible [[#407](https://github.com/insin/nwb/issues/407)]
- Added missing docs for [`webpack.copy` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#copy-array--object)
- Added more headings to the [Commands docs](https://github.com/insin/nwb/blob/master/docs/Commands.md#commands) to make them easier to browse, and to make feature flags such as `--copy-files` for component builds more visible [[#407](https://github.com/insin/nwb/issues/407)]

# 0.20.0 / 2017-11-18

Expand Down
210 changes: 131 additions & 79 deletions docs/Configuration.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/guides/QuickDevelopment.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ Create a build of a React project which uses Inferno or Preact as the runtime vi
- [`do` expressions](http://babeljs.io/docs/plugins/transform-do-expressions/#detail)
- [`::` function binding operator](http://babeljs.io/docs/plugins/transform-function-bind/#detail)

- Polyfills for `Promise`, `fetch()` and `Object.assign()`, which can be disabled with a `--no-polyfill` flag if you don’t need them or want to provide your own.
- Polyfills for `Promise`, `fetch()` and `Object.assign()`, which can be disabled with a `--no-polyfill` flag if you don’t need them or want to provide your own.
- Import images and stylesheets into JavaScript like any other module, to be handled by Webpack as part of its build.

```js
Expand Down
2 changes: 1 addition & 1 deletion src/commands/build.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {getProjectType} from '../config'
import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_APP, WEB_MODULE} from '../constants'
import {getProjectType} from '../getUserConfig'
import buildInfernoApp from './build-inferno-app'
import buildPreactApp from './build-preact-app'
import buildReactApp from './build-react-app'
Expand Down
3 changes: 1 addition & 2 deletions src/commands/check-config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import getUserConfig, {UserConfigReport} from '../getUserConfig'
import getPluginConfig from '../getPluginConfig'
import {getPluginConfig, getUserConfig, UserConfigReport} from '../config'

function getFullEnv(env) {
if (env === 'dev') return 'development'
Expand Down
2 changes: 1 addition & 1 deletion src/commands/clean.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {getProjectType} from '../config'
import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_APP, WEB_MODULE} from '../constants'
import {getProjectType} from '../getUserConfig'
import cleanApp from './clean-app'
import cleanModule from './clean-module'

Expand Down
2 changes: 1 addition & 1 deletion src/commands/serve.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import {getProjectType} from '../config'
import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_APP} from '../constants'
import {UserError} from '../errors'
import {getProjectType} from '../getUserConfig'
import serveInfernoApp from './serve-inferno-app'
import servePreactApp from './serve-preact-app'
import serveReactApp from './serve-react-app'
Expand Down
2 changes: 1 addition & 1 deletion src/commands/test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import {getProjectType} from '../config'
import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT} from '../constants'
import {getProjectType} from '../getUserConfig'
import karmaServer from '../karmaServer'
import testInferno from './test-inferno'
import testPreact from './test-preact'
Expand Down
106 changes: 106 additions & 0 deletions src/config/UserConfigReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import util from 'util'

import chalk from 'chalk'
import figures from 'figures'

import {pluralise as s} from '../utils'

export default class UserConfigReport {
constructor({configFileExists, configPath} = {}) {
this.configFileExists = configFileExists
this.configPath = configPath
this.deprecations = []
this.errors = []
this.hints = []
this.hasArgumentOverrides = false
}

deprecated(path, ...messages) {
this.deprecations.push({path, messages})
}

error(path, value, message) {
this.errors.push({path, value, message})
}

hasErrors() {
return this.errors.length > 0
}

hasSomethingToReport() {
return this.errors.length + this.deprecations.length + this.hints.length > 0
}

hint(path, ...messages) {
this.hints.push({path, messages})
}

getConfigSource() {
if (this.configFileExists) {
let description = this.configPath
if (this.hasArgumentOverrides) {
description += ' (with CLI argument overrides)'
}
return description
}
else if (this.hasArgumentOverrides) {
return 'config via CLI arguments'
}
return 'funsies'
}

getReport() {
let report = []

report.push(chalk.underline(`nwb config report for ${this.getConfigSource()}`))
report.push('')

if (!this.hasSomethingToReport()) {
report.push(chalk.green(`${figures.tick} Nothing to report!`))
return report.join('\n')
}

if (this.errors.length) {
let count = this.errors.length > 1 ? `${this.errors.length} ` : ''
report.push(chalk.red.underline(`${count}Error${s(this.errors.length)}`))
report.push('')
}
this.errors.forEach(({path, value, message}) => {
report.push(`${chalk.red(`${figures.cross} ${path}`)} ${chalk.cyan('=')} ${util.inspect(value)}`)
report.push(` ${message}`)
report.push('')
})

if (this.deprecations.length) {
let count = this.deprecations.length > 1 ? `${this.deprecations.length} ` : ''
report.push(chalk.yellow.underline(`${count}Deprecation Warning${s(this.deprecations.length)}`))
report.push('')
}
this.deprecations.forEach(({path, messages}) => {
report.push(chalk.yellow(`${figures.warning} ${path}`))
messages.forEach(message => {
report.push(` ${message}`)
})
report.push('')
})

if (this.hints.length) {
let count = this.hints.length > 1 ? `${this.hints.length} ` : ''
report.push(chalk.cyan.underline(`${count}Hint${s(this.hints.length)}`))
report.push('')
}
this.hints.forEach(({path, messages}) => {
report.push(chalk.cyan(`${figures.info} ${path}`))
messages.forEach(message => {
report.push(` ${message}`)
})
report.push('')
})

return report.join('\n')
}

log() {
console.log(this.getReport())
}
}
156 changes: 156 additions & 0 deletions src/config/babel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import chalk from 'chalk'

import {pluralise as s, typeOf} from '../utils'

const BABEL_RUNTIME_OPTIONS = new Set(['helpers', 'polyfill'])

export function processBabelConfig({report, userConfig}) {
let {
cherryPick,
env,
loose,
plugins,
presets,
removePropTypes,
reactConstantElements,
runtime,
stage,
config,
...unexpectedConfig
} = userConfig.babel

let unexpectedProps = Object.keys(unexpectedConfig)
if (unexpectedProps.length > 0) {
report.error(
'babel',
unexpectedProps.join(', '),
`Unexpected prop${s(unexpectedProps.length)} in ${chalk.cyan('babel')} config - ` +
'see https://github.com/insin/nwb/blob/master/docs/Configuration.md#babel-configuration for supported config'
)
}

// cherryPick
if ('cherryPick' in userConfig.babel) {
if (typeOf(cherryPick) !== 'string' && typeOf(cherryPick) !== 'array') {
report.error(
'babel.cherryPick',
cherryPick,
`Must be a ${chalk.cyan('String')} or an ${chalk.cyan('Array')}`
)
}
}

// env
if ('env' in userConfig.babel) {
if (typeOf(env) !== 'Object') {
report.error(
'babel.env',
env,
`Must be an ${chalk.cyan('Object')}`
)
}
}

// loose
if ('loose' in userConfig.babel) {
if (typeOf(loose) !== 'boolean') {
report.error(
'babel.loose',
loose,
`Must be ${chalk.cyan('Boolean')}`
)
}
}

// plugins
if ('plugins' in userConfig.babel) {
if (typeOf(plugins) === 'string') {
userConfig.babel.plugins = [plugins]
}
else if (typeOf(userConfig.babel.plugins) !== 'array') {
report.error(
'babel.plugins',
plugins,
`Must be a ${chalk.cyan('String')} or an ${chalk.cyan('Array')}`
)
}
}

// presets
if ('presets' in userConfig.babel) {
if (typeOf(presets) === 'string') {
userConfig.babel.presets = [presets]
}
else if (typeOf(presets) !== 'array') {
report.error(
'babel.presets',
presets,
`Must be a ${chalk.cyan('String')} or an ${chalk.cyan('Array')}`
)
}
}

// removePropTypes
if ('removePropTypes' in userConfig.babel) {
if (removePropTypes !== false && typeOf(removePropTypes) !== 'object') {
report.error(
`babel.removePropTypes`,
removePropTypes,
`Must be ${chalk.cyan('false')} (to disable removal of PropTypes) ` +
`or an ${chalk.cyan('Object')} (to configure react-remove-prop-types)`
)
}
}

// reactConstantElements
if ('reactConstantElements' in userConfig.babel) {
if (typeOf(reactConstantElements) !== 'boolean') {
report.error(
'babel.reactConstantElements',
reactConstantElements,
`Must be ${chalk.cyan('Boolean')}`
)
}
}

// runtime
if ('runtime' in userConfig.babel &&
typeOf(runtime) !== 'boolean' &&
!BABEL_RUNTIME_OPTIONS.has(runtime)) {
report.error(
'babel.runtime',
runtime,
`Must be ${chalk.cyan('Boolean')}, ${chalk.cyan("'helpers'")} or ${chalk.cyan("'polyfill'")}`
)
}

// stage
if ('stage' in userConfig.babel) {
if (typeOf(stage) === 'number') {
if (stage < 0 || stage > 3) {
report.error(
'babel.stage',
stage,
`Must be between ${chalk.cyan(0)} and ${chalk.cyan(3)}`
)
}
}
else if (stage !== false) {
report.error(
'babel.stage',
stage,
`Must be a ${chalk.cyan('Number')} between ${chalk.cyan('0')} and ${chalk.cyan('3')} (to choose a stage preset), ` +
`or ${chalk.cyan('false')} (to disable use of a stage preset)`
)
}
}

// config
if ('config' in userConfig.babel && typeOf(config) !== 'function') {
report.error(
`babel.config`,
`type: ${typeOf(config)}`,
`Must be a ${chalk.cyan('Function')}`
)
}
}
3 changes: 3 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {getPluginConfig} from './plugin'
export {getProjectType, getUserConfig} from './user'
export {default as UserConfigReport} from './UserConfigReport'
Loading

0 comments on commit 6f918ba

Please sign in to comment.