Hello World

This commit is contained in:
SleepWalker 2016-08-28 16:25:56 +03:00
parent 7e8e198763
commit 7e618b23df
15 changed files with 838 additions and 1 deletions

9
.babelrc Normal file
View File

@ -0,0 +1,9 @@
{
"presets": ["react", "es2015", "stage-0"],
"plugins": ["transform-runtime", ["react-intl", {"messagesDir": "./dist/messages/"}]],
"env": {
"development": {
"presets": ["react-hmre"]
}
}
}

213
.eslintrc.json Normal file
View File

@ -0,0 +1,213 @@
{
"parser": "babel-eslint",
"plugins": [
"react"
],
"ecmaFeatures": {
"jsx": true,
"modules": true,
"classes": true,
"defaultParams": true,
"destructuring": true,
"spread": true,
"arrowFunctions": true,
"blockBindings": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"restParams": true,
"superInFunctions": true,
"templateStrings": true,
"experimentalObjectRestSpread": true
},
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
// @see: http://eslint.org/docs/rules/
"rules": {
// possible errors (including eslint:recommended)
"valid-jsdoc": ["warn", {
"requireParamDescription": false,
"requireReturn": false,
"requireReturnDescription": false,
"prefer": {
"returns": "return"
},
"preferType": {
"String": "string",
"Object": "object",
"Number": "number",
"Function": "function"
}
}],
// best practice
"block-scoped-var": "error",
"curly": "error",
"default-case": "error",
"dot-location": ["error", "property"],
"dot-notation": "error",
"eqeqeq": ["error", "smart"],
"no-alert": "error",
"no-caller": "error",
"no-case-declarations": "error",
"no-div-regex": "error",
"no-else-return": "error",
"no-empty-pattern": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "warn",
"no-fallthrough": "error",
"no-floating-decimal": "warn",
"no-implied-eval": "error",
"no-invalid-this": "off",
"no-labels": "error",
"no-lone-blocks": "warn",
"no-loop-func": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-native-reassign": "error",
"no-new-wrappers": "warn",
"no-new": "warn",
"no-octal-escape": "warn",
"no-octal": "error",
"no-proto": "error",
"no-redeclare": "warn",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unused-expressions": ["warn", {"allowShortCircuit": true, "allowTernary": true}],
"no-useless-call": "warn",
"no-useless-concat": "warn",
"no-void": "error",
"no-with": "error",
"radix": "error",
"wrap-iife": "error",
"yoda": "warn",
"no-constant-condition": "error",
// strict mode
"strict": ["warn", "never"], // babel все сделает за нас
// variables
"no-catch-shadow": "error",
"no-delete-var": "error",
"no-label-var": "error",
"no-shadow-restricted-names": "error",
"no-shadow": "off",
"no-undef-init": "error",
"no-undef": "error",
"no-use-before-define": ["warn", "nofunc"],
// CommonJS
"no-mixed-requires": "warn",
"no-path-concat": "warn",
// stylistic
"array-bracket-spacing": "error",
"block-spacing": ["error", "never"],
"brace-style": ["error", "1tbs", {"allowSingleLine": true}],
"comma-spacing": "error",
"comma-style": "error",
"comma-dangle": ["warn", "only-multiline"],
"computed-property-spacing": "error",
"consistent-this": ["error", "that"],
"camelcase": "warn",
"eol-last": "warn",
"id-length": ["error", {"min": 2, "exceptions": ["x", "y", "i", "$"]}],
"indent": ["error", 4, {"SwitchCase": 1}],
"jsx-quotes": "error",
"key-spacing": ["error", {"mode": "minimum"}],
"linebreak-style": "error",
"max-depth": "error",
"new-cap": "error",
"new-parens": "error",
"no-array-constructor": "warn",
"no-bitwise": "warn",
"no-lonely-if": "error",
"no-negated-condition": "warn",
"no-nested-ternary": "error",
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "warn",
"no-unneeded-ternary": "warn",
"one-var": ["error", "never"],
"operator-assignment": ["warn", "always"],
"operator-linebreak": ["error", "before"],
"padded-blocks": ["warn", "never"],
"quote-props": ["warn", "as-needed"],
"quotes": ["warn", "single"],
"semi": "error",
"semi-spacing": "error",
"keyword-spacing": "warn",
"space-before-blocks": "error",
"space-before-function-paren": ["error", "never"],
"space-in-parens": "warn",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "warn",
// es6
"arrow-body-style": "warn",
"arrow-parens": "error",
"arrow-spacing": "error",
"constructor-super": "error",
"generator-star-spacing": "warn",
"no-class-assign": "error",
"no-const-assign": "error",
"no-dupe-class-members": "error",
"no-this-before-super": "error",
"no-var": "warn",
"object-shorthand": "warn",
"prefer-arrow-callback": "warn",
"prefer-const": "warn",
"prefer-reflect": "warn",
"prefer-spread": "warn",
"prefer-template": "warn",
"require-yield": "error",
// react
"react/display-name": "warn",
"react/forbid-prop-types": "warn",
"react/jsx-boolean-value": "warn",
"react/jsx-closing-bracket-location": "warn",
"react/jsx-curly-spacing": "warn",
"react/jsx-handler-names": ["warn", {"eventHandlerPrefix": "on", "eventHandlerPropPrefix": "on"}],
"react/jsx-indent-props": "warn",
"react/jsx-key": "warn",
"react/jsx-max-props-per-line": ["warn", {"maximum": 3}],
"react/jsx-no-bind": "warn",
"react/jsx-no-duplicate-props": "warn",
"react/jsx-no-literals": "warn",
"react/jsx-no-undef": "warn",
"react/jsx-pascal-case": "warn",
"react/jsx-uses-react": "warn",
"react/jsx-uses-vars": "warn",
"react/jsx-no-comment-textnodes": "warn",
"react/jsx-wrap-multilines": "warn",
"react/no-deprecated": "warn",
"react/no-did-mount-set-state": "warn",
"react/no-did-update-set-state": "warn",
"react/no-direct-mutation-state": "warn",
"react/require-render-return": "warn",
"react/no-is-mounted": "warn",
"react/no-multi-comp": "warn",
"react/no-string-refs": "warn",
"react/no-unknown-property": "warn",
"react/prefer-es6-class": "warn",
"react/prop-types": "warn",
"react/react-in-jsx-scope": "warn",
"react/self-closing-comp": "warn",
"react/sort-comp": ["warn", {"order": ["lifecycle", "render", "everything-else"]}]
}
}

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

