Merge branch 'react-pr' of https://github.com/netlify/netlify-cms into react-pr

This commit is contained in:
Cássio Zen 2016-09-19 15:31:12 -03:00
commit 98972ae21e
20 changed files with 355 additions and 175 deletions

View File

@ -2,7 +2,6 @@ import { configure } from '@kadira/storybook';
import '../src/index.css'; import '../src/index.css';
function loadStories() { function loadStories() {
require('../src/containers/stories/');
require('../src/components/stories/'); require('../src/components/stories/');
} }

View File

@ -1,34 +1 @@
/* global module, __dirname, require */ module.exports = require('../webpack.base.js');
var webpack = require("webpack");
const path = require("path");
module.exports = {
module: {
loaders: [
{
test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader?limit=100000'
},
{ test: /\.json$/, loader: 'json-loader' },
{
test: /\.css$/,
loader: 'style!css?modules&importLoaders=1!postcss'
},
{
loader: 'babel',
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
query: {
cacheDirectory: true,
presets: ['react', 'es2015'],
plugins: ['transform-class-properties', 'transform-object-assign', 'transform-object-rest-spread']
}
}
]
},
postcss: [
require("postcss-import")({addDependencyTo: webpack}),
require("postcss-cssnext")()
],
};

View File

@ -4,7 +4,7 @@
"description": "Netlify CMS lets content editors work on structured content stored in git", "description": "Netlify CMS lets content editors work on structured content stored in git",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "webpack-dev-server --config webpack.config.js", "start": "webpack-dev-server --config webpack.dev.js",
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require ./test/setup.js", "test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require ./test/setup.js",
"test:watch": "npm test -- --watch", "test:watch": "npm test -- --watch",
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
@ -53,6 +53,7 @@
"lint-staged": "^3.0.2", "lint-staged": "^3.0.2",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"moment": "^2.11.2", "moment": "^2.11.2",
"node-sass": "^3.10.0",
"normalizr": "^2.0.0", "normalizr": "^2.0.0",
"postcss-cssnext": "^2.7.0", "postcss-cssnext": "^2.7.0",
"postcss-import": "^8.1.2", "postcss-import": "^8.1.2",
@ -69,10 +70,12 @@
"react-router-redux": "^4.0.5", "react-router-redux": "^4.0.5",
"redux": "^3.3.1", "redux": "^3.3.1",
"redux-thunk": "^1.0.3", "redux-thunk": "^1.0.3",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.13.2", "webpack": "^1.13.2",
"webpack-dev-server": "^1.15.1", "webpack-dev-server": "^1.15.1",
"webpack-merge": "^0.14.1",
"webpack-postcss-tools": "^1.1.1", "webpack-postcss-tools": "^1.1.1",
"whatwg-fetch": "^1.0.0" "whatwg-fetch": "^1.0.0"
}, },
@ -80,16 +83,20 @@
"bricks.js": "^1.7.0", "bricks.js": "^1.7.0",
"dateformat": "^1.0.12", "dateformat": "^1.0.12",
"fuzzy": "^0.1.1", "fuzzy": "^0.1.1",
"immutability-helper": "^2.0.0",
"js-base64": "^2.1.9", "js-base64": "^2.1.9",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"localforage": "^1.4.2", "localforage": "^1.4.2",
"lodash": "^4.13.1", "lodash": "^4.13.1",
"markup-it": "git+https://github.com/cassiozen/markup-it.git", "markup-it": "git+https://github.com/cassiozen/markup-it.git",
"material-design-icons": "^3.0.1",
"normalize.css": "^4.2.0",
"pluralize": "^3.0.0", "pluralize": "^3.0.0",
"prismjs": "^1.5.1", "prismjs": "^1.5.1",
"react-addons-css-transition-group": "^15.3.1", "react-addons-css-transition-group": "^15.3.1",
"react-datetime": "^2.6.0", "react-datetime": "^2.6.0",
"react-portal": "^2.2.1", "react-portal": "^2.2.1",
"react-toolbox": "^1.2.1",
"react-simple-dnd": "^0.1.2", "react-simple-dnd": "^0.1.2",
"selection-position": "^1.0.0", "selection-position": "^1.0.0",
"semaphore": "^1.0.5", "semaphore": "^1.0.5",

View File

