@@ -0,0 +1,71 @@
# React Apps

nwb provides a template for React apps and pre-configures development tools to work with it.

## Create a new React app

```
nwb new react-app <name>
```

Creates a skeleton for a React app with the given name.

## Start the development server

```
nwb serve
```

Starts a development server which serves up your app with hot reloading.

* JavaScript and CSS changes will be applied on the fly without refreshing the page.

* Syntax errors made while the development server is running will appear as an overlay in your browser.

* Rendering errors in React components will also appear as an overlay.

## Run tests

```
nwb test
```

Runs `-test.js`

```
nwb test --server
```

Starts a Karma server which runs tests on every change.

## Create a static build

Static builds default to production mode, which strips out development code from React, performs some React elements optimisations in your components and minifies code:

```
nwb build
```

For a development build:

```
nwb build --set-env-NODE_ENV=development
```

## Clean

Deletes the static build:

```
nwb clean
```

## `nwb.config.js`

### `type` - must be `'react-app'`



### `babel` - custom Babel configuration

### `webpack` - custom Webpack configuration
@@ -0,0 +1 @@
# React Components
@@ -0,0 +1,25 @@
# Web Modules

A web module is a module published to npm which is expxected to be able to run on the browser

## Create a new web module

```
nwb new module app-name
```

## Run tests

```
nwb test
```

Starts a Karma server which runs tests on every change.

## Prepare a distribution

```
nwb dist
```

Creates an ES5 build of your module in `lib/` and a UMD build in `umd/`.

This file was deleted.