View File

@ -1 +1 @@
init # Hello World

74
package.json Normal file
View File

@ -0,0 +1,74 @@
{
"name": "email-renderer",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/elyby/email-renderer.git"
},
"keywords": [],
"author": "SleepWalker <dev@udf.su>",
"license": "private",
"bugs": {
"url": "https://gitlab.com/elyby/email-renderer/issues"
},
"homepage": "https://gitlab.com/elyby/email-renderer#README",
"scripts": {
"start": "rm -rf dist/ && webpack-dev-server --progress --colors",
"up": "npm update",
"test": "karma start ./karma.conf.js",
"lint": "eslint ./src",
"i18n": "cd ./scripts && ./node_modules/.bin/babel-node i18n-collect.js",
"build": "rm -rf dist/ && webpack --progress --colors -p",
"render": ""
},
"dependencies": {
"babel-polyfill": "^6.3.14",
"classnames": "^2.1.3",
"history": "^2.0.0",
"intl": "^1.2.2",
"intl-format-cache": "^2.0.4",
"intl-messageformat": "^1.1.0",
"react": "^15.0.0",
"react-dom": "^15.0.0",
"react-intl": "^2.0.0"
},
"devDependencies": {
"babel-core": "^6.0.0",
"babel-eslint": "^6.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-react-intl": "^2.0.0",
"babel-plugin-transform-runtime": "^6.3.13",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-react-hmre": "^1.0.1",
"babel-preset-stage-0": "^6.3.13",
"babel-runtime": "^6.0.0",
"bundle-loader": "^0.5.4",
"css-loader": "^0.23.0",
"eslint": "^3.1.1",
"eslint-plugin-react": "^6.0.0",
"exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.9.0",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.0.0",
"imports-loader": "^0.6.5",
"json-loader": "^0.5.4",
"loader-utils": "^0.2.15",
"node-sass": "^3.4.2",
"postcss-import": "^8.1.2",
"postcss-loader": "^0.9.0",
"postcss-scss": "^0.1.8",
"postcss-url": "SleepWalker/postcss-url#switch-to-async-api",
"raw-loader": "^0.5.1",
"sass-loader": "^4.0.0",
"scripts": "file:scripts",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0",
"webpack-utils": "file:webpack-utils"
}
}

3
scripts/.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"breakConfig": true
}

183
scripts/i18n-collect.js Normal file
View File