@ -1,5 +1,5 @@
import history from '../routing/history'; import history from '../routing/history';
import { SEARCH } from '../containers/FindBar'; import { SEARCH } from '../components/UI/FindBar/FindBar';
export const RUN_COMMAND = 'RUN_COMMAND'; export const RUN_COMMAND = 'RUN_COMMAND';
export const SHOW_COLLECTION = 'SHOW_COLLECTION'; export const SHOW_COLLECTION = 'SHOW_COLLECTION';
@ -10,14 +10,22 @@ export function run(commandName, payload) {
return { type: RUN_COMMAND, command: commandName, payload }; return { type: RUN_COMMAND, command: commandName, payload };
} }
export function navigateToCollection(collectionName) {
return runCommand(SHOW_COLLECTION, { collectionName });
}
export function createNewEntryInCollection(collectionName) {
return runCommand(CREATE_COLLECTION, { collectionName });
}
export function runCommand(commandName, payload) { export function runCommand(commandName, payload) {
return (dispatch, getState) => { return dispatch => {
switch (commandName) { switch (commandName) {
case SHOW_COLLECTION: case SHOW_COLLECTION:
history.push(`/collections/${payload.collectionName}`); history.push(`/collections/${payload.collectionName}`);
break; break;
case CREATE_COLLECTION: case CREATE_COLLECTION:
window.alert(`Create a new ${payload.collectionName} - not supported yet`); history.push(`/collections/${payload.collectionName}/entries/new`);
break; break;
case HELP: case HELP:
window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.'); window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');

View File

@ -0,0 +1,17 @@
:root {
--foregroundColor: #fff;
--backgroundColor: #272e30;
--textFieldBorderColor: #e7e7e7;
--highlightFGColor: #fff;
--highlightBGColor: #3ab7a5;
}
.appBar {
background-color: var(--backgroundColor);
}
.createBtn {
position: fixed;
right: 2rem;
top: 3.5rem;
}

View File

@ -0,0 +1,90 @@
import React from 'react';
import pluralize from 'pluralize';
import { IndexLink } from 'react-router';
import { Menu, MenuItem, Button, IconButton } from 'react-toolbox';
import AppBar from 'react-toolbox/lib/app_bar';
import FindBar from '../FindBar/FindBar';
import styles from './AppHeader.css';
export default class AppHeader extends React.Component {
state = {
createMenuActive: false
}
handleCreatePostClick = collectionName => {
const { onCreateEntryClick } = this.props;
if (onCreateEntryClick) {
onCreateEntryClick(collectionName);
}
}
handleCreateButtonClick = () => {
this.setState({
createMenuActive: true
});
}
handleCreateMenuHide = () => {
this.setState({
createMenuActive: false
});
}
render() {
const {
collections,
commands,
defaultCommands,
runCommand,
toggleNavDrawer
} = this.props;
const { createMenuActive } = this.state;
return (
<AppBar
fixed
theme={styles}
>
<IconButton
icon="menu"
inverse
onClick={toggleNavDrawer}
/>
<IndexLink to="/">
Dashboard
</IndexLink>
<FindBar
commands={commands}
defaultCommands={defaultCommands}
runCommand={runCommand}
/>
<Button
className={styles.createBtn}
icon='add'
floating
accent
onClick={this.handleCreateButtonClick}
>
<Menu
active={createMenuActive}
position="topRight"
onHide={this.handleCreateMenuHide}
>
{
collections.valueSeq().map(collection =>
<MenuItem
key={collection.get('name')}
value={collection.get('name')}
onClick={this.handleCreatePostClick.bind(this, collection.get('name'))}
caption={pluralize(collection.get('label'), 1)}
/>
)
}
</Menu>
</Button>
</AppBar>
);
}
}

View File

@ -7,15 +7,14 @@
} }
.root { .root {
flex: 1;
position: relative; position: relative;
background-color: var(--backgroundColor); background-color: var(--backgroundColor);
padding: 1px 0; padding: 5px;
margin: 4px auto;
} }
.inputArea { .inputArea {
display: table; display: table;
width: calc(100% - 10px); width: 100%;
margin: 5px;
color: var(--foregroundColor); color: var(--foregroundColor);
background-color: #fff; background-color: #fff;
border: 1px solid var(--textFieldBorderColor); border: 1px solid var(--textFieldBorderColor);

View File

@ -1,9 +1,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import fuzzy from 'fuzzy'; import fuzzy from 'fuzzy';
import _ from 'lodash'; import _ from 'lodash';
import { runCommand } from '../actions/findbar'; import { Icon } from '../index';
import { connect } from 'react-redux';
import { Icon } from '../components/UI';
import styles from './FindBar.css'; import styles from './FindBar.css';
export const SEARCH = 'SEARCH'; export const SEARCH = 'SEARCH';
@ -102,13 +100,15 @@ class FindBar extends Component {
const paramName = command && command.param ? command.param.name : null; const paramName = command && command.param ? command.param.name : null;
const enteredParamValue = command && command.param && match[1] ? match[1].trim() : null; const enteredParamValue = command && command.param && match[1] ? match[1].trim() : null;
console.log(this.props.runCommand);
if (command.search) { if (command.search) {
this.setState({ this.setState({
activeScope: SEARCH, activeScope: SEARCH,
placeholder: '' placeholder: ''
}); });
enteredParamValue && this.props.dispatch(runCommand(SEARCH, { searchTerm: enteredParamValue })); enteredParamValue && this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
} else if (command.param && !enteredParamValue) { } else if (command.param && !enteredParamValue) {
// Partial Match // Partial Match
// Command was partially matched: It requires a param, but param wasn't entered // Command was partially matched: It requires a param, but param wasn't entered
@ -133,7 +133,7 @@ class FindBar extends Component {
if (paramName) { if (paramName) {
payload[paramName] = enteredParamValue; payload[paramName] = enteredParamValue;
} }
this.props.dispatch(runCommand(command.type, payload)); this.props.runCommand(command.type, payload);
} }
} }
@ -373,8 +373,7 @@ FindBar.propTypes = {
pattern: PropTypes.string.isRequired pattern: PropTypes.string.isRequired
})).isRequired, })).isRequired,
defaultCommands: PropTypes.arrayOf(PropTypes.string), defaultCommands: PropTypes.arrayOf(PropTypes.string),
dispatch: PropTypes.func.isRequired, runCommand: PropTypes.func.isRequired,
}; };
export { FindBar }; export default FindBar;
export default connect()(FindBar);

