You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/03/21 09:09:02 UTC

[couchdb-fauxton] branch master updated: (#868) - Update to webpack 2

This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

The following commit(s) were added to refs/heads/master by this push:
       new  eef2bf9   (#868) - Update to webpack 2
eef2bf9 is described below

commit eef2bf9de5848644211a7fb4888bbec7249e7b0c
Author: garren smith <ga...@gmail.com>
AuthorDate: Tue Mar 21 11:08:59 2017 +0200

    (#868) - Update to webpack 2
    
    This upgrades to webpack 2. Webpack 2 makes it a little easier to do certain things. One of them is to split the bundle.js into three separate files:
    
    manifest.js -> webpack loader file
    vendor.js -> all required libraries
    bundle.js -> Fauxton code
    Each file also has a a hash applied to it based on the file contents. This should improve caching and load times for Fauxton as the manifest file and vendor file will not change as often as the bundle file.
    
    Webpack is now also generating the html file, this was previously done in grunt.
---
 Gruntfile.js                |  40 ++--------------
 assets/index.underscore     |  13 +-----
 devserver.js                |  77 +++++++++++++++++--------------
 docker/dc.selenium.yml      |   2 +-
 package.json                |  10 ++--
 settings.json.default.json  |  20 +++-----
 tasks/fauxton.js            |   8 ----
 tasks/helper.js             |  23 +++++-----
 test/test.config.underscore |   2 +-
 webpack.config.dev.js       | 103 ++++++++++++++++++++++++++++--------------
 webpack.config.release.js   | 108 +++++++++++++++++++++++++++++++-------------
 webpack.config.test-dev.js  |  86 ++++++++++++++++++++++-------------
 webpack.config.test.js      |  80 ++++++++++++++++++++------------
 13 files changed, 324 insertions(+), 248 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index 1eb6946..572823b 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -18,8 +18,6 @@
 /*jslint node: true */
 "use strict";
 
-const path = require('path');
-
 module.exports = function (grunt) {
   var helper = require('./tasks/helper.js'),
       initHelper = helper.init(grunt),
@@ -195,33 +193,7 @@ module.exports = function (grunt) {
         template: 'test/nightwatch_tests/nightwatch.json.underscore',
         dest: 'test/nightwatch_tests/nightwatch.json'
       }
-    },
-
-    // these rename the already-bundled, minified requireJS and CSS files to include their hash
-    md5: {
-      bundlejs: {
-        files: { 'dist/release/dashboard.assets/js/': 'dist/release/bundle.js' },
-        options: {
-          afterEach: function (fileChanges) {
-            // replace the REQUIREJS_FILE placeholder with the actual filename
-            const newFilename = path.basename(fileChanges.newPath);
-            config.template.release.variables.bundlejs = config.template.release.variables.bundlejs.replace(/BUNDLEJS_FILE/, newFilename);
-          }
-        }
-      },
-
-      css: {
-        files: { 'dist/release/dashboard.assets/css/': 'dist/release/styles.css' },
-        options: {
-          afterEach: function (fileChanges) {
-            // replace the CSS_FILE placeholder with the actual filename
-            const newFilename = path.basename(fileChanges.newPath);
-            config.template.release.variables.css = config.template.release.variables.css.replace(/CSS_FILE/, newFilename);
-          }
-        }
-      }
-    },
-
+    }
   };
 
   grunt.initConfig(config);
@@ -237,7 +209,6 @@ module.exports = function (grunt) {
   grunt.loadNpmTasks('grunt-contrib-copy');
   grunt.loadNpmTasks('grunt-contrib-clean');
   grunt.loadNpmTasks('grunt-shell');
-  grunt.loadNpmTasks('grunt-md5');
   /*
    * Default task
    */
@@ -255,8 +226,7 @@ module.exports = function (grunt) {
   grunt.registerTask('dependencies', ['get_deps', 'gen_load_addons:default']);
 
   // minify code and css, ready for release.
-  grunt.registerTask('build', ['copy:distDepsRequire', 'shell:webpackrelease',
-    'md5:bundlejs', 'md5:css', 'template:release']);
+  grunt.registerTask('build', ['copy:distDepsRequire', 'shell:webpackrelease']);
   /*
    * Build the app in either dev, debug, or release mode
    */
@@ -267,13 +237,13 @@ module.exports = function (grunt) {
 
   // build a debug release
   grunt.registerTask('debug', ['clean', 'dependencies', "gen_initialize:development",
-    'template:development', 'copy:debug']);
+    'copy:debug']);
 
   grunt.registerTask('debugDev', ['clean', 'dependencies', "gen_initialize:development",
-    'template:development', 'copy:debug', 'shell:webpack']);
+    'copy:debug', 'shell:webpack']);
 
   grunt.registerTask('devSetup', ['dependencies', "gen_initialize:development",
-    'template:development', 'copy:debug']);
+    'copy:debug']);
   grunt.registerTask('devSetupWithClean', ['clean', 'devSetup']);
 
   grunt.registerTask('watchRun', ['clean:watch', 'dependencies', 'shell:stylecheck']);
