diff --git a/bin/dev-server.js b/bin/dev-server.js new file mode 100644 index 00000000..aaa670fb --- /dev/null +++ b/bin/dev-server.js @@ -0,0 +1,19 @@ +if (process.env.NODE_ENV === 'production') { + process.exit(1); +} + +process.env.NODE_ENV = 'development'; + +// Error.stackTraceLimit = Infinity; +require('trace'); +require('clarify'); + +const bb = require('bluebird'); +require('babel-runtime/core-js/promise').default = bb; +global.Promise = bb; + +require('./utils/hook'); +const attachChangeCallback = require('./utils/watch')(); + +require('../server')(attachChangeCallback), +require('../tasks')(); diff --git a/bin/start-server.js b/bin/start-server.js index bd947cb1..622959fe 100644 --- a/bin/start-server.js +++ b/bin/start-server.js @@ -1,21 +1,5 @@ -if (process.env.NODE_ENV === 'development') { - // Error.stackTraceLimit = Infinity; - require('trace'); - require('clarify'); -} - const bb = require('bluebird'); - require('babel-runtime/core-js/promise').default = bb; global.Promise = bb; -require("babel-register")({ - ignore: /\/(public|node_modules)\// -}); - -const { server } = require('universal-webpack'); - -const settings = require('../server-uwsettings'); -const configuration = require('../webpack.config.server.babel').default; - -server(configuration, settings); +require('../public/server/server')(); diff --git a/bin/start-tasks.js b/bin/start-tasks.js index 699926de..ca6d38d0 100644 --- a/bin/start-tasks.js +++ b/bin/start-tasks.js @@ -1,21 +1,5 @@ -if (process.env.NODE_ENV === 'development') { - Error.stackTraceLimit = Infinity; - require('trace'); - require('clarify'); -} - const bb = require('bluebird'); - require('babel-runtime/core-js/promise').default = bb; global.Promise = bb; -require("babel-register")({ - ignore: /\/(public|node_modules)\// -}); - -const { server } = require('universal-webpack'); - -const settings = require('../tasks-uwsettings'); -const configuration = require('../webpack.config.server.babel').default; - -server(configuration, settings); +require('../public/server/tasks')(); diff --git a/bin/utils/hook.js b/bin/utils/hook.js new file mode 100644 index 00000000..f9fe0882 --- /dev/null +++ b/bin/utils/hook.js @@ -0,0 +1,31 @@ +const fs = require('fs'); +const addHook = require('asset-require-hook/lib/hook'); +const assetRequireHook = require('asset-require-hook'); +const babelRegisterHook = require("babel-register"); + +function rawLoader(resourcePath) { + const content = fs.readFileSync(resourcePath, 'utf-8'); + return content; +} + +// attachHook(rawLoader, '.ejs'); +addHook('.ejs', rawLoader); + +assetRequireHook({ + extensions: ['.png', 'jpg', '.gif', '.svg'], + name: 'assets/images/[name]-[hash].[ext]', + publicPath: '/' +}); + +assetRequireHook({ + extensions: ['.ico'], + name: '[name]-[hash].[ext]', + publicPath: '/' +}); + +babelRegisterHook({ + ignore: /\/(public|node_modules)\//, + plugins: [ + 'react-hot-loader/babel' + ] +}); diff --git a/bin/utils/watch.js b/bin/utils/watch.js new file mode 100644 index 00000000..de7ccb42 --- /dev/null +++ b/bin/utils/watch.js @@ -0,0 +1,36 @@ +const path = require('path'); + +const watcher = require('chokidar') + .watch([ + path.join(__dirname, '../../src/api'), + path.join(__dirname, '../../src/utils') + ]); + +module.exports = function () { + const callbacks = []; + + watcher.on('ready', () => { + watcher.on('all', () => { + // eslint-disable-next-line no-console + console.log('Clearing server module cache from server'); + Object.keys(require.cache).forEach(id => { + if (/[/\\]src[/\\](api|utils)/.test(id)) { + delete require.cache[id]; + } + }); + + callbacks.forEach(c => c()); + }); + }); + + return callbacks.push.bind(callbacks); +}; + +/* Optional: "hot-reloading" of client related modules on the server + + const compiler = require('webpack')(require('../../res/webpack/client.js')); + compiler.plugin('done', () => { + // Need to separate client and server code to prevent unnecessary reloads + }); + +*/ diff --git a/package.json b/package.json index 31154121..0d8d2345 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,22 @@ }, "main": "index.js", "scripts": { - "start": "run-s build:pre dev:all", - "start:prod": "run-s build:all:prod start:_all:prod", - "build:all:prod": "run-s build:pre build:_all:prod", - "test": "NODE_ENV=development DB_ENV=test run-s reset-db:test mocha", - "coverage": "NODE_ENV=development DB_ENV=test npm run coverage:run", - "travis": "NODE_ENV=development DB_ENV=travis run-s reset-db:travis build:client:dev travis:test lint travis:flow", + "start": "run-s build:client:vendor start-dev", + "start-dev": "node bin/dev-server.js 2>&1 | bunyan", + "prod": "run-s build:all:prod start:all:prod", + "start:all:prod": "NODE_ENV=production run-p start:server start:tasks", + "start:server": "node bin/start-server.js 2>&1 | bunyan", + "start:tasks": "node bin/start-tasks.js", + "build": "run-s cleanup build:client:app build:server build:tasks", + "build:all:prod": "NODE_ENV=production npm run build", + "build:client": "run-s build:client:vendor build:client:app", + "build:client:app": "webpack --config './res/webpack/client.js'", + "build:client:vendor": "webpack --config './res/webpack/client-vendor.js'", + "build:server": "webpack --config './res/webpack/server.js'", + "build:tasks": "webpack --config './res/webpack/tasks.js'", + "test": "DB_ENV=test run-s reset-db:test cleanup build:client mocha", + "coverage": "DB_ENV=test npm run coverage:run", + "travis": "DB_ENV=travis run-s reset-db:travis cleanup build:client travis:test lint travis:flow", "travis:flow": "run-s flow:install-types flow", "travis:test": "run-s coverage:run coverage:coveralls coverage:clean", "reset-db:test": "echo \"Preparing test database...\" && babel-node test-helpers/dropDatabase.js && knex --env test migrate:latest", @@ -37,28 +47,7 @@ "update-post-counters": "babel-node bin/postCounters.js", "gulp:build": "gulp build", "gulp:watch": "gulp watch", - "build:pre": "run-s cleanup prepare-server-build build:server:dummy-chunks", - "cleanup": "rm -rf public/*", - "prepare-server-build": "universal-webpack --settings ./server-uwsettings.js prepare", - "build:server:dummy-chunks": "ln -s ../webpack-chunks.json public/server/webpack-chunks.json", - "dev:all": "run-p build:_all:dev:watch start:_all:dev", - "start:_all:dev": "run-p start:server:dev start:tasks:dev", - "start:_all:prod": "run-p start:server:prod start:tasks:prod", - "start:server:prod": "DEV=0 NODE_ENV=production node bin/start-server.js 2>&1 | bunyan", - "start:tasks:prod": "DEV=0 NODE_ENV=production node bin/start-tasks.js", - "start:server:dev": "DEV=1 NODE_ENV=development nodemon ./bin/start-server.js --watch ./public/server 2>&1 | bunyan", - "start:tasks:dev": "DEV=1 NODE_ENV=development nodemon ./bin/start-tasks.js --watch ./public/server", - "build:_all:prod": "run-p build:client:prod build:server:prod build:tasks:prod", - "build:_all:dev:watch": "run-p build:client:dev:watch build:server:dev:watch build:tasks:dev:watch", - "build:client:dev": "DEV=1 NODE_ENV=development webpack --config './webpack.config.client.babel.js' --colors --hide-modules --display-error-details", - "build:client:dev:watch": "DEV=1 NODE_ENV=development webpack --config './webpack.config.client.babel.js' --colors --hide-modules --watch", - "build:client:prod": "DEV=0 NODE_ENV=production webpack --config './webpack.config.client.babel.js' --colors --display-error-details", - "build:server:dev": "DEV=1 NODE_ENV=development webpack --config './webpack.config.server.babel.js' --colors --hide-modules --display-error-details", - "build:server:dev:watch": "DEV=1 NODE_ENV=development webpack --config './webpack.config.server.babel.js' --colors --hide-modules --display-error-details --watch", - "build:server:prod": "DEV=0 NODE_ENV=production webpack --config './webpack.config.server.babel.js' --colors --display-error-details", - "build:tasks:dev": "DEV=1 NODE_ENV=development webpack --config './webpack.config.tasks.babel.js' --colors --hide-modules --display-error-details", - "build:tasks:dev:watch": "DEV=1 NODE_ENV=development webpack --config './webpack.config.tasks.babel.js' --colors --hide-modules --display-error-details --watch", - "build:tasks:prod": "DEV=0 NODE_ENV=production webpack --config './webpack.config.tasks.babel.js' --colors --display-error-details" + "cleanup": "rm -rf public/*" }, "author": { "name": "Loki Education (Social Enterprise)", @@ -162,6 +151,7 @@ }, "devDependencies": { "adm-zip": "^0.4.7", + "asset-require-hook": "^1.2.0", "autoprefixer": "~7.1.4", "babel-core": "~6.26.0", "babel-eslint": "~8.0.0", @@ -184,6 +174,7 @@ "babel-preset-react-hmre": "^1.1.1", "babel-preset-stage-1": "~6.24.1", "brfs": "^1.4.3", + "chokidar": "^1.7.0", "clarify": "^2.0.0", "cookie": "~0.3.1", "coveralls": "^2.11.9", @@ -211,6 +202,7 @@ "istanbul": "~1.1.0-alpha.1", "jsdom": "~11.2.0", "json-loader": "~0.5.7", + "koa-webpack-dev-middleware": "^2.0.2", "less": "^2.6.0", "less-loader": "~4.0.3", "loader-utils": "^1.1.0", @@ -225,6 +217,7 @@ "postcss-loader": "~2.0.6", "raf": "^3.4.0", "raw-loader": "^0.5.1", + "react-hot-loader": "^3.1.1", "react-svg-inline-loader": "^0.2.2", "react-test-renderer": "^16.2.0", "react-transform-hmr": "^1.0.1", @@ -240,9 +233,12 @@ "unexpected-immutable": "~0.2.6", "unexpected-react": "^5.0.1", "unexpected-sinon": "~10.8.2", - "universal-webpack": "~0.4.0", "url-loader": "^0.5.7", "webpack": "~3.6.0", + "webpack-bundle-analyzer": "^2.9.0", + "webpack-koa-hot-middleware": "^0.1.2", + "webpack-manifest-plugin": "^1.3.2", + "webpack-node-externals": "^1.6.0", "wikidata-sdk": "~5.2.7", "zopfli-webpack-plugin": "^0.1.0" }, diff --git a/res/webpack/base.js b/res/webpack/base.js new file mode 100644 index 00000000..5e13ab14 --- /dev/null +++ b/res/webpack/base.js @@ -0,0 +1,90 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +const path = require('path'); +const merge = require('lodash/merge'); +const webpack = require('webpack'); + +const + NODE_ENV = process.env.NODE_ENV, + __DEV__ = NODE_ENV !== 'production', + context = path.join(__dirname, '../../'); + +// console.log(NODE_ENV); + +const baseConfiguration = { + context, + module: { + noParse: (path) => { + if (/react.*\.production\.min\.js$/.test(path)) { + return false; + } + return /\.min\.js$/.test(path); + } + }, + output: { + filename: '[name].js', + publicPath: '/' + }, + performance: { + hints: false + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), + 'process.env.FACEBOOK_AUTH_ENABLED': !!process.env.FACEBOOK_CLIENT_ID, + 'process.env.GOOGLE_AUTH_ENABLED': !!process.env.GOOGLE_CLIENT_ID, + 'process.env.TWITTER_AUTH_ENABLED': !!process.env.TWITTER_CONSUMER_KEY, + 'process.env.GITHUB_AUTH_ENABLED': !!process.env.GITHUB_CLIENT_ID + }), + new webpack.NoEmitOnErrorsPlugin(), + new webpack.LoaderOptionsPlugin({ + debug: __DEV__, + minimize: !__DEV__, + options: { + context + } + }) + ], + stats: { + assets: false, + cached: false, + children: false, + chunks: true, + chunkOrigins: false, + modules: false, + reasons: false, + source: false, + timings: true + } +}; + +if (__DEV__) { + merge(baseConfiguration, { + cache: true, + performance: { + hints: 'warning' + }, + resolve: { + alias: { + 'prop-types$': path.join(context, './src/external/prop-types.js') + } + } + }); +} + +module.exports = baseConfiguration; diff --git a/res/webpack/client-vendor.js b/res/webpack/client-vendor.js new file mode 100644 index 00000000..60e74e22 --- /dev/null +++ b/res/webpack/client-vendor.js @@ -0,0 +1,78 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +const path = require('path'); +const webpack = require('webpack'); + +const context = path.join(__dirname, '../../'); + +module.exports = { + entry: { + vendor: [ + 'babel-polyfill', + // 'babel-runtime', + 'bluebird', + 'classnames', + 'codemirror', + 'debounce-promise', + 'flow-runtime', + // 'font-awesome', + 'immutable', + 'isomorphic-fetch', + 'leaflet', + 'lodash', + 'memoizee', + 'prop-types', + 'react', + 'react-autosuggest', + 'react-dom', + 'react-helmet', + 'react-hot-loader', + 'react-hot-loader/patch', + 'react-icons/lib/fa', + 'react-icons/lib/md', + 'react-inform', + 'react-linkify', + 'react-router', + 'react-router-redux', + 'react-transition-group', + 'redux', + 'redux-catch', + 'redux-immutablejs', + 'reselect', + 't8on', + 'webpack-hot-middleware', + 'zxcvbn' + ] + }, + output: { + filename: 'assets/[name].js', + library: '[name]', + libraryTarget: 'var', + path: path.join(context, 'public') + }, + plugins: [ + new webpack.DllPlugin({ + name: '[name]', + path: path.join(context, 'public/[name]-manifest.json') + }) + ], + resolve: { + extensions: ['.js', '.jsx', '.less', '.css'] + }, + target: 'web' +}; diff --git a/res/webpack/client.js b/res/webpack/client.js new file mode 100644 index 00000000..c41fd2cd --- /dev/null +++ b/res/webpack/client.js @@ -0,0 +1,146 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +const path = require('path'); +const merge = require('lodash/merge'); + +const webpack = require('webpack'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const ManifestPlugin = require('webpack-manifest-plugin'); +const ZopfliPlugin = require('zopfli-webpack-plugin'); + +const baseConfiguration = require('./base'); +const rules = require('./rules'); + +const + context = baseConfiguration.context, + NODE_ENV = process.env.NODE_ENV, + __DEV__ = NODE_ENV !== 'production'; + +const clientConfiguration = merge({}, baseConfiguration, { + output: { + filename: __DEV__ ? 'assets/[name].js' : 'assets/[name]-[chunkhash].js', + path: `${context}/public` + }, + module: { + rules: [ + ...rules.clientLibFixes(), + ...rules.clientJS(__DEV__, context), + ...rules.css(__DEV__), + ...rules.less(__DEV__, context), + ...rules.fonts(__DEV__), + ...rules.favicons(), + ...rules.images(), + ] + }, + plugins: [ + new webpack.IgnorePlugin(/^ioredis$/), + new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /en/), + new webpack.optimize.CommonsChunkPlugin({ + filename: __DEV__ ? 'assets/[name].js' : 'assets/[name]-[chunkhash].js', + name: 'manifest', + minChunks: Infinity + }) + ], + target: 'web' +}); + +if (__DEV__) { + merge(clientConfiguration, { + devtool: 'eval', + entry: { + app: [ + 'babel-polyfill', + 'react-hot-loader/patch', + 'webpack-hot-middleware/client?path=/__webpack_hmr', + `${context}/src/scripts/app.js` + ], + uikit: [ + 'babel-polyfill', + 'react-hot-loader/patch', + 'webpack-hot-middleware/client?path=/__webpack_hmr', + `${context}/src/uikit/scripts.js` + ] + }, + plugins: [ + new webpack.DllReferencePlugin({ + context, + name: 'vendor', + manifest: require(`${context}/public/vendor-manifest.json`), // eslint-disable-line + extensions: ['.js', '.jsx'] + }), + new webpack.NamedChunksPlugin(), + new webpack.HotModuleReplacementPlugin(), + new ManifestPlugin({ + fileName: 'webpack-chunks.json', + publicPath: '/', + writeToFileEmit: true + }) + ], + resolve: { + unsafeCache: true + } + }); +} else { + merge(clientConfiguration, { + entry: { + app: ['babel-polyfill', `${context}/src/scripts/app.js`], + uikit: ['babel-polyfill', `${context}/src/uikit/scripts.js`] + }, + plugins: [ + new webpack.EnvironmentPlugin([ + 'API_HOST', 'NODE_ENV', 'MAPBOX_ACCESS_TOKEN', + 'GOOGLE_ANALYTICS_ID', 'GOOGLE_TAG_MANAGER_ID' + ]), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + pure_getters: true, + unsafe: true, + unsafe_comps: true, + screw_ie8: true + }, + exclude: [/\.min\.js$/gi], + output: { + comments: false + }, + sourceMap: false + }), + new ExtractTextPlugin({ + filename: path.join('assets/styles/[name]-[contentHash].css'), + allChunks: true + }), + // cannot push to __DEV__-independent config + // since the ordering makes sense (after ExtractTextPlugin in this case) + new ManifestPlugin({ + fileName: 'webpack-chunks.json', + publicPath: '/', + writeToFileEmit: true + }), + new webpack.optimize.AggressiveMergingPlugin({ + minSizeReduce: true + }), + new ZopfliPlugin({ + algorithm: 'zopfli', + asset: '[path].gz[query]', + minRatio: 0.8 + }) + ] + }); +} + +module.exports = clientConfiguration; diff --git a/res/webpack/rules.js b/res/webpack/rules.js new file mode 100644 index 00000000..c7da34b2 --- /dev/null +++ b/res/webpack/rules.js @@ -0,0 +1,247 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +const path = require('path'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const omit = require('lodash/omit'); + +/** Embed font in CSS if dev */ +function fontLoader(options, __DEV__) { + const loaderObject = {}; + loaderObject.options = options; + if (__DEV__) { + loaderObject.loader = 'url-loader'; + loaderObject.options.limit = 150000; + } else { + loaderObject.loader = 'file-loader'; + } + return loaderObject; +} + +module.exports = {}; + +module.exports.clientJS = (__DEV__, context) => [{ + test: /\.js?$/, + include: [path.join(context, 'src'), path.join(context, 'public/assets')], + exclude: /(node_modules)/, + loader: 'babel-loader', + options: { + cacheDirectory: true, + ignore: /(node_modules)/, + presets: [ + 'react', + ['es2015', { modules: false }], + 'stage-1' + ], + plugins: (function () { + const plugins = [ + 'syntax-do-expressions', + 'transform-do-expressions', + 'lodash' + ]; + + if (__DEV__) { + plugins.push( + 'transform-runtime' + /*, ['flow-runtime', { annotate: false, assert: true, warn: true }] */ + ); + } else { + plugins.push( + 'transform-react-constant-elements', + 'transform-react-inline-elements' + ); + } + + return plugins; + })() + } +}]; + +module.exports.clientLibFixes = () => [{ + enforce: 'post', + test: /\.js$/, + include: /node_modules\/grapheme-breaker/, + loader: 'transform-loader/cacheable?brfs' +}]; + +module.exports.css = (__DEV__) => [{ + test: /\.css$/ +}].map(rule => { + let use; + if (__DEV__) { + use = [ + { loader: 'style-loader?sourceMap' }, + { loader: 'css-loader?sourceMap' }, + { loader: 'postcss-loader?sourceMap' } + ]; + } else { + use = ExtractTextPlugin.extract({ + fallback: 'style-loader', + publicPath: '../../', + use: [ + { loader: 'css-loader', + options: { + autoprefixer: false, + calc: false, + mergeIdents: false, + mergeRules: false, + uniqueSelectors: false + } }, + { loader: 'postcss-loader' } + ] + }); + } + + return Object.assign(rule, { use }); +}); + +module.exports.ejsIndexTemplate = () => [{ + test: /\/index\.ejs$/, + loader: 'raw-loader' +}]; + +module.exports.favicons = () => [{ + test: /\.ico$/, + loader: 'file-loader', + options: { + name: '[name]-[hash].[ext]' + } +}]; + +module.exports.fonts = (__DEV__) => { + const outputName = 'assets/fonts/[name]-[hash].[ext]'; + return [ + { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, + include: /node_modules\/font-awesome/, + options: { + mimetype: 'image/svg+xml', + name: outputName } }, + { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, + options: { + mimetype: 'application/font-woff', + name: outputName } }, + { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, + options: { + mimetype: 'application/font-woff2', + name: outputName } }, + { test: /\.otf(\?v=\d+\.\d+\.\d+)?$/, + options: { + mimetype: 'application/x-font-opentype', + name: outputName } }, + { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, + options: { + mimetype: 'application/x-font-truetype', + name: outputName } }, + { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, + options: { + mimetype: 'application/vnd.ms-fontobject', + name: outputName } } + ].map(rule => ({ + ...omit(rule, ['options']), + loader: fontLoader(rule.options, __DEV__) + })); +}; + +module.exports.images = (__SERVER__) => [{ + test: /\.(png|jpg|gif|svg)(\?v=\d+\.\d+\.\d+)?$/, + exclude: /node_modules|fonts/, + loader: 'file-loader', + options: { + emitFile: !__SERVER__, + name: 'assets/images/[name]-[hash].[ext]' + } +}]; + +module.exports.less = (__DEV__, context) => [{ + resource: { + and: [ + { test: /\.less$/ }, + { include: path.join(context, 'src/less') }, + ] + } +}].map(rule => { + let use; + if (__DEV__) { + use = [ + { loader: 'style-loader?sourceMap' }, + { loader: 'css-loader?sourceMap' }, + { loader: 'postcss-loader?sourceMap' }, + { loader: 'less-loader?sourceMap' } + ]; + } else { + use = ExtractTextPlugin.extract({ + fallback: 'style-loader', + publicPath: '../../', + use: [ + { loader: 'css-loader', + options: { + autoprefixer: false, + calc: false, + mergeIdents: false, + mergeRules: false, + uniqueSelectors: false + } }, + { loader: 'postcss-loader' }, + { loader: 'less-loader' } + ] + }); + } + + return Object.assign(rule, { use }); +}); + +module.exports.serverJS = (__DEV__, context) => [{ + test: /\.js?$/, + include: [ + path.join(context, 'server.js'), + path.join(context, 'tasks.js'), + path.join(context, 'src'), + ], + exclude: /(node_modules)/, + loader: 'babel-loader', + options: { + cacheDirectory: true, + ignore: /(node_modules)/, + presets: ["react"], + plugins: (() => { + const plugins = [ + "syntax-class-properties", + "syntax-do-expressions", + "syntax-dynamic-import", + "syntax-object-rest-spread", + "transform-do-expressions", + "transform-object-rest-spread", + "transform-class-properties", + "lodash" + ]; + + /* + if (__DEV__) { + plugins.push( + ["flow-runtime", { + "annotate": false, + "assert": true, + "warn": true + }] + ); + } + */ + + return plugins; + })(), + } +}]; diff --git a/res/webpack/server.js b/res/webpack/server.js new file mode 100644 index 00000000..bde80991 --- /dev/null +++ b/res/webpack/server.js @@ -0,0 +1,55 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +const merge = require('lodash/merge'); +const nodeExternals = require('webpack-node-externals'); + +const baseConfiguration = require('./base'); +const rules = require('./rules'); + +const + context = baseConfiguration.context, + NODE_ENV = process.env.NODE_ENV, + __DEV__ = NODE_ENV !== 'production', + __SERVER__ = true; + +const serverConfiguration = merge({}, baseConfiguration, { + entry: { + server: `${context}/server.js`, + }, + externals: [ + nodeExternals() + ], + module: { + rules: [ + ...rules.serverJS(__DEV__, context), + ...rules.ejsIndexTemplate(), + ...rules.images(__SERVER__) + ] + }, + node: { + __dirname: false, + __filename: false + }, + output: { + libraryTarget: 'commonjs2', + path: `${context}/public/server` + }, + target: 'node' +}); + +module.exports = serverConfiguration; diff --git a/res/webpack/tasks.js b/res/webpack/tasks.js new file mode 100644 index 00000000..1357e675 --- /dev/null +++ b/res/webpack/tasks.js @@ -0,0 +1,50 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +const merge = require('lodash/merge'); +const nodeExternals = require('webpack-node-externals'); + +const baseConfiguration = require('./base'); +const rules = require('./rules'); + +const + context = baseConfiguration.context, + NODE_ENV = process.env.NODE_ENV, + __DEV__ = NODE_ENV !== 'production'; + +const tasksConfiguration = merge({}, baseConfiguration, { + entry: { + tasks: `${context}/tasks.js`, + }, + externals: [ + nodeExternals() + ], + module: { + rules: rules.serverJS(__DEV__, context) + }, + node: { + __dirname: false, + __filename: false + }, + output: { + libraryTarget: 'commonjs2', + path: `${context}/public/server` + }, + target: 'node' +}); + +module.exports = tasksConfiguration; diff --git a/server-uwsettings.js b/server-uwsettings.js deleted file mode 100644 index 3e80e675..00000000 --- a/server-uwsettings.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - server: { - input: `${__dirname}/server.js`, - output: `${__dirname}/public/server/server.js`, - publicPath: '/' - } -}; diff --git a/server.js b/server.js index 82d9ed75..2f1577c8 100644 --- a/server.js +++ b/server.js @@ -39,7 +39,6 @@ import redis from 'redis'; import t from 't8on'; import createRequestLogger from './src/utils/bunyan-koa-request'; -import { initApi } from './src/api/routing'; import initBookshelf from './src/api/db'; import initSphinx from './src/api/sphinx'; import { API_HOST } from './src/config'; @@ -183,11 +182,15 @@ const initReduxForMainApp = async (ctx) => { const serve = (...params) => staticCache(...params); -function startServer(/*params*/) { +module.exports = function startServer(attachChangeCallback) { const sphinx = initSphinx(); - const api = initApi(bookshelf); - const staticsRoot = path.join(__dirname, '..'); // calculated starting from "public/server/server.js" + let staticsRoot; + if (process.env.NODE_ENV === 'production') { + staticsRoot = path.join(__dirname, '..'); + } else { + staticsRoot = path.join(__dirname, 'public'); + } const staticsAppConfig = { buffer: true, @@ -236,8 +239,24 @@ function startServer(/*params*/) { } }); - if (exec_env === 'development') { + if (process.env.NODE_ENV !== 'production' && dbEnv === 'development') { logger.level('debug'); + + const webpack = require('webpack'); + const webpackDevMiddleware = require('koa-webpack-dev-middleware'); + const webpackHotMiddleware = require('webpack-koa-hot-middleware').default; + const webpackConfig = require('./res/webpack/client'); + const compiler = webpack(webpackConfig); + + app.use(convert(webpackDevMiddleware(compiler, { + log: logger.debug.bind(logger), + path: '/__webpack_hmr', + publicPath: webpackConfig.output.publicPath, + stats: { + colors: true + } + }))); + app.use(convert(webpackHotMiddleware(compiler))); } app.use(createRequestLogger({ level: 'info', logger })); @@ -269,7 +288,16 @@ function startServer(/*params*/) { app.use(koaConditional()); app.use(koaEtag()); - app.use(mount('/api/v1', api)); + if (process.env.NODE_ENV !== 'production' && attachChangeCallback) { + const setupApiReload = require('./src/utils/reload-api').default; + const reloadApi = setupApiReload(app, bookshelf, sphinx); + attachChangeCallback(reloadApi); + reloadApi(true); // initialize the API + } else { + const { initApi } = require('./src/api/routing'); + app.use(mount('/api/v1', initApi(bookshelf, sphinx))); + } + app.use(staticsApp); app.use(mount('/uikit', getReactMiddleware( 'uikit', @@ -294,6 +322,4 @@ function startServer(/*params*/) { }); return app; -} - -export default startServer; +}; diff --git a/src/components/dev-container.js b/src/components/dev-container.js new file mode 100644 index 00000000..828c6103 --- /dev/null +++ b/src/components/dev-container.js @@ -0,0 +1,41 @@ +/* + This file is a part of libertysoil.org website + Copyright (C) 2017 Loki Education (Social Enterprise) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router'; + +import { getRoutes } from '../routing'; + +export default class DevContainer extends React.PureComponent { + static propTypes = { + handlers: PropTypes.arrayOf(PropTypes.any), + history: PropTypes.shape(), + store: PropTypes.shape() + }; + + render() { + return ( + + + {getRoutes(...this.props.handlers)} + + + ); + } +} diff --git a/src/scripts/app.js b/src/scripts/app.js index 5cdbb56d..42593608 100644 --- a/src/scripts/app.js +++ b/src/scripts/app.js @@ -19,12 +19,10 @@ import bluebird from 'bluebird'; import 'raf/polyfill'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; -import { Router, browserHistory } from 'react-router'; +import { browserHistory } from 'react-router'; import { syncHistoryWithStore } from 'react-router-redux'; import t from 't8on'; -import { getRoutes } from '../routing'; import { isStorageAvailable } from '../utils/browser'; import { AuthHandler, FetchHandler } from '../utils/loader'; import { API_HOST } from '../config'; @@ -65,12 +63,50 @@ if (!is_logged_in) { const authHandler = new AuthHandler(store); const fetchHandler = new FetchHandler(store, client); +const handlers = [ + authHandler.handle, + fetchHandler.handle, + fetchHandler.handleChange +]; -ReactDOM.hydrate( - - - {getRoutes(authHandler.handle, fetchHandler.handle, fetchHandler.handleChange)} - - , - document.getElementById('content') -); +let render; +if (process.env.NODE_ENV !== 'production' && module.hot) { + const ReactHotLoader = require('react-hot-loader').AppContainer; + const composeWith = (DevContainer) => ( + + + + ); + + render = function () { + const { default: DevContainer } = require('../components/dev-container'); + + ReactDOM.hydrate( + composeWith(DevContainer), + document.getElementById('content') + ); + }; + + module.hot.accept('../components/dev-container', render); +} else { + const { Provider } = require('react-redux'); + const { Router } = require('react-router'); + const { getRoutes } = require('../routing'); + + render = function () { + ReactDOM.hydrate( + + + {getRoutes(...handlers)} + + , + document.getElementById('content') + ); + }; +} + +render(); diff --git a/src/utils/koa-react.js b/src/utils/koa-react.js index 4117e924..699bf6d6 100644 --- a/src/utils/koa-react.js +++ b/src/utils/koa-react.js @@ -1,5 +1,5 @@ import fs from 'fs'; -import path from 'path'; +// import path from 'path'; import React from 'react'; @@ -20,11 +20,9 @@ import { AuthHandler, FetchHandler } from './loader'; const matchPromisified = promisify(match, { multiArgs: true }); const readFile = promisify(fs.readFile); -const isTest = ['test', 'travis'].includes(process.env.DB_ENV); - let template; -if (isTest) { - template = ejs.compile(fs.readFileSync(path.resolve(__dirname, '../views/index.ejs'), 'utf8')); +if (typeof templateData === 'function') { + template = templateData; } else { template = ejs.compile(templateData, { filename: 'index.ejs' }); } @@ -35,10 +33,7 @@ export function getReactMiddleware(appName, prefix, getRoutes, reduxInitializer, const reactMiddleware = async (ctx) => { if (!webpackChunks) { try { - let chunksFilename = `${__dirname}/../webpack-chunks.json`; - if (isTest) { - chunksFilename = `${__dirname}/../../public/webpack-chunks.json`; - } + const chunksFilename = `${__dirname}/../../public/webpack-chunks.json`; const data = await readFile(chunksFilename); webpackChunks = JSON.parse(data); @@ -96,6 +91,7 @@ export function getReactMiddleware(appName, prefix, getRoutes, reduxInitializer, ); + const state = JSON.stringify(store.getState().toJS()); if (fetchHandler.status !== null) { @@ -111,6 +107,10 @@ export function getReactMiddleware(appName, prefix, getRoutes, reduxInitializer, webpackChunks, }; + if (process.env.NODE_ENV !== 'production') { + paths.webpackChunks['vendor.js'] = '/assets/vendor.js'; + } + ctx.staus = 200; ctx.body = template({ appName, state, html, metadata, gtm, localization, paths }); } catch (e) { diff --git a/src/utils/reload-api.js b/src/utils/reload-api.js new file mode 100644 index 00000000..ffaff1dd --- /dev/null +++ b/src/utils/reload-api.js @@ -0,0 +1,19 @@ +import mount from 'koa-mount'; + +export default function setupApiReloader(app, bookshelf, sphinx) { + let middleware; + return function handleChange(initial) { + const { initApi } = require('../api/routing'); + const api = initApi(bookshelf, sphinx); + + if (initial) { + middleware = mount('/api/v1', api); + app.use(middleware); + } else { + const i = app.middleware.findIndex(m => m === middleware); + if (i >= 0) { + app.middleware[i] = middleware = mount('/api/v1', api); + } + } + }; +} diff --git a/src/views/index.ejs b/src/views/index.ejs index 77d96f3c..39290985 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -20,8 +20,12 @@ <%- metadata.title %> - - + <% if (paths.webpackChunks['vendor.css']) { %> + + <% } %> + <% if (paths.webpackChunks['app.css']) { %> + + <% } %> <% if (gtm) { %> @@ -45,9 +49,9 @@ var state = <%- state %>;
<%- html %>
- - - + + + diff --git a/tasks-uwsettings.js b/tasks-uwsettings.js deleted file mode 100644 index 5f07cb80..00000000 --- a/tasks-uwsettings.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - server: { - input: `${__dirname}/tasks.js`, - output: `${__dirname}/public/server/tasks.js`, - publicPath: '/' - } -}; diff --git a/tasks.js b/tasks.js index 1a5e95bd..357db680 100644 --- a/tasks.js +++ b/tasks.js @@ -31,7 +31,7 @@ const knexConfig = dbConfig[dbEnv]; const bookshelf = initBookshelf(knexConfig); const knex = bookshelf.knex; -export default function startServer(/*params*/) { +module.exports = function startServer(/*params*/) { const queue = kueLib.createQueue(config.kue); // Every 10 minutes, update post statistics. @@ -155,4 +155,4 @@ export default function startServer(/*params*/) { }); process.stdout.write(`Job service started\n`); -} +}; diff --git a/test-helpers/expect.js b/test-helpers/expect.js index 59be1916..7453854a 100644 --- a/test-helpers/expect.js +++ b/test-helpers/expect.js @@ -9,7 +9,7 @@ import { TestCache } from '../src/api/utils/cache'; global.$bookshelf = initBookshelf(global.$dbConfig); -const startServer = require('../server').default; +const startServer = require('../server'); const app = startServer(); // A very basic stubs to make API tests pass without intalling sphinx. diff --git a/webpack.config.client.babel.js b/webpack.config.client.babel.js deleted file mode 100644 index 02902b95..00000000 --- a/webpack.config.client.babel.js +++ /dev/null @@ -1,39 +0,0 @@ -import path from 'path'; - -import webpack from 'webpack'; -import { client_configuration } from 'universal-webpack'; -import ExtractTextPlugin from 'extract-text-webpack-plugin'; - -import { opts } from './webpack/base-config'; -import baseClientConfiguration from './webpack/client-config'; -import settings from './server-uwsettings'; // eslint-disable-line import/default - - -const clientConfiguration = client_configuration( - { - ...baseClientConfiguration, - entry: { - "app": ['babel-polyfill', `${__dirname}/src/scripts/app.js`], - "uikit": ['babel-polyfill', `${__dirname}/src/uikit/scripts.js`], - }, - plugins: [ - ...baseClientConfiguration.plugins, - new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /en/), - new webpack.optimize.CommonsChunkPlugin({ - name: "vendor", - minChunks: (module) => /node_modules/.test(module.resource) - }), - new webpack.optimize.CommonsChunkPlugin({ - name: "manifest", - minChunks: Infinity - }), - new ExtractTextPlugin({ - filename: path.join('assets', 'styles', '[name]-[contentHash].css'), - allChunks: true - }), - ], - }, - { ...settings, development: opts.dev, silent: true } -); - -export default clientConfiguration; diff --git a/webpack.config.server.babel.js b/webpack.config.server.babel.js deleted file mode 100644 index 78b47014..00000000 --- a/webpack.config.server.babel.js +++ /dev/null @@ -1,15 +0,0 @@ -import { server_configuration } from 'universal-webpack'; - -import settings from './server-uwsettings'; // eslint-disable-line import/default -import configuration from './webpack/server-config'; - -global.__SERVER__ = true; - -const serverConfiguration = { - ...configuration, - entry: { - "server": `${__dirname}/server.js`, - } -}; - -export default server_configuration(serverConfiguration, settings); diff --git a/webpack.config.tasks.babel.js b/webpack.config.tasks.babel.js deleted file mode 100644 index a8bfea98..00000000 --- a/webpack.config.tasks.babel.js +++ /dev/null @@ -1,15 +0,0 @@ -import { server_configuration } from 'universal-webpack'; - -import settings from './tasks-uwsettings'; // eslint-disable-line import/default -import configuration from './webpack/server-config'; - -global.__SERVER__ = true; - -const serverConfiguration = { - ...configuration, - entry: { - "tasks": `${__dirname}/tasks.js`, - } -}; - -export default server_configuration(serverConfiguration, settings); diff --git a/webpack/base-config.js b/webpack/base-config.js deleted file mode 100644 index 906ea1e2..00000000 --- a/webpack/base-config.js +++ /dev/null @@ -1,82 +0,0 @@ -import path from 'path'; - -import webpack from 'webpack'; - -import RuleGenerator from './rules'; -import { skipFalsy, strToBool } from './utils'; - - -const { env } = process; -export const opts = { - dev: strToBool(env.DEV, false), - prefix: '/' -}; - -const rules = new RuleGenerator(opts.dev); - -const config = { - context: __dirname, - performance: { hints: false }, - stats: { - children: false, modules: false, reasons: false, timings: true, assets: false, chunks: true, cached: false, source: false, chunkOrigins: false - }, - plugins: skipFalsy([ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': opts.dev ? '"development"' : '"production"', - 'process.env.FACEBOOK_AUTH_ENABLED': !!process.env.FACEBOOK_CLIENT_ID, - 'process.env.GOOGLE_AUTH_ENABLED': !!process.env.GOOGLE_CLIENT_ID, - 'process.env.TWITTER_AUTH_ENABLED': !!process.env.TWITTER_CONSUMER_KEY, - 'process.env.GITHUB_AUTH_ENABLED': !!process.env.GITHUB_CLIENT_ID, - }), - opts.dev && new webpack.HotModuleReplacementPlugin(), - opts.dev && new webpack.NoEmitOnErrorsPlugin(), - new webpack.LoaderOptionsPlugin({ - debug: opts.dev, - minimize: !opts.dev - }), - ]), - - output: { - devtoolModuleFilenameTemplate: '/[absolute-resource-path]', - filename: path.join("scripts", opts.dev ? "[name].js" : "[name]-[chunkHash].js"), - path: path.join(rules.root, "public"), - publicPath: opts.prefix, - }, - - module: { - rules: [ - rules.commmonLess, - rules.externalCss, - rules.componentsLess, - rules.images, - rules.favicons, - ...rules.fonts - ], - noParse: (path) => { - if (/react.*\.production\.min\.js$/.test(path)) { - return false; - } - return /\.min\.js$/.test(path); - }, - }, - resolveLoader: { - alias: { - "mycssmoduleloader": path.join(rules.root, 'webpack', "/cssmoduleloader.js"), - } - }, - - resolve: { - alias: { - _assets: path.normalize(path.join(rules.root, "src", "_assets")), - _common: path.normalize(path.join(rules.root, "src", "_common")), - _root: path.normalize(rules.root), - 'prop-types$': path.join(__dirname, '../src/external/prop-types.js') - } - }, - - watchOptions: { - ignored: /node_modules/ - } -}; - -export default config; diff --git a/webpack/client-config.js b/webpack/client-config.js deleted file mode 100644 index 9eb642cf..00000000 --- a/webpack/client-config.js +++ /dev/null @@ -1,63 +0,0 @@ -import webpack from 'webpack'; -import ZopfliPlugin from "zopfli-webpack-plugin"; - -import configuration, { opts } from './base-config'; -import RuleGenerator from './rules'; - - -const rules = new RuleGenerator(opts.dev); - -const clientConfiguration = { - ...configuration -}; - -if (opts.dev) { - clientConfiguration.devtool = 'eval'; -} - -clientConfiguration.module.rules = [ - ...configuration.module.rules, - rules.clientJs, - ...rules.clientLibFixes, - rules.htmlForDevRender -]; - -let additionalPlugins = [ - new webpack.IgnorePlugin(/^ioredis$/) -]; - -if (opts.dev === false) { - additionalPlugins = [ - ...additionalPlugins, - new webpack.EnvironmentPlugin([ - 'API_HOST', 'NODE_ENV', 'MAPBOX_ACCESS_TOKEN', 'GOOGLE_ANALYTICS_ID', 'GOOGLE_TAG_MANAGER_ID' - ]), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, // Suppress uglification warnings - pure_getters: true, - unsafe: true, - unsafe_comps: true, - screw_ie8: true - }, - sourceMap: false, - output: { - comments: false, - }, - exclude: [/\.min\.js$/gi] // skip pre-minified libs - }), - new webpack.optimize.AggressiveMergingPlugin({ minSizeReduce: true }), - new ZopfliPlugin({ - asset: "[path].gz[query]", - algorithm: "zopfli", - minRatio: 0.8 - }) - ]; -} - -clientConfiguration.plugins = [ - ...configuration.plugins, - ...additionalPlugins -]; - -export default clientConfiguration; diff --git a/webpack/cssmoduleloader.js b/webpack/cssmoduleloader.js deleted file mode 100644 index 47ec6110..00000000 --- a/webpack/cssmoduleloader.js +++ /dev/null @@ -1,15 +0,0 @@ -import path from 'path'; - -import loaderUtils from 'loader-utils'; - - -export default function myCssModuleLoader(content) { - const config = loaderUtils.getOptions(this); - - const imports = config.files.map(((el) => { - const request = path.relative(this.resource, el).replace("../", ""); - return `@import '${request}';${String.fromCharCode(13)}`; - })); - - return `${imports.join('')}${content}`; -} diff --git a/webpack/rules.js b/webpack/rules.js deleted file mode 100644 index d37be2cd..00000000 --- a/webpack/rules.js +++ /dev/null @@ -1,343 +0,0 @@ -import path from 'path'; - -import ExtractTextPlugin from 'extract-text-webpack-plugin'; - -import { skipFalsy } from './utils'; - - -class RuleGenerator { - dev; - root; - - constructor(dev) { - this.dev = dev; - - const pieces = __dirname.split('/'); - const { length } = pieces; - - if (pieces[length - 1] === 'server' && pieces[length - 2] === 'public') { - this.root = path.join(__dirname, '..', '..'); // looks like we're inside of server bundle - } else { - this.root = path.join(__dirname, '..'); - } - } - - /** Embed font in CSS if dev */ - fontLoader = (loaderQuery) => { - const loaderObject = {}; - loaderObject.query = loaderQuery; - if (this.dev) { - loaderObject.loader = 'url-loader'; - loaderObject.query.limit = 150000; - } else { - loaderObject.loader = 'file-loader'; - } - return loaderObject; - }; - - - get commmonLess() { - const loaders = [ - { - loader: 'css-loader', - options: { - autoprefixer: false, - calc: false, - mergeIdents: false, - mergeRules: false, - uniqueSelectors: false - } - }, - { - loader: 'postcss-loader' - }, - { - loader: "less-loader" - } - ]; - return { - use: ExtractTextPlugin.extract({ - fallback: "style-loader", - use: loaders.map(this.addSourceMap) - }), - resource: { - and: [ - { test: /\.less$/ }, - { include: path.join(this.root, 'src', 'less') }, - ] - } - }; - } - - get externalCss() { - const loaders = [ - { - loader: 'css-loader', - options: { - autoprefixer: false, - calc: false, - mergeIdents: false, - mergeRules: false, - uniqueSelectors: false - } - }, - { - loader: 'postcss-loader' - } - ]; - - return { - test: /\.css$/, - use: ExtractTextPlugin.extract({ - fallback: "style-loader", - use: loaders.map(this.addSourceMap) - }) - }; - } - - get componentsLess() { - const loaders = [ - { - loader: 'css-loader', - options: { - autoprefixer: false, - modules: true, - importLoaders: true, - localIdentName: '[folder]__[local]___[hash:base64:5]', - calc: false, - mergeIdents: false - } - }, - { - loader: 'postcss-loader' - }, - { - loader: "less-loader" - }, - { - loader: "mycssmoduleloader", - options: { - files: [ - path.join(this.root, 'src', '_assets', 'less', 'vars.less'), - path.join(this.root, 'src', '_assets', 'less', 'extend.less'), - // path.join(this.root, 'src', '_assets', 'less', 'plugins', 'ico-font.less') - ] - } - } - ]; - - return { - use: ExtractTextPlugin.extract({ - fallback: "style-loader", - use: loaders.map(this.addSourceMap) - }), - resource: { - and: [ - { test: /\.less$/ }, - { exclude: path.join(this.root, 'src', 'less') }, - ] - } - }; - } - - get images() { - return { - test: /\.(png|jpg|gif|svg)(\?v=\d+\.\d+\.\d+)?$/, - use: [ - { - loader: 'file-loader', - options: { - name: 'assets/images/[name]-[hash].[ext]' - }, - } - ], - exclude: /node_modules|fonts/ - }; - } - - get favicons() { - return { - test: /\.ico$/, - use: [ - { - loader: 'file-loader', - options: { - name: '[name]-[hash].[ext]' - } - } - ] - }; - } - - get fonts() { - const outputName = 'assets/fonts/[name]-[hash].[ext]'; - return [ - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - use: [ - this.fontLoader({ - mimetype: 'image/svg+xml', - name: outputName - }) - ], - include: /node_modules\/font-awesome/ - }, - { - test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, - ...this.fontLoader({ - mimetype: 'application/font-woff', - name: outputName - }) - }, - { - test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, - ...this.fontLoader({ - mimetype: 'application/font-woff2', - name: outputName - }) - }, - { - test: /\.otf(\?v=\d+\.\d+\.\d+)?$/, - ...this.fontLoader( - { - mimetype: 'application/x-font-opentype', - name: outputName - } - ) - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - ...this.fontLoader({ - mimetype: 'application/x-font-truetype', - name: outputName - }) - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - ...this.fontLoader({ - mimetype: 'application/vnd.ms-fontobject', - name: outputName - }) - } - ]; - } - - get ejsIndexTemplate() { - return { - test: /\/index\.ejs$/, - use: 'raw-loader' - }; - } - - get clientJs() { - return { - test: /\.js?$/, - exclude: /(node_modules)/, - use: [ - { - loader: 'babel-loader', - options: { - ignore: /(node_modules)/, - presets: ["react", ["es2015", { "modules": false }], "stage-1"], - plugins: skipFalsy([ - ["syntax-do-expressions"], - ["transform-do-expressions"], - ["lodash"], - this.dev && ['transform-runtime'], - /** - * Get rid of active usage of PropTypes indicating the props - * which components exactly know and handle in a special way - * (i.e. not only for type-checking purpose) before - * activation of the plugin: - * - * `!this.dev && ['transform-react-remove-prop-types'],` - */ - !this.dev && ["transform-react-constant-elements"], - !this.dev && ["transform-react-inline-elements"], - /* - this.dev && ["flow-runtime", { - "annotate": false, - "assert": true, - "warn": true - }], - */ - ]) - }, - } - ], - include: [ - path.join(this.root, 'src'), - ] - }; - } - - get clientLibFixes() { - return [ - // Fix grapheme-breaker - { - enforce: 'post', - test: /\.js$/, - include: /node_modules\/grapheme-breaker/, - loader: 'transform-loader/cacheable?brfs' - }, - ]; - } - - get serverJs() { - return { - test: /\.js?$/, - exclude: /(node_modules)/, - use: [ - { - loader: 'babel-loader', - options: { - ignore: /(node_modules)/, - presets: ["react"], - plugins: skipFalsy([ - "syntax-class-properties", - "syntax-do-expressions", - "syntax-dynamic-import", - "syntax-object-rest-spread", - "transform-do-expressions", - "transform-object-rest-spread", - "transform-class-properties", - "lodash", - /* - this.dev && ["flow-runtime", { - "annotate": false, - "assert": true, - "warn": true - }], - */ - ]), - }, - } - ], - include: [ - path.join(this.root, 'server.js'), - path.join(this.root, 'tasks.js'), - path.join(this.root, 'src'), - ] - }; - } - - get htmlForDevRender() { - return { - test: /index\.html$/, - use: [ - { - loader: 'file-loader', - options: { - name: 'index.html' - } - } - ], - }; - } - - addSourceMap = (loader) => { - return this.dev ? { ...loader, options: { ...loader.options, sourceMap: true } } : loader; - }; -} - -export default RuleGenerator; diff --git a/webpack/server-config.js b/webpack/server-config.js deleted file mode 100644 index d468e0a6..00000000 --- a/webpack/server-config.js +++ /dev/null @@ -1,15 +0,0 @@ -import configuration, { opts } from './base-config'; -import RuleGenerator from './rules'; - - -const rules = new RuleGenerator(opts.dev); - -const serverConfiguration = { ...configuration }; - -serverConfiguration.module.rules = [ - ...configuration.module.rules, - rules.serverJs, - rules.ejsIndexTemplate -]; - -export default serverConfiguration; diff --git a/webpack/utils.js b/webpack/utils.js deleted file mode 100644 index c2562ec2..00000000 --- a/webpack/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -export function strToBool(val, def) { - if (val === undefined) { - return def; - } - val = String(val).toLowerCase(); - return val === '1' || val === 'true' || val === 'yes' || val === 'y'; -} - -export function skipFalsy(array) { - return array.filter(Boolean); -} diff --git a/yarn.lock b/yarn.lock index 7d45631d..dbc537fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31,6 +31,13 @@ accepts@^1.2.2, accepts@~1.3.3: mime-types "~2.1.11" negotiator "0.6.1" +accepts@~1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + dependencies: + mime-types "~2.1.16" + negotiator "0.6.1" + acorn-dynamic-import@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" @@ -133,6 +140,10 @@ ansi-escapes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" +ansi-regex@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -358,6 +369,14 @@ assert@^1.1.1: dependencies: util "0.10.3" +asset-require-hook@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/asset-require-hook/-/asset-require-hook-1.2.0.tgz#dd4b8f7c187774223ec31e30bd93aa875d174c6e" + dependencies: + loader-utils "^0.2.12" + lodash.assign "^4.0.0" + mime "^1.3.4" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -1397,6 +1416,21 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.7" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + body-parser@^1.12.2: version "1.17.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" @@ -1594,10 +1628,6 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" -builtins@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-0.0.7.tgz#355219cd6cf18dbe7c01cc7fd2dce765cfdc549a" - bunyan@~1.8.12: version "1.8.12" resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797" @@ -2046,6 +2076,10 @@ content-type@^1.0.0, content-type@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -2390,7 +2424,7 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@^2.6.1, debug@~2.6.3: +debug@2.6.9, debug@^2.6.1, debug@~2.6.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -2646,7 +2680,7 @@ duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" -duplexer@~0.1.1: +duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2670,7 +2704,7 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -ejs@~2.5.7: +ejs@^2.5.6, ejs@~2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" @@ -3100,6 +3134,10 @@ etag@^1.3.0, etag@~1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + event-emitter@^0.3.5, event-emitter@~0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" @@ -3202,6 +3240,41 @@ express@^4.12.2: utils-merge "1.0.0" vary "~1.1.1" +express@^4.15.2: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" + dependencies: + accepts "~1.3.4" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.1" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.0" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.2" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.1" + serve-static "1.13.1" + setprototypeof "1.1.0" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.1" + vary "~1.1.2" + extend@3, extend@^3.0.0, extend@^3.0.1, extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -3347,6 +3420,10 @@ fileset@^2.0.2: glob "^7.0.3" minimatch "^3.0.3" +filesize@^3.5.9: + version "3.5.11" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" + fill-range@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -3357,6 +3434,18 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + finalhandler@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" @@ -3546,11 +3635,15 @@ forwarded@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + fresh@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" -fresh@^0.5.0: +fresh@0.5.2, fresh@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -4035,6 +4128,12 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" +gzip-size@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520" + dependencies: + duplexer "^0.1.1" + handlebars@^4.0.3: version "4.0.10" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" @@ -4220,7 +4319,7 @@ http-assert@^1.1.0: deep-equal "~1.0.1" http-errors "~1.6.1" -http-errors@1.6.2: +http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: @@ -4407,6 +4506,10 @@ ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" +ipaddr.js@1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" + is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" @@ -5151,6 +5254,12 @@ koa-static-cache@~5.1.1: mime-types "~2.1.8" mz "~2.6.0" +koa-webpack-dev-middleware@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/koa-webpack-dev-middleware/-/koa-webpack-dev-middleware-2.0.2.tgz#bab6207198beb6344a0688bf9bd15cfaafa89d91" + dependencies: + webpack-dev-middleware "^1.10.0" + koa@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/koa/-/koa-2.3.0.tgz#9e1e8e4da401839c57b8527eadc57f76127555a7" @@ -5311,7 +5420,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.14: +loader-utils@^0.2.12, loader-utils@^0.2.14: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" dependencies: @@ -5406,7 +5515,7 @@ lodash.assign@^3.0.0: lodash._createassigner "^3.0.0" lodash.keys "^3.0.0" -lodash.assign@^4.0.3, lodash.assign@^4.0.6: +lodash.assign@^4.0.0, lodash.assign@^4.0.3, lodash.assign@^4.0.6: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -5528,14 +5637,14 @@ lodash@3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.0.tgz#93d51c672828a4416a12af57220ba8a8737e2fbb" +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.0, lodash@^4.6.1, lodash@~4.17.0, lodash@~4.17.4: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + "lodash@^3.0.1 || ^2.0.0": version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.0, lodash@^4.6.1, lodash@~4.17.0, lodash@~4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -5787,12 +5896,22 @@ mime-db@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + mime-types@^2.0.7, mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7, mime-types@~2.1.8: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" dependencies: mime-db "~1.27.0" +mime-types@~2.1.16: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + mime@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -5801,6 +5920,10 @@ mime@1.3.x, mime@^1.2.11, mime@^1.2.9: version "1.3.6" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" +mime@1.4.1, mime@^1.3.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + mime@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.0.2.tgz#097fd2c88c652eae48b2702d7cbf54c08d8ef50a" @@ -6391,6 +6514,10 @@ only@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" +opener@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" + optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -6563,6 +6690,10 @@ parseurl@^1.3.0, parseurl@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + passerror@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/passerror/-/passerror-1.1.0.tgz#d72a29e30bc48a682b59d79b19c99c8da52307da" @@ -7184,6 +7315,13 @@ proxy-addr@~1.1.4: forwarded "~0.1.0" ipaddr.js "1.3.0" +proxy-addr@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.5.2" + proxy-middleware@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/proxy-middleware/-/proxy-middleware-0.15.0.tgz#a3fdf1befb730f951965872ac2f6074c61477a56" @@ -7334,14 +7472,14 @@ qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@6.5.1, qs@^6.4.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + qs@^2.3.3: version "2.4.2" resolved "https://registry.yarnpkg.com/qs/-/qs-2.4.2.tgz#f7ce788e5777df0b5010da7f7c4e73ba32470f5a" -qs@^6.4.0: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -7357,7 +7495,7 @@ querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" -querystring@0.2.0: +querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -7416,11 +7554,11 @@ randombytes@^2.0.0, randombytes@^2.0.1: dependencies: safe-buffer "^5.1.0" -range-parser@~1.2.0: +range-parser@^1.0.3, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raw-body@^2.2.0: +raw-body@2.3.2, raw-body@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" dependencies: @@ -7491,6 +7629,10 @@ react-deep-force-update@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.0.1.tgz#f911b5be1d2a6fe387507dd6e9a767aa2924b4c7" +react-deep-force-update@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909" + react-dom@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" @@ -7517,6 +7659,16 @@ react-helmet@~5.2.0: prop-types "^15.5.4" react-side-effect "^1.1.0" +react-hot-loader@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-3.1.1.tgz#e06db8cd0841c41e3ab0b395b2b774126fc8914e" + dependencies: + global "^4.3.0" + react-deep-force-update "^2.1.1" + react-proxy "^3.0.0-alpha.0" + redbox-react "^1.3.6" + source-map "^0.6.1" + react-icon-base@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d" @@ -7556,6 +7708,12 @@ react-proxy@^1.1.7: lodash "^4.6.1" react-deep-force-update "^1.0.0" +react-proxy@^3.0.0-alpha.0: + version "3.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz#4400426bcfa80caa6724c7755695315209fa4b07" + dependencies: + lodash "^4.6.1" + react-redux@~5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" @@ -7744,6 +7902,15 @@ redbox-react@^1.2.2: prop-types "^15.5.4" sourcemapped-stacktrace "^1.1.6" +redbox-react@^1.3.6: + version "1.5.0" + resolved "https://registry.yarnpkg.com/redbox-react/-/redbox-react-1.5.0.tgz#04dab11557d26651bf3562a67c22ace56c5d3967" + dependencies: + error-stack-parser "^1.3.6" + object-assign "^4.0.1" + prop-types "^15.5.4" + sourcemapped-stacktrace "^1.1.6" + redeyed@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a" @@ -8107,10 +8274,14 @@ rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + safe-json-stringify@~1: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911" @@ -8215,6 +8386,24 @@ send@0.15.3: range-parser "~1.2.0" statuses "~1.3.1" +send@0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" + dependencies: + debug "2.6.9" + depd "~1.1.1" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + sendgrid@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sendgrid/-/sendgrid-2.0.0.tgz#e24de0b43479e0425fb6f27374f774cbc66ba190" @@ -8241,6 +8430,15 @@ serve-static@1.12.3: parseurl "~1.3.1" send "0.15.3" +serve-static@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.1" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -8257,6 +8455,10 @@ setprototypeof@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.8" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" @@ -8375,12 +8577,24 @@ source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" +source-map-support@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.0.tgz#cb92292bc05455ce48691de545ac2690bb1cc976" + dependencies: + source-map "0.1.32" + source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: source-map "^0.5.6" +source-map@0.1.32: + version "0.1.32" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" + dependencies: + amdefine ">=0.0.4" + source-map@0.1.x, source-map@~0.1.33: version "0.1.43" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" @@ -8401,6 +8615,10 @@ source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + source-map@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" @@ -8603,6 +8821,12 @@ stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" +strip-ansi@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-2.0.1.tgz#df62c1aa94ed2f114e1d0f21fd1d50482b79a60e" + dependencies: + ansi-regex "^1.0.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -8859,6 +9083,10 @@ time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -9068,6 +9296,10 @@ uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" +ultron@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" + unc-path-regex@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -9219,15 +9451,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -universal-webpack@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/universal-webpack/-/universal-webpack-0.4.0.tgz#8023c57537083703621dbd46067c501c55c1a84d" - dependencies: - colors "^1.1.2" - fs-extra "^0.30.0" - minimist "^1.2.0" - validate-npm-package-name "^2.2.2" - universalify@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.0.tgz#9eb1c4651debcc670cc94f1a75762332bb967778" @@ -9324,7 +9547,7 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" -utils-merge@1.x.x: +utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -9353,16 +9576,14 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -validate-npm-package-name@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-2.2.2.tgz#f65695b22f7324442019a3c7fa39a6e7fd299085" - dependencies: - builtins "0.0.7" - vary@^1.0.0, vary@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + vendors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" @@ -9471,6 +9692,57 @@ webidl-conversions@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" +webpack-bundle-analyzer@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.9.0.tgz#b58bc34cc30b27ffdbaf3d00bf27aba6fa29c6e3" + dependencies: + acorn "^5.1.1" + chalk "^1.1.3" + commander "^2.9.0" + ejs "^2.5.6" + express "^4.15.2" + filesize "^3.5.9" + gzip-size "^3.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + opener "^1.4.3" + ws "^2.3.1" + +webpack-dev-middleware@^1.10.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" + dependencies: + memory-fs "~0.4.1" + mime "^1.3.4" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + time-stamp "^2.0.0" + +webpack-hot-middleware@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.6.0.tgz#95e082d783a57d25ff4285ee1ea0fc7a5a939a3a" + dependencies: + querystring "^0.2.0" + strip-ansi "^2.0.1" + +webpack-koa-hot-middleware@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/webpack-koa-hot-middleware/-/webpack-koa-hot-middleware-0.1.2.tgz#d69586f4fb01cfcc1d54ea875b280f249924b9e2" + dependencies: + source-map-support "0.4.0" + webpack-hot-middleware "2.6.0" + +webpack-manifest-plugin@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz#5ea8ee5756359ddc1d98814324fe43496349a7d4" + dependencies: + fs-extra "^0.30.0" + lodash ">=3.5 <5" + +webpack-node-externals@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.6.0.tgz#232c62ec6092b100635a3d29d83c1747128df9bd" + webpack-sources@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" @@ -9624,6 +9896,13 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +ws@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80" + dependencies: + safe-buffer "~5.0.1" + ultron "~1.1.0" + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"