@ -0,0 +1,183 @@
/* eslint-disable no-console */
import fs from 'fs';
import {sync as globSync} from 'glob';
import {sync as mkdirpSync} from 'mkdirp';
import chalk from 'chalk';
import prompt from 'prompt';
const MESSAGES_PATTERN = '../dist/messages/**/*.json';
const LANG_DIR = '../src/i18n';
const DEFAULT_LOCALE = 'en';
const SUPPORTED_LANGS = [DEFAULT_LOCALE].concat('ru', 'be', 'uk');
/**
* Aggregates the default messages that were extracted from the app's
* React components via the React Intl Babel plugin. An error will be thrown if
* there are messages in different components that use the same `id`. The result
* is a flat collection of `id: message` pairs for the app's default locale.
*/
let idToFileMap = {};
let duplicateIds = [];
const collectedMessages = globSync(MESSAGES_PATTERN)
.map((filename) => [filename, JSON.parse(fs.readFileSync(filename, 'utf8'))])
.reduce((collection, [file, descriptors]) => {
descriptors.forEach(({id, defaultMessage}) => {
if (collection.hasOwnProperty(id)) {
duplicateIds.push(id);
}
collection[id] = defaultMessage;
idToFileMap[id] = (idToFileMap[id] || []).concat(file);
});
return collection;
}, {});
if (duplicateIds.length) {
console.log('\nFound duplicated ids:');
duplicateIds.forEach((id) => console.log(`${chalk.yellow(id)}:\n - ${idToFileMap[id].join('\n - ')}\n`));
console.log(chalk.red('Please correct the errors above to proceed further!'));
return;
}
duplicateIds = null;
idToFileMap = null;
/**
* Making a diff with the previous DEFAULT_LOCALE version
*/
const defaultMessagesPath = `${LANG_DIR}/${DEFAULT_LOCALE}.json`;
let keysToUpdate = [];
let keysToAdd = [];
let keysToRemove = [];
const keysToRename = [];
const isNotMarked = (value) => value.slice(0, 2) !== '--';
try {
const prevMessages = JSON.parse(fs.readFileSync(defaultMessagesPath, 'utf8'));
const prevMessagesMap = Object.entries(prevMessages).reduce((acc, [key, value]) => {
if (acc[value]) {
acc[value].push(key);
} else {
acc[value] = [key];
}
return acc;
}, {});
keysToAdd = Object.keys(collectedMessages).filter((key) => !prevMessages[key]);
keysToRemove = Object.keys(prevMessages).filter((key) => !collectedMessages[key]).filter(isNotMarked);
keysToUpdate = Object.entries(prevMessages).reduce((acc, [key, message]) =>
acc.concat(collectedMessages[key] && collectedMessages[key] !== message ? key : [])
, []);
// detect keys to rename, mutating keysToAdd and keysToRemove
[].concat(keysToAdd).forEach((toKey) => {
const keys = prevMessagesMap[collectedMessages[toKey]] || [];
const fromKey = keys.find((fromKey) => keysToRemove.indexOf(fromKey) > -1);
if (fromKey) {
keysToRename.push([fromKey, toKey]);
keysToRemove.splice(keysToRemove.indexOf(fromKey), 1);
keysToAdd.splice(keysToAdd.indexOf(toKey), 1);
}
});
} catch (err) {
console.log(chalk.yellow(`Can not read ${defaultMessagesPath}. The new file will be created.`), err);
}
if (!keysToAdd.length && !keysToRemove.length && !keysToUpdate.length && !keysToRename.length) {
return console.log(chalk.green('Everything is up to date!'));
}
console.log(chalk.magenta(`The diff relative to default locale (${DEFAULT_LOCALE}) is:`));
if (keysToRemove.length) {
console.log('The following keys will be removed:');
console.log([chalk.red('\n - '), keysToRemove.join(chalk.red('\n - ')), '\n'].join(''));
}
if (keysToAdd.length) {
console.log('The following keys will be added:');
console.log([chalk.green('\n + '), keysToAdd.join(chalk.green('\n + ')), '\n'].join(''));
}
if (keysToUpdate.length) {
console.log('The following keys will be updated:');
console.log([chalk.yellow('\n @ '), keysToUpdate.join(chalk.yellow('\n @ ')), '\n'].join(''));
}
if (keysToRename.length) {
console.log('The following keys will be renamed:\n');
console.log(keysToRename.reduce((str, pair) =>
[str, pair[0], chalk.yellow(' -> '), pair[1], '\n'].join('')
, ''));
}
prompt.start();
prompt.get({
properties: {
apply: {
description: 'Apply changes? [Y/n]',
pattern: /^y|n$/i,
message: 'Please enter "y" or "n"',
default: 'y',
before: (value) => value.toLowerCase() === 'y'
}
}
}, (err, resp) => {
console.log('\n');
if (err || !resp.apply) {
return console.log(chalk.red('Aborted'));
}
buildLocales();
console.log(chalk.green('All locales was successfuly built'));
});
function buildLocales() {
mkdirpSync(LANG_DIR);
SUPPORTED_LANGS.map((lang) => {
const destPath = `${LANG_DIR}/${lang}.json`;
let newMessages = {};
try {
newMessages = JSON.parse(fs.readFileSync(destPath, 'utf8'));
} catch (err) {
console.log(chalk.yellow(`Can not read ${destPath}. The new file will be created.`), err);
}
keysToRename.forEach(([fromKey, toKey]) => {
newMessages[toKey] = newMessages[fromKey];
delete newMessages[fromKey];
});
keysToRemove.forEach((key) => {
delete newMessages[key];
});
keysToUpdate.forEach((key) => {
newMessages[`--${key}`] = newMessages[key];
newMessages[key] = collectedMessages[key];
});
keysToAdd.forEach((key) => {
newMessages[key] = collectedMessages[key];
});
const sortedKeys = Object.keys(newMessages).sort((key1, key2) => {
key1 = key1.replace(/^\-+/, '');
key2 = key2.replace(/^\-+/, '');
return key1 < key2 || !isNotMarked(key1) ? -1 : 1;
});
const sortedNewMessages = sortedKeys.reduce((acc, key) => {
acc[key] = newMessages[key];
return acc;
}, {});
fs.writeFileSync(destPath, JSON.stringify(sortedNewMessages, null, 4) + '\n');
});
}