diff --git a/assets/index.underscore b/assets/index.underscore
index 2d97e0b..d92c6e2 100644
--- a/assets/index.underscore
+++ b/assets/index.underscore
@@ -19,11 +19,8 @@
   <meta name="viewport" content="width=device-width,initial-scale=1">
   <meta http-equiv="Content-Language" content="en" />
   <link rel="shortcut icon" type="image/png" href="dashboard.assets/img/couchdb-logo.png"/>
-  <title>Project Fauxton</title>
+  <title><%= htmlWebpackPlugin.options.title %></title>
 
-  <% if (!development) { %>
-  <link rel="stylesheet" href="<%= css %>"/>
-  <% } %>
   <!-- Application styles. -->
   <style>
     .noscript-warning {
@@ -36,9 +33,6 @@
     }
   </style>
 
-  <% if (base) { %>
-  <base href="<%= base %>"></base>
-  <% } %>
 </head>
 
 <body id="home">
@@ -53,9 +47,6 @@
 
   <div id="app"></div>
 
-  <!-- Application source. -->
-  <script src="<%= bundlejs %>" type="text/javascript"></script>
-  <!-- <%= generationLabel %><%= generationDate %> -->
-
+ <!-- <%= htmlWebpackPlugin.options.generationLabel %> : <%= htmlWebpackPlugin.options.generationDate %> -->
 </body>
 </html>