View File

@ -2,3 +2,4 @@ export { default as Card } from './card/Card';
export { default as Loader } from './loader/Loader'; export { default as Loader } from './loader/Loader';
export { default as Icon } from './icon/Icon'; export { default as Icon } from './icon/Icon';
export { default as Toast } from './toast/Toast'; export { default as Toast } from './toast/Toast';
export { default as AppHeader } from './AppHeader/AppHeader';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { storiesOf, action } from '@kadira/storybook'; import { storiesOf, action } from '@kadira/storybook';
import { FindBar } from '../FindBar'; import FindBar from '../UI/FindBar/FindBar';
const CREATE_COLLECTION = 'CREATE_COLLECTION'; const CREATE_COLLECTION = 'CREATE_COLLECTION';
const CREATE_POST = 'CREATE_POST'; const CREATE_POST = 'CREATE_POST';
@ -30,15 +30,13 @@ const style = {
margin: 20 margin: 20
}; };
const dispatch = action('DISPATCH');
storiesOf('FindBar', module) storiesOf('FindBar', module)
.add('Default View', () => ( .add('Default View', () => (
<div style={style}> <div style={style}>
<FindBar <FindBar
commands={commands} commands={commands}
defaultCommands={[CREATE_POST, CREATE_COLLECTION, OPEN_SETTINGS, HELP, MORE_COMMANDS]} defaultCommands={[CREATE_POST, CREATE_COLLECTION, OPEN_SETTINGS, HELP, MORE_COMMANDS]}
dispatch={f => f(dispatch)} runCommand={action}
/> />
</div> </div>
)); ));

View File

@ -1,3 +1,4 @@
import './Card'; import './Card';
import './Icon'; import './Icon';
import './Toast'; import './Toast';
import './FindBar';

View File

@ -1,3 +1,10 @@
.layout .navDrawer .drawerContent {
padding-top: 54px;
}
.nav {
display: block;
padding: 1rem;
}
.main { .main {
padding-top: 54px; padding-top: 54px;
} }

View File