@@ -23,7 +23,7 @@
"eventsource-polyfill": "0.9.6",
"express": "4.13.3",
"glob" : "6.0.1",
"inquirer" : "0.11.0",
"inquirer": "0.11.0",
"minimist": "1.2.0",
"resolve": "1.1.6",
"rimraf": "2.4.4",
@@ -25,38 +25,21 @@ Options:
-h, --help display this help message
-v, --version print nwb's version
Common commands:
These will detect which type of project they're being run in.
build clean and build
clean delete build
test start running tests
test --once run tests once
serve serve with hot reloading
React app commands:
build-react-app build app into public/build/
serve-react-app serve with hot reloading
Generic app commands:
Module creation commands:
new react-app <name> create a React app
new react-component <name> create a React component with a demo app
new web-module <name> craete a web module
build-app build app into public/build/
clean-app delete public/build/
Web module commands:
build-module transpile from src/ into lib/
build-umd create UMD builds from src/index.js into umd/
clean-module delete lib/ and umd/
dist clean and build module and demo app (if present)
Web module demo app commands:
build-demo build demo app from demo/src/index.js into demo/dist/
clean-demo delete demo/dist/
dist-demo clean and build demo app
Common commands:
build clean and build
clean delete build
test run tests
--server keep running tests on every change
React module commands:
serve serve an app, or a component's demo app, with hot reloading
--info show webpack build info
--port port to run the dev server on [3000]
`)
process.exit(0)
}
@@ -1,11 +1,26 @@
import path from 'path'
import exec from '../exec'

let config = path.join(__dirname, '../config/webpack-build.js')
import webpackBuild from '../webpackBuild'

console.log('nwb: build-demo')
exec(
'webpack',
['--config=' + config, '--set-env-ORIGINAL_CWD=' + process.cwd()],
{cwd: path.join(__dirname, '..')
})
/**
* Build a web module's demo app from demo/src/index.js.
*/
export default function(args) {
let pkg = require(path.resolve('package.json'))

webpackBuild(args, {
entry: path.resolve('demo/src/index.js'),
output: {
filename: 'demo.js',
path: path.resolve('demo/dist')
},
plugins: {
html: {
template: require.resolve('html-webpack-template/index.html'),
appMountId: 'app',
mobile: true,
title: `${pkg.name} ${pkg.version} Demo`
}
}
})
}
@@ -1,7 +1,19 @@
import path from 'path'

import webpackBuild from '../webpackBuild'

// Use a config function, as this won't be called until after NODE_ENV has been
// set by build-app and we don't want these optimisations in development builds.
// set by webpackBuild() and we don't want these optimisations in development
// builds.
let buildConfig = () => {
let config = {}
let config = {
entry: path.resolve('src/index.js'),
output: {
filename: 'app.js',
path: path.resolve('public/build'),
publicPath: 'build/'
}
}
if (process.env.NODE_ENV === 'production') {
config.loaders = {
babel: {
@@ -16,5 +28,6 @@ let buildConfig = () => {
}

export default function(args) {
require('./build-app')(args, buildConfig)
console.log(`nwb: ${args._[0]}`)
webpackBuild(args, buildConfig)
}
@@ -8,10 +8,10 @@ console.log('nwb: build-umd')
exec(
'webpack',
[`--config=${config}`, `--set-env-ORIGINAL_CWD=${cwd}`],
{cwd: path.join(__dirname, '..')
{cwd: path.join(__dirname, '../../')
})
exec(
'webpack',
[`--config=${config}`, `--set-env-ORIGINAL_CWD=${cwd}`, '--set-env-NODE_ENV=production'],
{cwd: path.join(__dirname, '..')
{cwd: path.join(__dirname, '../../')
})
@@ -1,18 +1,21 @@
import glob from 'glob'

import {REACT_APP, REACT_COMPONENT, WEB_MODULE} from '../constants'
import getUserConfig from '../getUserConfig'

export default function(args) {
var userConfig = getUserConfig()
if (userConfig.type === 'react-app') {
let userConfig = getUserConfig(args)
if (userConfig.type === REACT_APP) {
require('./clean-app')
require('./build-react-app')(args)
}
else if (glob.sync('public/').length > 0) {
require('./clean-app')
require('./build-app')(args)
}
else {
else if (userConfig.type === REACT_COMPONENT || userConfig.type === WEB_MODULE) {
require('./clean-module')
require('./build-module')
require('./build-umd')
if (userConfig.type === REACT_COMPONENT || glob.sync('demo/').length > 0) {
require('./clean-demo')
require('./build-demo')
}
}
}
@@ -1,8 +1,12 @@
import glob from 'glob'
import {REACT_APP, WEB_MODULE} from '../constants'
import getUserConfig from '../getUserConfig'

if (glob.sync('public/').length > 0) {
require('./clean-app')
}
else {
require('./clean-module')
export default function(args) {
let {type} = getUserConfig(args)
if (type === REACT_APP) {
require('./clean-app')
}
else if (type === WEB_MODULE) {
require('./clean-module')
}
}
@@ -1,7 +1,6 @@
import glob from 'glob'

// Prepare a web module for distribution
require('./clean-module')
require('./build-module')
require('./build-umd')

This file was deleted.

@@ -2,62 +2,110 @@ import path from 'path'
import {execSync} from 'child_process'

import copyTemplateDir from 'copy-template-dir'
import inquirer from 'inquirer'
import glob from 'glob'

import {REACT_APP, REACT_COMPONENT, REACT_VERSION, WEB_MODULE, MODULE_TYPES} from '../constants'
import pkg from '../../package.json'

export default function(args) {
let [cmd, moduleType, name] = args._
if (!moduleType) {
console.error(`${cmd}: a module type must be provided, one of: ${Object.keys(MODULE_TYPES).join(', ')}`)
process.exit(1)
}
if (!(moduleType in MODULE_TYPES)) {
console.error(`${cmd}: module type must be one of: ${Object.keys(MODULE_TYPES).join(', ')}`)
process.exit(1)
}
if (!name) {
console.error(`${cmd}: a module name must be provided`)
process.exit(1)
}
if (glob.sync(`${name}/`).length !== 0) {
console.error(`${cmd}: a ${name} directory already exists`)
process.exit(1)
}

let targetDir = path.join(process.cwd(), name)
MODULE_TYPES[moduleType](name, targetDir)
function getWebModulePrefs(cb) {
inquirer.prompt([
{
type: 'input',
name: 'globalVariable',
message: `Which global variable will the UMD build export?`
}
], cb)
}

const MODULE_TYPES = {
'module': function(name, targetDir, cb) {
let templateDir = path.join(__dirname, '../../templates/module')
let moduleCreators = {
[REACT_APP](name, targetDir) {
let templateDir = path.join(__dirname, `../../templates/${REACT_APP}`)
copyTemplateDir(templateDir, targetDir, {
name,
nwbVersion: pkg.version
nwbVersion: `~${pkg.version}`,
reactVersion: REACT_VERSION
}, err => {
if (err) {
console.error(err.stack)
process.exit(1)
}
console.log(`nwb: created ${targetDir}`)
console.log('nwb: installing dependencies')
execSync(`npm install react@${REACT_VERSION} react-dom@${REACT_VERSION}`, {
cwd: targetDir,
stdio: [0, 1, 2]
})
})
},

'react-app': function(name, targetDir, cb) {
let templateDir = path.join(__dirname, '../../templates/react-app')
copyTemplateDir(templateDir, targetDir, {
name,
nwbVersion: pkg.version,
reactVersion: '^0.14.0'
}, err => {
if (err) {
console.error(err.stack)
process.exit(1)
}
console.log(`nwb: created ${targetDir}`)
console.log(`nwb: installing dependencies`)
execSync('npm install --production', {cwd: targetDir, stdio: [0, 1, 2]})
[REACT_COMPONENT](name, targetDir) {
getWebModulePrefs(({globalVariable}) => {
let templateDir = path.join(__dirname, `../../templates/${REACT_COMPONENT}`)
copyTemplateDir(templateDir, targetDir, {
globalVariable,
name,
nwbVersion: `~${pkg.version}`,
reactVersion: REACT_VERSION
}, err => {
if (err) {
console.error(err.stack)
process.exit(1)
}
console.log(`nwb: created ${targetDir}`)
console.log('nwb: installing dependencies')
execSync(`npm install react@${REACT_VERSION} react-dom@${REACT_VERSION}`, {
cwd: targetDir,
stdio: [0, 1, 2]
})
})
})
},

[WEB_MODULE](name, targetDir) {
getWebModulePrefs(({globalVariable}) => {
let templateDir = path.join(__dirname, `../../templates/${WEB_MODULE}`)
copyTemplateDir(templateDir, targetDir, {
globalVariable,
name,
nwbVersion: `~${pkg.version}`
}, err => {
if (err) {
console.error(err.stack)
process.exit(1)
}
console.log(`nwb: created ${targetDir}`)
})
})
}
}

export default function(args) {
if (args._.length === 1) {
console.log(`usage: nwb new [${MODULE_TYPES.join('|')}] <name>`)
process.exit(0)
}

let moduleType = args._[1]
if (!moduleType) {
console.error(`nwb: a module type must be provided, one of: ${MODULE_TYPES.join(', ')}`)
process.exit(1)
}
if (!(moduleType in MODULE_TYPES)) {
console.error(`nwb: module type must be one of: ${MODULE_TYPES.join(', ')}`)
process.exit(1)
}

let name = args._[2]
if (!name) {
console.error(`nwb: a module name must be provided`)
process.exit(1)
}
if (glob.sync(`${name}/`).length !== 0) {
console.error(`nwb: a ${name} directory already exists`)
process.exit(1)
}

let targetDir = path.join(process.cwd(), name)
moduleCreators[moduleType](name, targetDir)
}
@@ -1,4 +1,15 @@
import path from 'path'

import serveReact from '../serveReact'

export default function(args) {
args._[1] = 'src/index.js'
require('./serve-react')(args)
serveReact(args, {
entry: path.resolve('src/index.js'),
output: {
path: path.resolve('public/build'),
filename: 'app.js',
publicPath: '/build/'
},
staticPath: path.resolve('public')
})
}
@@ -0,0 +1,27 @@
import path from 'path'

import serveReact from '../serveReact'

/**
* Serve a web module React demo app from demo/src/index.js.
*/
export default function(args) {
let pkg = require(path.resolve('package.json'))
serveReact(args, {
entry: path.resolve('demo/src/index.js'),
output: {
filename: 'app.js',
// This doesn't really matter, as files will be served from memory
path: __dirname,
publicPath: '/'
},
plugins: {
html: {
template: require.resolve('html-webpack-template/index.html'),
appMountId: args['mount-id'] || 'app',
mobile: true,
title: `${pkg.name} ${pkg.version} Demo`
}
}
})
}
@@ -1,73 +1,33 @@
import path from 'path'

import resolve from 'resolve'
import glob from 'glob'

import devServer from '../devServer'
import getUserConfig from '../getUserConfig'
import createWebpackConfig from '../createWebpackConfig'
import serveReact from '../serveReact'

export default function(args) {
let [cmd, entryPath] = args._

if (!entryPath) {
console.error(`${cmd}: the path to an entry module must be provided`)
process.exit(1)
}

let cwd = process.cwd()
let entry = path.resolve(entryPath)
let port = args.port || 3000
let reactPath
try {
reactPath = resolve.sync('react', {basedir: cwd})
if (args._.length === 1) {
console.log(`usage: nwb serve-react <entry module>`)
}
catch (e) {
console.error(`${cmd}: React must be installed locally`)
let entry = args._[1]
if (glob.sync(entry).length === 0) {
console.error(`entry module not found: ${path.join(process.cwd(), entry)}`)
process.exit(1)
}
let userConfig = getUserConfig(args.config)
let webpackConfig = createWebpackConfig(cwd, {
server: true,
devtool: 'eval-source-map',
entry: [
// Polyfill EventSource for IE, as webpack-hot-middleware/client uses it
require.resolve('eventsource-polyfill'),
require.resolve('webpack-hot-middleware/client'),
entry
],

serveReact(args, {
entry,
output: {
path: path.resolve('public/build'),
filename: 'app.js',
publicPath: '/build/'
path: __dirname,
publicPath: '/'
},
loaders: {
babel: {
query: {
loose: 'all',
plugins: [
require.resolve('babel-plugin-react-display-name'),
require.resolve('babel-plugin-react-transform')
],
extra: {
'react-transform': {
transforms: [{
transform: require.resolve('react-transform-hmr'),
imports: [reactPath],
locals: ['module']
}, {
transform: require.resolve('react-transform-catch-errors'),
imports: [reactPath, require.resolve('redbox-noreact')]
}]
}
}
}
plugins: {
html: {
template: require.resolve('html-webpack-template/index.html'),
appMountId: args['mount-id'] || 'app',
mobile: true,
title: args.title || 'serve-react'
}
}
}, userConfig.webpack)

devServer(webpackConfig, {
noInfo: !args.info,
port,
staticPath: path.resolve('public')
})
}
@@ -1,12 +1,16 @@
import {REACT_APP, REACT_COMPONENT} from '../constants'
import getUserConfig from '../getUserConfig'

export default function(args) {
var userConfig = getUserConfig()
if (userConfig.type === 'react-app') {
let userConfig = getUserConfig(args)
if (userConfig.type === REACT_APP) {
require('./serve-react-app')(args)
}
else if (userConfig.type === REACT_COMPONENT) {
require('./serve-react-demo')(args)
}
else {
console.log('serve: unable to serve anything in the current directory')
console.log('nwb: unable to serve anything in the current module')
process.exit(1)
}
}
@@ -4,11 +4,13 @@ import exec from '../exec'
export default function(args) {
let config = path.join(__dirname, '../config/karma.js')
let cwd = process.cwd()
let karmaArgs = ['start', config, `--set-env-ORIGINAL_CWD=${cwd}`]
if (!args.server) {
karmaArgs.push('--single-run')
}

console.log('nwb: test')
exec(
'karma',
['start', config, `--set-env-ORIGINAL_CWD=${cwd}`],
{cwd: path.resolve(__dirname, '../..')}
)
exec('karma', karmaArgs, {
cwd: path.resolve(__dirname, '../../')
})
}
@@ -33,7 +33,18 @@ if (runCoverage) {
reporters.push('coverage')
}

let webpackConfig = createWebpackConfig({
// Find the node_modules directory containing nwb's dependencies
let nodeModules
if (glob.sync('../../node_modules/', {cwd: __dirname}).length > 0) {
// Global installs and npm@2 local installs have a local node_modules dir
nodeModules = path.join(__dirname, '../../node_modules')
}
else {
// Otherwise assume an npm@3 local install, with node_modules as the parent
nodeModules = path.join(__dirname, '../../../node_modules')
}

let webpackConfig = createWebpackConfig(cwd, {
server: true,
devtool: 'inline-source-map',
loaders: {
@@ -43,22 +54,11 @@ let webpackConfig = createWebpackConfig({
alias: {
'src': path.join(cwd, 'src')
},
// Resolve testing dependencies from nwb's dependencies
// Fall back to resolve testing dependencies from nwb's dependencies
fallback: [nodeModules]
}
})

// Find the node_modules directory containing nwb's dependencies
let nodeModules
if (glob.sync('../../node_modules/').length > 0) {
// Global installs and npm@2 local installs have a local node_modules dir
nodeModules = path.join(__dirname, '../../node_modules')
}
else {
// Otherwise assume an npm@3 local install, with node_modules as the parent
nodeModules = path.join(__dirname, '../../../node_modules')
}

export default function(config) {
config.set({
browsers: ['PhantomJS'],

This file was deleted.

@@ -74,19 +74,14 @@ export default {
externals,
plugins,
resolve: {
extensions: ['', '.js', '.jsx', '.json'],
modulesDirectories: ['node_modules']
},
resolveLoader: {
modulesDirectories: ['node_modules'],
root: path.join(__dirname, 'node_modules')
extensions: ['', '.js', '.jsx', '.json']
},
module: {
loaders: [
{test: /\.jsx?$/, loader: 'babel', exclude: /node_modules/},
{test: /\.jsx?$/, loader: require.resolve('babel-loader'), exclude: /node_modules/},
// TODO Revisit
{test: /\.css$/, loader: 'null'},
{test: /\.json$/, loader: 'json'}
{test: /\.css$/, loader: require.resolve('null-loader')},
{test: /\.json$/, loader: require.resolve('json-loader')}
]
}
}
@@ -0,0 +1,7 @@
export const REACT_VERSION = '~0.14.0'

export const REACT_APP = 'react-app'
export const REACT_COMPONENT = 'react-component'
export const WEB_MODULE = 'web-module'

export const MODULE_TYPES = [REACT_APP, REACT_COMPONENT, WEB_MODULE]
@@ -1,18 +1,25 @@
/**
* Functions for creating a Webpack config with:
*
* - a default set of loaders which can be customised for a particular type of
* build and further tweaked by the end user.
* - a default set of plugins which can be customised for a particular type of
* build and environment, with only configuration or a flag required to enable
* additional pre-selected plugins.
*/
import assert from 'assert'
import path from 'path'

import combineLoaders from 'webpack-combine-loaders'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import webpack, {optimize} from 'webpack'

/**
* Merge webpack loader config ({test: ..., loader: ..., query: ...}) objects.
* Merge webpack loader config ({test, loader, query, inclue, exclude}) objects.
*/
export function mergeLoaderConfig(defaultConfig = {}, buildConfig = {}, userConfig = {}) {
if (!defaultConfig && !buildConfig && !userConfig) {
return null
}

// TODO Intelligent merging instead of... this
// TODO Intelligent merging instead of this dumb overriding/query merging
return {
test: userConfig.test || buildConfig.test || defaultConfig.test,
loader: userConfig.loader || buildConfig.loader || defaultConfig.loader,
@@ -35,34 +42,22 @@ export let loaderConfigFactory = (buildConfig, userConfig) =>
({id, ...mergeLoaderConfig(defaultConfig, buildConfig[id], userConfig[id])})

/**
* Force the build to fail by exiting with a non-zero code when there are
* compilation errors.
* Create a default style-handling pipeline for either a static build (default)
* or a server build.
*/
export function failBuildOnCompilationError() {
this.plugin('done', ({compilation}) => {
if (compilation.errors && compilation.errors.length > 0) {
console.error('webpack build failed:')
compilation.errors.forEach(error => console.error(error.message))
process.exit(1)
}
})
}

export function createStyleLoader(loader, server) {
export function createStyleLoader(loader, server, prefix) {
let name = (name) => prefix ? `${prefix}-${name}` : name
let loaders = [
loader('css', {
loader: require.resolve('css-loader'),
query: {
minimize: false
}
loader(name('css'), {
loader: require.resolve('css-loader')
}),
loader('autoprefixer', {
loader(name('autoprefixer'), {
loader: require.resolve('autoprefixer-loader')
})
]

if (server) {
loaders.unshift(loader('style', {
loaders.unshift(loader(name('style'), {
loader: require.resolve('style-loader')
}))
return combineLoaders(loaders)
@@ -81,23 +76,29 @@ export function createLoaders(server, buildConfig = {}, userConfig = {}) {
loader: require.resolve('babel-loader'),
exclude: /node_modules/
}),
{
loader('css-pipeline', {
test: /\.css$/,
loader: createStyleLoader(loader, server)
},
loader: createStyleLoader(loader, server),
exclude: /node_modules/
}),
loader('vendor-css-pipeline', {
test: /\.css$/,
loader: createStyleLoader(loader, server, 'vendor'),
include: /node_modules/
}),
loader('graphics', {
test: /\.(gif|png)$/,
loader: require.resolve('url-loader'),
query: {
limit: 10240
}
}),
loader('jpg', {
loader('jpeg', {
test: /\.jpe?g$/,
loader: require.resolve('file-loader')
}),
loader('font', {
test: /\.(otf|svg|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
loader('fonts', {
test: /\.()(\?v=\d+\.\d+\.\d+)?$/,
loader: require.resolve('url-loader'),
query: {
limit: 10240
@@ -110,14 +111,48 @@ export function createLoaders(server, buildConfig = {}, userConfig = {}) {
loader('json', {
test: /\.json$/,
loader: require.resolve('json-loader')
})
}),
// Undocumented escape hatches for adding new loaders
...buildConfig._extra || [],
...userConfig._extra || []
]
}

/**
* A webpack plugin which forces the build to fail by exiting with a non-zero
* code when there are compilation errors. This is intended for use on a CI
* server which is running webpack builds.
*/
export function failBuildOnCompilationError() {
this.plugin('done', ({compilation}) => {
if (compilation.errors && compilation.errors.length > 0) {
console.error('webpack build failed:')
compilation.errors.forEach(error => console.error(error.message))
process.exit(1)
}
})
}

export function createPlugins(server, cwd, {
appStyle, banner, define, style, vendorJS, vendorStyle
// File name to use for extracted CSS - only applicable for server builds
appStyle,
// Banner comment to be added to each generated file in a UMD build
banner,
// Extra constant replacements for DefinePlugin. Since plugins aren't
// currently exposed for user config, it's assumed any user-provided defines
// have already been merged into this.
define,
// Escape hatch for adding new build-specific plugins
extra,
// Options for HtmlWebpackPlugin
html,
// Name to use for a vendor JavaScript chunk - providing a name causes it to
// be created.
vendorJS
// TODO Name to use for a vendor CSS chunk
// vendorStyle
} = {}) {
var plugins = [
let plugins = [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
...define
@@ -126,6 +161,7 @@ export function createPlugins(server, cwd, {
new optimize.OccurenceOrderPlugin()
]

// Assumption: we're always hot reloading if we're bundling on the server
if (server) {
plugins.unshift(
new webpack.HotModuleReplacementPlugin(),
@@ -141,11 +177,11 @@ export function createPlugins(server, cwd, {
plugins.push(new ExtractTextPlugin(appStyle))
}

// Move JavaScript imported from node_modules into a vendor bundle
// Move JavaScript imported from node_modules into a vendor chunk
if (vendorJS) {
plugins.push(new optimize.CommonsChunkPlugin({
name: vendorJS,
filename: vendorJS,
filename: `${vendorJS}.js`,
minChunks: function(module, count) {
return (
module.resource &&
@@ -156,11 +192,11 @@ export function createPlugins(server, cwd, {
}))
}

// // Move styles imported from node_modules into a vendor stylesheet
// TODO Move CSS imported from node_modules into a vendor chunk
// if (vendorStyle) {
// plugins.push(new optimize.CommonsChunkPlugin({
// name: vendorStyle,
// filename: vendorStyle,
// filename: `${vendorStyle}.css`,
// minChunks: function(module, count) {
// return (
// module.resource &&
@@ -180,10 +216,18 @@ export function createPlugins(server, cwd, {
}))
}

if (html) {
plugins.push(new HtmlWebpackPlugin(html))
}

if (banner) {
plugins.push(new webpack.BannerPlugin(banner))
}

if (extra) {
plugins = plugins.concat(extra)
}

return plugins
}

@@ -192,15 +236,21 @@ export function createPlugins(server, cwd, {
* creating a static build (default) or serving an app with hot reloading.
*/
export default function createWebpackConfig(cwd, {
loaders = {}, plugins = {}, resolve = {}, server = false, userConfig = {}, ...otherConfig
loaders = {},
plugins = {},
resolve = {},
server = false,
userConfig = {},
...otherConfig
} = {}) {
assert.equal(typeof cwd, 'string')
return {
module: {
loaders: createLoaders(server, loaders, userConfig.loaders)
},
plugins: createPlugins(server, cwd, plugins),
resolve: {
extensions: ['', '.js', '.jsx', '.json'],
extensions: ['', '.web.js', '.js', '.jsx', '.json'],
...resolve
},
...otherConfig
@@ -1,12 +1,20 @@
import express from 'express'
import webpack from 'webpack'

module.exports = function server(webpackConfig, {noInfo, port, staticPath}) {
var app = express()
var compiler = webpack(webpackConfig)
/**
* Start an express server which uses webpack-dev-middleware to build and serve
* assets using Webpack's watch mode, and webpack-hot-middleware to hot reload
* changes in the browser.
*
* If static path config is also provided, express will also be used to serve
* static content from the given path.
*/
export default function server(webpackConfig, {noInfo, port, staticPath}) {
let app = express()
let compiler = webpack(webpackConfig)

app.use(require('webpack-dev-middleware')(compiler, {
noInfo: noInfo,
noInfo,
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true
@@ -15,7 +23,9 @@ module.exports = function server(webpackConfig, {noInfo, port, staticPath}) {

app.use(require('webpack-hot-middleware')(compiler))

app.use(express.static(staticPath))
if (staticPath) {
app.use(express.static(staticPath))
}

app.listen(port, 'localhost', function(err) {
if (err) {
@@ -9,7 +9,7 @@ import glob from 'glob'
*/
function getBinScript(name) {
// Global install or npm@2 local install, dependencies in local node_modules
var paths = glob.sync(`../node_modules/.bin/${name}`, {cwd: __dirname})
let paths = glob.sync(`../node_modules/.bin/${name}`, {cwd: __dirname})
if (paths.length > 0) return path.join(__dirname, paths[0])
// Local npm@3 install, .bin and dependencies are siblings
paths = glob.sync(`../../.bin/${name}`, {cwd: __dirname})
@@ -1,24 +1,33 @@
import path from 'path'

import {MODULE_TYPES} from './constants'
import debug from './debug'

export default function getUserConfig(userConfigFile) {
// Try to load default user config, or user config file we were given
var userConfig = {}
export default function getUserConfig(args) {
// Try to load default user config, or user a config file path we were given
let userConfig = {}
let userConfigPath = path.join(process.cwd(), args.config || 'nwb.config.js')
try {
var userConfigPath = path.join(process.cwd(), userConfigFile || `nwb.config.js`)
userConfig = require(userConfigPath)
debug(`Found user nwb config at ${userConfigPath}`)
debug(`found nwb config file at ${userConfigPath}`)
}
catch (e) {
if (userConfigFile) {
console.error(`specified nwb config file not found: ${userConfigFile}`)
process.exit(1)
}
console.error(`couldn't find nwb config file at ${userConfigPath}`)
process.exit(1)
}