diff --git a/devserver.js b/devserver.js
index c8d0ffc..dd8f328 100644
--- a/devserver.js
+++ b/devserver.js
@@ -1,13 +1,14 @@
-var spawn = require('child_process').spawn;
-var fs = require("fs");
-var webpack = require('webpack');
-var WebpackDev = require('webpack-dev-server');
-var config = require('./webpack.config.dev.js');
-var httpProxy = require('http-proxy');
+const spawn = require('child_process').spawn;
+const fs = require("fs");
+const webpack = require('webpack');
+const WebpackDev = require('webpack-dev-server');
+const config = require('./webpack.config.dev.js');
+const httpProxy = require('http-proxy');
+const path = require('path');
 
 
-var loadSettings = function () {
-  var fileName = './settings.json.default.json';
+const loadSettings = function () {
+  let fileName = './settings.json.default.json';
   if (fs.existsSync('./settings.json')) {
     fileName = './settings.json';
   }
@@ -22,16 +23,16 @@ var loadSettings = function () {
   };
 };
 
-var settings = loadSettings();
+const settings = loadSettings();
 
-var devSetup = function (cb) {
+const devSetup = function (cb) {
   console.log('setup dev environment');
-  var cmd = 'devSetupWithClean';
+  const cmd = 'devSetupWithClean';
   if (settings.noClean) {
     cmd = 'devSetup';
   }
 
-  var grunt = spawn('grunt', [cmd]);
+  const grunt = spawn('grunt', [cmd]);
 
   grunt.stdout.on('data', (data) => {
     console.log(data.toString());
@@ -63,26 +64,8 @@ function getCspHeaders () {
   };
 };
 
-var runWebpackServer = function () {
-  var options = {
-    contentBase: __dirname + '/dist/debug',
-    publicPath: '/',
-    outputPath: '/',
-    filename: 'bundle.js',
-    host: 'localhost',
-    port: process.env.FAUXTON_PORT || 8000,
-    hot: false,
-    historyApiFallback: true,
-    stats: {
-      colors: true,
-    },
-    headers: getCspHeaders(),
-  };
-
-  var compiler = webpack(config);
-
-  var server = new WebpackDev(compiler, options);
-  var proxy = httpProxy.createServer({
+const runWebpackServer = function () {
+  const proxy = httpProxy.createServer({
     secure: false,
     changeOrigin: true,
     target: settings.proxy.target
@@ -98,9 +81,33 @@ var runWebpackServer = function () {
     // don't explode on cancelled requests
   });
 
-  server.app.all('*', function (req, res) {
-    proxy.web(req, res);
-  });
+  const options = {
+    contentBase: path.join(__dirname, '/dist/debug/'),
+    host: 'localhost',
+    port: process.env.FAUXTON_PORT || 8000,
+    overlay: true,
+    hot: false,
+    historyApiFallback: false,
+    stats: {
+      colors: true,
+    },
+    headers: getCspHeaders(),
+    setup: (app) => {
+      app.all('*', (req, res, next) => {
+        const accept = req.headers.accept ? req.headers.accept.split(',') : '';
+
+        if (/application\/json/.test(accept[0])) {
+          proxy.web(req, res);
+          return;
+        }
+
+        next();
+      });
+    }
+  };
+
+  const compiler = webpack(config);
+  const server = new WebpackDev(compiler, options);
 
   server.listen(options.port, '0.0.0.0', function (err) {
     if (err) {
diff --git a/docker/dc.selenium.yml b/docker/dc.selenium.yml
index 5d1c385..f65f284 100644
--- a/docker/dc.selenium.yml
+++ b/docker/dc.selenium.yml
@@ -8,7 +8,7 @@ services:
 
   couchdb:
     container_name: couchdb
-    image: klaemo/couchdb:2.0-dev@sha256:e9b71abaff6aeaa34ee28604c3aeb78f3a7c789ad74a7b88148e2ef78f1e3b21
+    image: klaemo/couchdb:2.0-dev
     command: '--with-haproxy -a tester:testerpass'
     ports:
       - "5984:5984"
\ No newline at end of file
diff --git a/package.json b/package.json
index dabe4dd..ff101cf 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
     "bootstrap": "^3.3.7",
     "enzyme": "^2.7.1",
     "es5-shim": "4.5.4",
-    "extract-text-webpack-plugin": "^1.0.1",
+    "extract-text-webpack-plugin": "~2.1.0",
     "fetch-mock": "^5.9.3",
     "jest": "^18.1.0",
     "less": "^2.7.2",
@@ -74,8 +74,8 @@
     "grunt-contrib-copy": "~1.0.0",
     "grunt-couchapp": "~0.2.1",
     "grunt-exec": "~1.0.1",
-    "grunt-md5": "^0.1.11",
     "grunt-shell": "^2.0.1",
+    "html-webpack-plugin": "^2.28.0",
     "http-proxy": "^1.16.0",
     "imports-loader": "^0.7.0",
     "jquery": "^2.2.0",
@@ -107,15 +107,15 @@
     "velocity-animate": "^1.4.2",
     "velocity-react": "1.2.0",
     "visualizeRevTree": "git+https://github.com/neojski/visualizeRevTree.git#gh-pages",
-    "webpack": "^1.12.12",
-    "webpack-dev-server": "^1.14.1",
+    "webpack": "~2.2.1",
+    "webpack-dev-server": "~2.4.1",
     "whatwg-fetch": "~2.0.1"
   },
   "scripts": {
     "stylecheck": "eslint --ext=js,jsx .",
     "webpack:dev": "webpack --debug --progress --colors --config ./webpack.config.dev.js",
     "webpack:test": "webpack --debug --progress --colors --config ./webpack.config.test.js",
-    "webpack:release": "webpack --debug --progress --colors --config ./webpack.config.release.js",
+    "webpack:release": "webpack --optimize-minimize --debug --progress --colors --config ./webpack.config.release.js",
     "jest": "jest --config ./jest-config.json",
     "test": "grunt test && npm run jest",
     "phantomjs": "./node_modules/.bin/mocha-phantomjs --debug=false --ssl-protocol=sslv2 --web-security=false --ignore-ssl-errors=true ./test/runner.html",
diff --git a/settings.json.default.json b/settings.json.default.json
index 510f7f2..fde5dad 100644
--- a/settings.json.default.json
+++ b/settings.json.default.json
@@ -17,13 +17,11 @@
   ],
     "template": {
       "development": {
-        "src": "assets/index.underscore",
+        "src": "./assets/index.underscore",
         "dest": "dist/debug/index.html",
         "variables": {
-          "bundlejs": "/bundle.js",
-          "css": "./dashboard.assets/css/index.css",
-          "base": null,
-          "development": true
+          "title": "Project Fauxton",
+          "generationLabel": "Fauxton Dev"
         },
         "app": {
           "root": "/",
@@ -35,10 +33,8 @@
         "src": "assets/index.underscore",
         "dest": "dist/debug/index.html",
         "variables": {
-          "bundlejs": "./dashboard.assets/js/BUNDLEJS_FILE",
-          "css": "./dashboard.assets/css/CSS_FILE",
-          "base": null,
-          "development": false
+          "title": "Project Fauxton",
+          "generationLabel": "Fauxton Release"
         },
         "app": {
           "root": "/",
@@ -50,10 +46,8 @@
         "src": "assets/index.underscore",
         "dest": "dist/debug/index.html",
         "variables": {
-          "bundlejs": "./js/BUNDLEJS_FILE",
-          "css": "./css/CSS_FILE",
-          "base": null,
-          "development": false
+          "title": "Project Fauxton",
+          "generationLabel": "Fauxton Couchapp"
         },
         "app": {
           "root": "/",
diff --git a/tasks/fauxton.js b/tasks/fauxton.js
index 3fbad30..e4c2f88 100644
--- a/tasks/fauxton.js
+++ b/tasks/fauxton.js
@@ -15,14 +15,6 @@ module.exports = function (grunt) {
       fs = require('fs'),
       os = require('os');
 
-  grunt.registerMultiTask('template', 'generates an html file from a specified template', function () {
-    var data = this.data,
-        _ = grunt.util._,
-        tmpl = _.template(grunt.file.read(data.src), null, data.variables);
-
-    grunt.file.write(data.dest, tmpl(data.variables));
-  });
-
   grunt.registerMultiTask('get_deps', 'Fetch external dependencies', function () {
 
     grunt.log.writeln('Fetching external dependencies');
diff --git a/tasks/helper.js b/tasks/helper.js
index 70d4e12..d880ae5 100644
--- a/tasks/helper.js
+++ b/tasks/helper.js
@@ -16,26 +16,25 @@ var fs = require('fs'),
 exports.devServerPort = 8000;
 exports.couch = 'http://couch:5984/';
 
-exports.init = function (grunt) {
-  var _ = grunt.util._;
+exports.init = function () {
 
   return {
     readSettingsFile: function () {
-      if (fs.existsSync("settings.json")) {
-        return grunt.file.readJSON("settings.json");
-      } else if (fs.existsSync('settings.json.default.json')) {
-        return grunt.file.readJSON('settings.json.default.json');
+      if (fs.existsSync(path.join(__dirname, "../settings.json"))) {
+        return require(path.join(__dirname, "../settings.json"));
+      } else if (fs.existsSync(path.join(__dirname, '../settings.json.default.json'))) {
+        return require(path.join(__dirname, '../settings.json.default.json'));
       }
 
       throw new Error('settings.json file missing');
     },
 
     readI18nFile: function () {
-      if (fs.existsSync('i18n.json')) {
-        return grunt.file.readJSON('i18n.json');
+      if (fs.existsSync(path.join(__dirname, '../i18n.json'))) {
+        return require(path.join(__dirname, '../i18n.json'));
       }
-      if (fs.existsSync('i18n.json.default.json')) {
-        return grunt.file.readJSON('i18n.json.default.json');
+      if (fs.existsSync(path.join(__dirname, '../i18n.json.default.json'))) {
+        return require(path.join(__dirname, '../i18n.json.default.json'));
       }
 
       throw new Error('i18n file missing');
@@ -46,9 +45,9 @@ exports.init = function (grunt) {
     },
 
     getFileList: function (fileExtensions, defaults) {
-      return _.reduce(this.readSettingsFile().deps, function (files, dep) {
+      return this.readSettingsFile().deps.reduce((files, dep) => {
         if (dep.path) {
-          _.each(fileExtensions, function (fileExtension) {
+          fileExtensions.forEach(fileExtension => {
             files.push(path.join(dep.path, '**/*' + fileExtension));
           });
         }
diff --git a/test/test.config.underscore b/test/test.config.underscore
index 32a16a7..501c6f5 100644
--- a/test/test.config.underscore
+++ b/test/test.config.underscore
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 //
-
+var Promise = require('bluebird');
 require([
   "url-polyfill",
   <% _.each(testFiles, function (test) {%>
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index ac734db..89d3dbd 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -10,69 +10,102 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 const webpack = require('webpack');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const path = require('path');
+const settings = require('./tasks/helper')
+                    .init()
+                    .readSettingsFile()
+                    .template
+                    .development;
 
 module.exports = {
-  entry: [
-    './app/main.js' //Our starting point for our development.
-  ],
+  entry: {
+    bundle: './app/main.js' //Our starting point for our development.
+  },
+
+  output: {
+    path: path.join(__dirname, '/dist/debug/'),
+    filename: 'dashboard.assets/js/[name].js'
+  },
+
   plugins: [
-    new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1})
+    new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}),
+    new HtmlWebpackPlugin(Object.assign({
+      template: settings.src,
+      title: 'Project Fauxton',
+      filename: 'index.html',
+      generationLabel: 'Fauxton Dev',
+      generationDate: new Date().toISOString()
+    }, settings.variables)),
   ],
   module: {
-    preLoaders: [
-      {
-        test: /\.jsx?$/,
-        loaders: ['eslint'],
-        exclude: /node_modules/
-      }
-    ],
-    loaders: [
+    rules: [
+    {
+      test: /\.jsx?$/,
+      enforce: "pre",
+      use: ['eslint-loader'],
+      exclude: /node_modules/
+    },
     {
       test: /\.jsx?$/,
       exclude: /node_modules/,
-      //loader: 'react-hot!babel'
-      loader: 'babel'
+      use: 'babel-loader'
     },
-    { test: require.resolve("jquery"),
-      loader: "expose?$!expose?jQuery"
+    {
+     test: require.resolve('jquery'),
+      use: [{
+          loader: 'expose-loader',
+          options: 'jQuery'
+      },
+      {
+          loader: 'expose-loader',
+          options: '$'
+      }]
      },
-    { test: require.resolve("backbone"),
-      loader: "expose?Backbone"
+     {
+      test: require.resolve("backbone"),
+      use: [{
+          loader: 'expose-loader',
+          options: 'Backbone'
+      }]
     },
     {
       test: /\.less$/,
-      loader: 'style!css!less'
+      use: [
+        "style-loader",
+        "css-loader",
+        "less-loader"
+      ]
+    },
+    {
+      test: /\.css$/,
+      use: [
+        "style-loader",
+        "css-loader"
+      ]
     },
-    { test: /\.css$/, loader: 'style!css' },
     {
       test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
-      loader: 'url?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
+      loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.woff2(\?\S*)?$/,   loader: 'url?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.woff2(\?\S*)?$/,   loader: 'url-loader?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
     },
-    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/fonts/[name].[ext]' },
-    { test: /\.swf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/[name].[ext]' },
-    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
+    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/fonts/[name].[ext]' },
+    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
   ]
   },
   resolve: {
-    extensions: ['', '.js', '.jsx'], //We can use .js and React's .jsx files using Babel
+    extensions: ['*', '.js', '.jsx'], //We can use .js and React's .jsx files using Babel
     alias: {
       "bootstrap": "../assets/js/libs/bootstrap",
       "underscore": "lodash",
     }
   },
-  output: {
-    path: __dirname + '/dist/debug',
-    publicPath: '/',
-    filename: 'bundle.js' //All our code is compiled into a single file called bundle.js
-  },
-
   devtool: 'source-map'
 };
diff --git a/webpack.config.release.js b/webpack.config.release.js
index 0b71897..99316c9 100644
--- a/webpack.config.release.js
+++ b/webpack.config.release.js
@@ -11,17 +11,23 @@
 // the License.
 var webpack = require('webpack');
 var ExtractTextPlugin = require("extract-text-webpack-plugin");
+var HtmlWebpackPlugin = require('html-webpack-plugin');
 var path = require('path');
+const settings = require('./tasks/helper')
+                    .init()
+                    .readSettingsFile()
+                    .template
+                    .release;
 
 module.exports = {
   // Entry point for static analyzer:
-  entry: [
-    './app/main.js'
-  ],
+  entry: {
+    bundle: './app/main.js'
+  },
 
   output: {
-    path: path.join(__dirname, '/dist/release'),
-    filename: 'bundle.js'
+    path: path.join(__dirname, '/dist/release/'),
+    filename: 'dashboard.assets/js/[name].[chunkhash].js'
   },
 
   plugins: [
@@ -34,13 +40,31 @@ module.exports = {
     // moment doesn't offer a modular API, so manually remove locale
     // see https://github.com/moment/moment/issues/2373
     new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
-    new webpack.optimize.DedupePlugin(),
     new webpack.optimize.UglifyJsPlugin({
       compress: {
         warnings: false
+      },
+      sourceMap: true
+    }),
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor', // Specify the common bundle's name.
+      minChunks: function (module) {
+        // this assumes your vendor imports exist in the node_modules directory
+        return module.context && module.context.indexOf('node_modules') !== -1;
       }
     }),
-    new ExtractTextPlugin("styles.css")
+    new webpack.optimize.CommonsChunkPlugin({
+      name: "manifest",
+      minChunks: Infinity
+    }),
+    new HtmlWebpackPlugin(Object.assign({
+      template: settings.src,
+      title: 'Project Fauxton',
+      filename: 'index.html',
+      generationLabel: 'Fauxton Release',
+      generationDate: new Date().toISOString()
+    }, settings.variables)),
+    new ExtractTextPlugin("dashboard.assets/css/styles.[chunkhash].css"),
   ],
 
   resolve: {
@@ -49,51 +73,71 @@ module.exports = {
   },
 
   module: {
-    preLoaders: [
-      {
-        test: /\.jsx?$/,
-        loaders: ['eslint'],
-        exclude: /node_modules/
-      }
-    ],
     loaders: [
     {
       test: /\.jsx?$/,
+      enforce: "pre",
+      use: ['eslint-loader'],
+      exclude: /node_modules/
+    },
+    {
+      test: /\.jsx?$/,
       exclude: /node_modules/,
-      //loader: 'react-hot!babel'
-      loader: 'babel'
+      use: 'babel-loader'
     },
-    { test: require.resolve("jquery"),
-      loader: "expose?$!expose?jQuery"
+    {
+      test: require.resolve('jquery'),
+      use: [{
+          loader: 'expose-loader',
+          options: 'jQuery'
+      },
+      {
+          loader: 'expose-loader',
+          options: '$'
+      }]
      },
-    { test: require.resolve("backbone"),
-      loader: "expose?Backbone"
+     {
+      test: require.resolve("backbone"),
+      use: [{
+          loader: 'expose-loader',
+          options: 'Backbone'
+      }]
     },
-    { test: /\.less/,
-      loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader", {
+    {
+      test: /\.less/,
+      use: ExtractTextPlugin.extract({
+        fallback: "style-loader",
+        use: [
+          "css-loader",
+          "less-loader"
+        ],
         publicPath: '../../'
       }),
     },
-    { test: /\.css$/, loader: 'style!css' },
+    {
+      test: /\.css$/, use: [
+        'style-loader',
+        'css-loader'
+        ]
+    },
     {
       test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
-      loader: 'url?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
+      loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.woff2(\?\S*)?$/,   loader: 'url?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.woff2(\?\S*)?$/,   loader: 'url-loader?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
     },
-    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/fonts/[name].[ext]' },
-    { test: /\.swf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/[name].[ext]' },
-    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
+    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/fonts/[name].[ext]' },
+    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
     ]
   },
   resolve: {
-    extensions: ['', '.js', '.jsx'],
+    extensions: ['*', '.js', '.jsx'],
     alias: {
       "underscore": "lodash",
       "bootstrap": "../assets/js/libs/bootstrap",
diff --git a/webpack.config.test-dev.js b/webpack.config.test-dev.js
index e24fbd1..cdbfefd 100644
--- a/webpack.config.test-dev.js
+++ b/webpack.config.test-dev.js
@@ -13,60 +13,84 @@ const webpack = require('webpack');
 
 module.exports = {
   entry: [
-    'mocha!./test/dev.js', //Our starting point for our testing.
+    'mocha-loader!./test/dev.js', //Our starting point for our testing.
   ],
   module: {
-    preLoaders: [
-      {
-        test: /\.jsx?$/,
-        loaders: ['eslint'],
-        exclude: /node_modules/
-      }
-    ],
-    loaders: [
+    rules: [
+    {
+      test: /\.jsx?$/,
+      enforce: "pre",
+      use: ['eslint-loader'],
+      exclude: /node_modules/
+    },
     {
       test: /\.jsx?$/,
       exclude: /node_modules/,
-      //loader: 'react-hot!babel'
-      loader: 'babel'
+      use: 'babel-loader'
     },
-    { test: require.resolve("jquery"),
-      loader: "expose?$!expose?jQuery"
-     },
-    { test: require.resolve("sinon"),
-      loader: "expose?sinon"
-     },
-    { test: require.resolve("backbone"),
-      loader: "expose?Backbone"
+    {
+      test: require.resolve("jquery"),
+      use: [
+      {
+        loader: 'expose-loader',
+        options: 'jQuery'
+      },
+      {
+        loader: 'expose-loader',
+        options: '$'
+      }]
+    },
+    {
+      test: require.resolve("backbone"),
+      use: [{
+        loader: 'expose-loader',
+        options: 'Backbone'
+      }]
+    },
+    {
+      test: require.resolve("sinon"),
+      use: [{
+        loader: 'expose-loader',
+        options: 'sinon'
+      }]
     },
     {
       test: require.resolve("react"),
-      loader: "imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham"
+      loader: "imports-loader?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham"
     },
     {
       test: /\.less$/,
-      loader: 'style!css!less'
+      use: [
+        "style-loader",
+        "css-loader",
+        "less-loader"
+      ]
+    },
+    {
+      test: /\.css$/,
+      use: [
+        "style-loader",
+        "css-loader"
+      ]
     },
-    { test: /\.css$/, loader: 'style!css' },
     {
       test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
-      loader: 'url?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
+      loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.woff2(\?\S*)?$/,   loader: 'url?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.woff2(\?\S*)?$/,   loader: 'url-loader?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
     },
-    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/fonts/[name].[ext]' },
-    { test: /\.swf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/[name].[ext]' },
-    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
+    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/fonts/[name].[ext]' },
+    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
   ]
   },
   resolve: {
-    extensions: ['', '.js', '.jsx'], //We can use .js and React's .jsx files using Babel
+    extensions: ['*', '.js', '.jsx'], //We can use .js and React's .jsx files using Babel
     alias: {
       "bootstrap": "../assets/js/libs/bootstrap",
       "underscore": "lodash"
diff --git a/webpack.config.test.js b/webpack.config.test.js
index 54a60bb..8cb016f 100644
--- a/webpack.config.test.js
+++ b/webpack.config.test.js
@@ -16,64 +16,86 @@ module.exports = {
     './test/test.config.js' //Our starting point for our testing.
   ],
   module: {
-    preLoaders: [
-      {
-        test: /\.jsx?$/,
-        loaders: ['eslint'],
-        exclude: /node_modules/
-      }
-    ],
-    loaders: [
+    rules: [
+    {
+      test: /\.jsx?$/,
+      enforce: "pre",
+      use: ['eslint-loader'],
+      exclude: /node_modules/
+    },
     {
       test: /\.jsx?$/,
       exclude: /node_modules/,
       //loader: 'react-hot!babel'
-      loader: 'babel'
+      use: 'babel-loader'
     },
     {
-      test: require.resolve("jquery"),
-      loader: "expose?$!expose?jQuery"
+     test: require.resolve('jquery'),
+     use: [
+        {
+          loader: 'expose-loader',
+          options: 'jQuery'
+        },
+        {
+          loader: 'expose-loader',
+          options: '$'
+        }]
+     },
+     {
+      test: require.resolve("backbone"),
+      use: [{
+        loader: 'expose-loader',
+        options: 'Backbone'
+      }]
     },
     {
       test: require.resolve("sinon"),
-      loader: "expose?sinon"
-    },
-    {
-      test: require.resolve("backbone"),
-      loader: "expose?Backbone"
+      use: [{
+        loader: 'expose-loader',
+        options: 'sinon'
+      }]
     },
     {
       test: require.resolve("url-polyfill"),
-      loader: "imports?this=>window"
+      use: "imports-loader?this=>window"
     },
     {
       test: require.resolve("react"),
-      loader: "imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham"
+      use: "imports-loader?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham"
     },
     {
       test: /\.less$/,
-      loader: 'style!css!less'
+      use: [
+        "style-loader",
+        "css-loader",
+        "less-loader"
+      ]
+    },
+    {
+      test: /\.css$/,
+      use: [
+        "style-loader",
+        "css-loader"
+      ]
     },
-    { test: /\.css$/, loader: 'style!css' },
     {
       test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
-      loader: 'url?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
+      loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.woff2(\?\S*)?$/,   loader: 'url?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.woff2(\?\S*)?$/,   loader: 'url-loader?limit=10000&mimetype=application/font-woff2&name=dashboard.assets/fonts/[name].[ext]'
     },
     {
-      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
+      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=application/font-tff&name=dashboard.assets/fonts/[name].[ext]'
     },
-    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/fonts/[name].[ext]' },
-    { test: /\.swf(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/[name].[ext]' },
-    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file?name=dashboard.assets/img/[name].[ext]' },
-    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
+    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/fonts/[name].[ext]' },
+    { test: /\.png(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,    loader: 'file-loader?name=dashboard.assets/img/[name].[ext]' },
+    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: 'url-loader?limit=10000&mimetype=image/svg+xml&name=dashboard.assets/img/[name].[ext]' }
   ]
   },
   resolve: {
-    extensions: ['', '.js', '.jsx'], //We can use .js and React's .jsx files using Babel
+    extensions: ['*', '.js', '.jsx'], //We can use .js and React's .jsx files using Babel
     alias: {
       "bootstrap": "../assets/js/libs/bootstrap",
       "underscore": "lodash",

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].