@ -1,15 +1,27 @@
import React from 'react'; import React from 'react';
import pluralize from 'pluralize';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Layout, Panel, NavDrawer, Navigation, Link } from 'react-toolbox';
import { loadConfig } from '../actions/config'; import { loadConfig } from '../actions/config';
import { loginUser } from '../actions/auth'; import { loginUser } from '../actions/auth';
import { currentBackend } from '../backends/backend'; import { currentBackend } from '../backends/backend';
import { Loader } from '../components/UI'; import {
import { SHOW_COLLECTION, CREATE_COLLECTION, HELP } from '../actions/findbar'; SHOW_COLLECTION,
import FindBar from './FindBar'; CREATE_COLLECTION,
HELP,
runCommand,
navigateToCollection,
createNewEntryInCollection
} from '../actions/findbar';
import { AppHeader, Loader } from '../components/UI/index';
import styles from './App.css'; import styles from './App.css';
import pluralize from 'pluralize';
class App extends React.Component { class App extends React.Component {
state = {
navDrawerIsVisible: false
}
componentDidMount() { componentDidMount() {
this.props.dispatch(loadConfig()); this.props.dispatch(loadConfig());
} }
@ -62,7 +74,7 @@ class App extends React.Component {
id: `show_${collection.get('name')}`, id: `show_${collection.get('name')}`,
pattern: `Show ${pluralize(collection.get('label'))}`, pattern: `Show ${pluralize(collection.get('label'))}`,
type: SHOW_COLLECTION, type: SHOW_COLLECTION,
payload: { collectionName:collection.get('name') } payload: { collectionName: collection.get('name') }
}); });
if (defaultCommands.length < 5) defaultCommands.push(`show_${collection.get('name')}`); if (defaultCommands.length < 5) defaultCommands.push(`show_${collection.get('name')}`);
@ -72,7 +84,7 @@ class App extends React.Component {
id: `create_${collection.get('name')}`, id: `create_${collection.get('name')}`,
pattern: `Create new ${pluralize(collection.get('label'), 1)}(:itemName as ${pluralize(collection.get('label'), 1)} Name)`, pattern: `Create new ${pluralize(collection.get('label'), 1)}(:itemName as ${pluralize(collection.get('label'), 1)} Name)`,
type: CREATE_COLLECTION, type: CREATE_COLLECTION,
payload: { collectionName:collection.get('name') } payload: { collectionName: collection.get('name') }
}); });
} }
}); });
@ -83,8 +95,23 @@ class App extends React.Component {
return { commands, defaultCommands }; return { commands, defaultCommands };
} }
toggleNavDrawer = () => {
this.setState({
navDrawerIsVisible: !this.state.navDrawerIsVisible
});
}
render() { render() {
const { user, config, children } = this.props; const { navDrawerIsVisible } = this.state;
const {
user,
config,
children,
collections,
runCommand,
navigateToCollection,
createNewEntryInCollection
} = this.props;
if (config === null) { if (config === null) {
return null; return null;
@ -105,19 +132,42 @@ class App extends React.Component {
const { commands, defaultCommands } = this.generateFindBarCommands(); const { commands, defaultCommands } = this.generateFindBarCommands();
return ( return (
<div> <Layout theme={styles}>
<header> <NavDrawer
<div className={styles.alignable}> active={navDrawerIsVisible}
<FindBar scrollY
commands={commands} permanentAt="md"
defaultCommands={defaultCommands} >
/> <nav className={styles.nav}>
<h1>Collections</h1>
<Navigation type='vertical'>
{
collections.valueSeq().map(collection =>
<Link
key={collection.get('name')}
onClick={navigateToCollection.bind(this, collection.get('name'))}
>
{collection.get('label')}
</Link>
)
}
</Navigation>
</nav>
</NavDrawer>
<Panel scrollY>
<AppHeader
collections={collections}
commands={commands}
defaultCommands={defaultCommands}
runCommand={runCommand}
onCreateEntryClick={createNewEntryInCollection}
toggleNavDrawer={this.toggleNavDrawer}
/>
<div className={`${styles.alignable} ${styles.main}`}>
{children}
</div> </div>
</header> </Panel>
<div className={`${styles.alignable} ${styles.main}`}> </Layout>
{children}
</div>
</div>
); );
} }
} }
@ -129,4 +179,19 @@ function mapStateToProps(state) {
return { auth, config, collections, user }; return { auth, config, collections, user };
} }
export default connect(mapStateToProps)(App); function mapDispatchToProps(dispatch) {
return {
dispatch,
runCommand: (type, payload) => {
dispatch(runCommand(type, payload));
},
navigateToCollection: (collection) => {
dispatch(navigateToCollection(collection));
},
createNewEntryInCollection: (collectionName) => {
dispatch(createNewEntryInCollection(collectionName));
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@ -1 +0,0 @@
import './FindBar';

View File

@ -1,3 +1,5 @@
@import "material-icons.css";
html { html {
box-sizing: border-box; box-sizing: border-box;
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;
@ -18,16 +20,6 @@ body {
margin: 0; margin: 0;
} }
header {
background-color: #272e30;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22);
height: 54px;
border-bottom: 2px solid #3ab7a5;
position: fixed;
width: 100%;
z-index: 999;
}
:global #root, :global #root > * { :global #root, :global #root > * {
height: 100%; height: 100%;
} }
@ -44,19 +36,6 @@ h1 {
font-size: 25px; font-size: 25px;
} }
header input {
margin-bottom: 0;
}
button {
border: 1px solid #3ab7a5;
padding: 3px 20px;
font-size: 12px;
line-height: 18px;
background-color: #fff;
cursor: pointer;
}
:global { :global {
& .cms-widget { & .cms-widget {
border-bottom: 1px solid #e8eae8; border-bottom: 1px solid #e8eae8;

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader' import { AppContainer } from 'react-hot-loader';
import Root from './root' import Root from './root';
import registry from './lib/registry'; import registry from './lib/registry';
import 'file?name=index.html!../example/index.html'; import 'file?name=index.html!../example/index.html';
import 'react-toolbox/lib/commons.scss';
import './index.css'; import './index.css';
// Create mount element dynamically // Create mount element dynamically

35
src/material-icons.css Normal file
View File

@ -0,0 +1,35 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url('material-design-icons/iconfont/MaterialIcons-Regular.woff2') format('woff2'),
url('material-design-icons/iconfont/MaterialIcons-Regular.woff') format('woff'),
url('material-design-icons/iconfont/MaterialIcons-Regular.ttf') format('truetype');
}
:global .material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}

45
webpack.base.js Normal file
View File

@ -0,0 +1,45 @@
const webpack = require('webpack');
module.exports = {
module: {
loaders: [
{
test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader?limit=100000'
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.scss$/,
loader: 'style!css?modules!sass',
},
{
test: /\.css$/,
loader: 'style!css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss',
},
{
loader: 'babel',
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
query: {
cacheDirectory: true,
presets: ['react', 'es2015'],
plugins: [
'transform-class-properties',
'transform-object-assign',
'transform-object-rest-spread',
'lodash',
'react-hot-loader/babel'
]
}
}
]
},
postcss: [
require('postcss-import')({ addDependencyTo: webpack }),
require('postcss-cssnext')
],
};

View File

@ -1,74 +0,0 @@
/* global module, __dirname, require */
var webpack = require('webpack');
var path = require('path');
const HOST = 'localhost';
const PORT = '8080';
module.exports = {
module: {
loaders: [
{
test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader?limit=100000'
},
{ test: /\.json$/, loader: 'json-loader' },
{
test: /\.css$/,
loader: 'style!css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss',
},
{
loader: 'babel',
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
query: {
cacheDirectory: true,
presets: ['react', 'es2015'],
plugins: [
'transform-class-properties',
'transform-object-assign',
'transform-object-rest-spread',
'lodash',
'react-hot-loader/babel'
]
}
}
]
},
postcss: [
require('postcss-import')({ addDependencyTo: webpack }),
require('postcss-cssnext')
],
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.ProvidePlugin({
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
})
],
context: path.join(__dirname, 'src'),
entry: {
cms: [
'webpack/hot/dev-server',
`webpack-dev-server/client?http://${HOST}:${PORT}/`,
'react-hot-loader/patch',
'./index'
],
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: `http://${HOST}:${PORT}/`,
},
externals: [/^vendor\/.+\.js$/],
devServer: {
hot: true,
contentBase: 'example/',
historyApiFallback: true,
devTool: 'cheap-module-source-map'
},
};

37
webpack.dev.js Normal file
View File

@ -0,0 +1,37 @@
/* global module, __dirname, require */
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const HOST = 'localhost';
const PORT = '8080';
module.exports = merge.smart(require('./webpack.base.js'), {
entry: {
cms: [
'webpack/hot/dev-server',
`webpack-dev-server/client?http://${HOST}:${PORT}/`,
'react-hot-loader/patch',
'./index'
],
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: `http://${HOST}:${PORT}/`,
},
context: path.join(__dirname, 'src'),
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.ProvidePlugin({
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
})
],
devServer: {
hot: true,
contentBase: 'example/',
historyApiFallback: true,
devTool: 'cheap-module-source-map'
},
});