17
scripts/package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "scripts",
"version": "1.0.0",
"description": "",
"main": "i18n-build.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^1.1.3",
"node-babel": "^0.1.2",
"prompt": "^1.0.0"
}
}

BIN
src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

24
src/index.ejs Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ely.by - Email Renderer</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<% if (htmlWebpackPlugin.files.favicon) { %>
<link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon %>">
<% } %>
</head>
<body>
<div id="app" class="app"></div>
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>
</body>
</html>

13
src/index.js Normal file
View File

@ -0,0 +1,13 @@
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { IntlProvider } from 'react-intl';
ReactDOM.render(
<IntlProvider locale="en" messages={{}}>
<div>Hello world</div>
</IntlProvider>,
document.getElementById('app')
);

36
webpack-utils/cssUrl.js Normal file
View File

@ -0,0 +1,36 @@
// при использовании sass-loader теряется контекст в импортированных модулях
// из-за чего css-loader не может правильно обработать относительные url
//
// препроцессим урлы перед тем, как пропускать их через sass-loader
// урлы, начинающиеся с / будут оставлены как есть
const cssUrl = require('postcss-url');
const loaderUtils = require('loader-utils');
// /#.+$/ - strip #hash part of svg font url
const urlToRequest = (url) => loaderUtils.urlToRequest(url.replace(/\??#.+$/, ''), true);
const urlPostfix = (url) => {
var idx = url.indexOf('?#');
if (idx < 0) {
idx = url.indexOf('#');
}
return idx >= 0 ? url.slice(idx) : '';
};
module.exports = function(loader) {
return cssUrl({
url: (url, decl, from, dirname, to, options, result) =>
new Promise((resolve, reject) =>
loaderUtils.isUrlRequest(url) ? loader.loadModule(urlToRequest(url), (err, source) =>
err ? reject(err) : resolve(
loader.exec(`
var __webpack_public_path__ = '${loader.options.output.publicPath}';
${source}
`) + urlPostfix(url)
)
) : resolve(url)
)
});
};

View File

@ -0,0 +1,21 @@
module.exports = function() {
this.cacheable && this.cacheable();
var moduleId = this.context
.replace(this.options.resolve.root, '')
.replace(/^\/|\/$/g, '')
.replace(/\//g, '.');
var content = this.inputValue[0];
content = JSON.stringify(Object.keys(content).reduce(function(translations, key) {
translations[key] = {
id: moduleId + '.' + key,
defaultMessage: content[key]
};
return translations;
}, {}));
return 'import { defineMessages } from \'react-intl\';'
+ 'export default defineMessages(' + content + ')';
};

View File

@ -0,0 +1,10 @@
{
"name": "webpack-utils",
"version": "1.0.0",
"description": "",
"keywords": [],
"author": "",
"dependencies": {
"loader-utils": "^0.2.12"
}
}

232
webpack.config.js Normal file
View File

@ -0,0 +1,232 @@
/* eslint-env node */
var path = require('path');
var webpack = require('webpack');
var loaderUtils = require('loader-utils');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var cssUrl = require('webpack-utils/cssUrl');
var cssImport = require('postcss-import');
var vendor = Object.keys(require('./package.json').dependencies);
const rootPath = path.resolve('./src');
const isProduction = process.argv.some((arg) => arg === '-p');
process.env.NODE_ENV = isProduction ? 'production' : 'development';
const CSS_CLASS_TEMPLATE = isProduction ? '[hash:base64:5]' : '[path][name]-[local]';
const fileCache = {};
const cssLoaderQuery = {
modules: true,
importLoaders: 2,
url: false,
localIdentName: CSS_CLASS_TEMPLATE,
/**
* cssnano options
*/
sourcemap: !isProduction,
autoprefixer: {
add: true,
remove: true,
browsers: ['last 2 versions']
},
safe: true,
// отключаем минификацию цветов, что бы она не ломала такие выражения:
// composes: black from './buttons.scss';
colormin: false,
discardComments: {
removeAll: true
}
};
var webpackConfig = {
entry: {
app: path.join(__dirname, 'src'),
vendor: vendor
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js?[hash]'
},
resolve: {
root: rootPath,
extensions: ['', '.js', '.jsx']
},
devServer: {
host: 'localhost',
port: 8080,
// proxy: {
// '/api*': {
// headers: {
// host: config.apiHost.replace(/https?:|\//g, '')
// },
// target: config.apiHost
// }
// },
hot: true,
inline: true,
historyApiFallback: true
},
devtool: 'eval',
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
},
__DEV__: !isProduction,
__PROD__: isProduction
}),
new HtmlWebpackPlugin({
template: 'src/index.ejs',
favicon: 'src/favicon.ico',
hash: isProduction,
filename: 'index.html',
inject: false,
minify: {
collapseWhitespace: isProduction
}
}),
new webpack.ProvidePlugin({
// window.fetch polyfill
fetch: 'imports?this=>self!exports?self.fetch!whatwg-fetch'
}),
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js?[hash]')
].concat(isProduction ? [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin()
] : []),
module: {
loaders: [
{
test: /\.scss$/,
extractInProduction: true,
loader: 'style!css?' + JSON.stringify(cssLoaderQuery) + '!sass!postcss?syntax=postcss-scss'
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
},
{
test: /\.(png|gif|jpg|svg)$/,
loader: 'file',
query: {
name: 'assets/[name].[ext]?[hash]'
}
},
{
test: /\.(woff|woff2|ttf)$/,
loader: 'file',
query: {
name: 'assets/fonts/[name].[ext]?[hash]'
}
},
{
test: /\.json$/,
exclude: /(intl|font)\.json/,
loader: 'json'
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.intl\.json$/,
loader: 'babel!intl!json'
},
{
test: /\.font\.(js|json)$/,
loader: 'raw!fontgen'
}
]
},
resolveLoader: {
alias: {
intl: path.resolve('webpack-utils/intl-loader')
}
},
postcss() {
return [
cssImport({
path: rootPath,
addDependencyTo: webpack,
resolve: ((defaultResolve) =>
(url, basedir, importOptions) =>
defaultResolve(loaderUtils.urlToRequest(url), basedir, importOptions)
)(require('postcss-import/lib/resolve-id')),
load: ((defaultLoad) =>
(filename, importOptions) => {
if (/\.font.(js|json)$/.test(filename)) {
if (!fileCache[filename] || !isProduction) {
// do not execute loader on the same file twice
// this is an overcome for a bug with ExtractTextPlugin, for isProduction === true
// when @imported files may be processed mutiple times
fileCache[filename] = new Promise((resolve, reject) =>
this.loadModule(filename, (err, source) =>
err ? reject(err) : resolve(this.exec(source))
)
);
}
return fileCache[filename];
}
return defaultLoad(filename, importOptions);
}
)(require('postcss-import/lib/load-content'))
}),
cssUrl(this)
];
}
};
if (isProduction) {
webpackConfig.module.loaders.forEach((loader) => {
if (loader.extractInProduction) {
// remove style-loader from chain and pass through ExtractTextPlugin
const parts = loader.loader.split('!');
loader.loader = ExtractTextPlugin.extract(
parts[0], // style-loader
parts.slice(1) // css-loader and rest
.join('!')
.replace(/[&?]sourcemap/, '')
);
}
});
webpackConfig.plugins.push(new ExtractTextPlugin('styles.css', {
allChunks: true
}));
webpackConfig.devtool = false;
}
if (!isProduction) {
webpackConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
);
}
module.exports = webpackConfig;