if (typeof userConfig == 'function') {
userConfig = userConfig()
}
debug('User config is ', userConfig)

if (MODULE_TYPES.indexOf(userConfig.type) === -1) {
console.error(`invalid module type configured in ${userConfigPath}: ${userConfig.type}`)
console.error(`'type' must be one of: ${MODULE_TYPES.join(', ')}`)
process.exit(1)
}

debug('User config loaded')
debug(userConfig)

return userConfig
}
@@ -0,0 +1,65 @@
import assert from 'assert'

import resolve from 'resolve'

import webpackServer from './webpackServer'

/**
* Start a development server with webpack dev and hot loading middelware,
* configuring babel-loader with react-transform plugins for hot loading and
* error catching.
*/
export default function(args, config) {
let {
entry,
output,
plugins,
staticPath = null
} = config

assert(entry, 'entry config is required to serve a React app')
assert(output, 'output config is required to serve a React app')

// Find the locally-installed version of React
let reactPath
try {
reactPath = resolve.sync('react', {basedir: process.cwd()})
}
catch (e) {
console.error('React must be installed locally to serve a React app')
process.exit(1)
}

// Start the development server
webpackServer(args, {
entry,
output,
loaders: {
babel: {
// Configure hot reloading and error catching via react-transform
query: {
plugins: [
require.resolve('babel-plugin-react-display-name'),
require.resolve('babel-plugin-react-transform')
],
extra: {
'react-transform': {
transforms: [{
transform: require.resolve('react-transform-hmr'),
imports: [reactPath],
locals: ['module']
}, {
transform: require.resolve('react-transform-catch-errors'),
imports: [reactPath, require.resolve('redbox-noreact')]
}]
}
}
}
}
},
plugins,
server: {
staticPath
}
})
}

This file was deleted.

@@ -1,46 +1,42 @@
// Set cross-platform environment variables based on --set-env-NAME arguments
require('argv-set-env')()

// Default environment settings
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'production'
}

import path from 'path'
import assert from 'assert'

import argvSetEnv from 'argv-set-env'
import webpack from 'webpack'

import createWebpackConfig from '../createWebpackConfig'
import getUserConfig from '../getUserConfig'

export default function(args, buildConfig = {}) {
export default function(args, buildConfig = {}, cb) {
// Set cross-platform environment variables based on --set-env-NAME arguments
argvSetEnv()
// Default environment setting for a build
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'production'
}

let userConfig = getUserConfig(args.config)
if (typeof buildConfig == 'function') {
buildConfig = buildConfig()
}

let cwd = process.cwd()
assert(buildConfig.entry, 'entry config is required to create a Webpack build')
assert(buildConfig.output, 'output config is required to create a Webpack build')

var webpackConfig = createWebpackConfig(cwd, {
let webpackConfig = createWebpackConfig(process.cwd(), {
server: false,
devtool: 'source-map',
entry: path.resolve('src/index.js'),
output: {
filename: 'app.js',
path: path.resolve('public/build'),
publicPath: 'build/'
},
entry: buildConfig.entry,
output: buildConfig.output,
loaders: buildConfig.loaders,
plugins: {
define: {...buildConfig.define, ...userConfig.define},
appStyle: 'style.css',
vendorJS: 'vendor.js'
vendorJS: 'vendor',
...buildConfig.plugins
}
}, userConfig.webpack)
}, userConfig)

let compiler = webpack(webpackConfig)

console.log('nwb: build-app')
compiler.run((err, stats) => {
if (err) {
console.error('webpack build error:')
@@ -53,5 +49,8 @@ export default function(args, buildConfig = {}) {
colors: true,
modules: false
}))
if (cb) {
cb()
}
})
}
@@ -0,0 +1,48 @@
import assert from 'assert'

import devServer from './devServer'
import getUserConfig from './getUserConfig'
import createWebpackConfig from './createWebpackConfig'

/**
* Start a development server with webpack using a given build configuration.
*/
export default function(args, buildConfig) {
// Force environment variable to development
process.env.NODE_ENV = 'development'

let userConfig = getUserConfig(args)
if (typeof buildConfig == 'function') {
buildConfig = buildConfig()
}

let {
define, entry, output, loaders = {}, plugins, server = {staticPath: null}
} = buildConfig

assert(entry, 'an entry file is required to serve a Webpack build')
assert(output, 'output config is required to serve a Webpack build')

let webpackConfig = createWebpackConfig(process.cwd(), {
server: true,
devtool: 'eval-source-map',
entry: [
// Polyfill EventSource for IE, as webpack-hot-middleware/client uses it
require.resolve('eventsource-polyfill'),
require.resolve('webpack-hot-middleware/client'),
entry
],
output,
loaders,
plugins: {
define: {...define, ...userConfig.define},
...plugins
}
}, userConfig)

devServer(webpackConfig, {
noInfo: !args.info,
port: args.port || 3000,
staticPath: server.staticPath
})
}

This file was deleted.

@@ -7,14 +7,14 @@ This README outlines the details of collaborating on this React application. A s
You will need the following things properly installed on your computer.

* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/) (with NPM)
* [Node.js](http://nodejs.org/) (with npm)
* [nwb](https://github.com/insin/nwb/) - `npm install -g nwb`

## Installation

* `git clone <repository-url>` this repository
* change into the new directory
* `npm install --production`
* `npm install
## Running / Development
@@ -1,5 +1,6 @@
module.exports = function() {
return {
// Let nwb know this is a React app when generic build commands are used
type: 'react-app'
}
}
@@ -2,10 +2,11 @@
"name": "{{name}}",
"version": "0.0.1",
"description": "Describe {{name}} here",
"private": true,
"scripts": {
"build": "nwb build-react-app",
"build": "nwb build",
"clean": "nwb clean",
"start": "nwb serve-react-app",
"start": "nwb serve",
"test": "nwb test"
},
"dependencies": {
@@ -2,6 +2,8 @@ import React from 'react'

export default React.createClass({
render() {
return <div>Welcome to {{name}}!</div>
return <div>
<h2>Welcome to React</h2>
</div>
}
})
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,13 @@
import React from 'react'
import {render} from 'react-dom'

import MyComponent from '../../src'

let Demo = React.createClass({
render() {
return <div>
<h1>{{name}} Demo</h1>
<MyComponent/>
</div>
}
})
@@ -0,0 +1,16 @@
module.exports = {
// Let nwb know this is a React component module when generic build commands
// are used.
type: 'react component',

// The name of the global variable the UMD build of this React component
// module will export.
global: '{{globalVariable}}',

// A mapping from the npm package names of this React component's
// peerDependencies to the global variables they're expected to be available
// as for use by the UMD build.
externals: {
'react': 'React'
}
}
@@ -0,0 +1,27 @@
{
"name": "{{name}}",
"version": "0.0.1",
"description": "{{name}} React component",
"scripts": {
"build": "nwb build",
"start": "nwb serve",
"test": "nwb test"
},
"dependencies": {
},
"peerDependencies": {
"react": "{{reactVersion}}",
},
"devDependencies": {
"nwb": "^{{nwbVersion}}",
"react": "{{reactVersion}}",
"react-dom": "{{reactVersion}}"
},
"author": "",
"homepage": "",
"license": "MIT",
"repository": "",
"keywords": [
"react-component"
]
}
@@ -0,0 +1,9 @@
import React from 'react'

export default React.createClass({
render() {
return <div>
<h2>Welcome to React components</h2>
</div>
}
})
File renamed without changes.
File renamed without changes.
Empty file.
@@ -0,0 +1,6 @@
/coverage
/demo/dist
/lib
/node_modules
/umd
npm-debug.log
@@ -0,0 +1,16 @@
sudo: false

language: node_js
node_js:
- 4.2

cache:
directories:
- node_modules

after_success:
- cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js

branches:
only:
- master
File renamed without changes.
@@ -0,0 +1,14 @@
module.exports = {
// Let nwb know this is a web module when generic build commands are used
type: 'web-module',

// The name of the global variable the UMD build of this web module will
// export.
global: '{{globalVariable}}',

// A mapping from the npm package names of this web module's peerDependencies
// - if it has any - to the global variables they're expected to be available
// as for use by the UMD build.
// e.g. {'react': 'React'}
externals: {}
}
@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "Describe {{name}} here",
"scripts": {
"build": "nwb build-module",
"build": "nwb build",
"clean": "nwb clean",
"test": "nwb test"
},
@@ -13,6 +13,7 @@
"nwb": "^{{nwbVersion}}"
},
"author": "",
"homepage": "",
"license": "MIT",
"repository": ""
}
File renamed without changes.
@@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}
@@ -0,0 +1,9 @@
import expect from 'expect'

import message from 'src/index'

describe('Module template', () => {
it('displays a welcome message', () => {
expect(message).toEqual('Hello world')
})
})

This file was deleted.