diff --git a/.babel_cache/.gitkeep b/.babel_cache/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.bowerrc b/.bowerrc
deleted file mode 100644
index 716bd20a3623f09e7214bffe1b89c9cfc8deb4d5..0000000000000000000000000000000000000000
--- a/.bowerrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "directory": "resources/frontend_client/app/bower_components"
-}
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..150f92b28905693528e2ccee5f0b48eb7d976f61
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,42 @@
+{
+    "parser": "babel-eslint",
+    "rules": {
+        "quotes": 0,
+        "camelcase": 0,
+        "no-unused-vars": 0,
+        "eqeqeq": 0,
+        "key-spacing": 0,
+        "no-underscore-dangle": 0,
+        "curly": 0,
+        "no-use-before-define": 0,
+        "comma-dangle": 0,
+        "space-infix-ops": 0,
+        "no-shadow": 0,
+        "no-empty": 0,
+        "no-extra-bind": 0,
+        "eol-last": 0,
+        "consistent-return": 0,
+        "yoda": 0,
+        "no-multi-spaces": 0,
+        "no-mixed-spaces-and-tabs": 0,
+        "no-alert": 0,
+        "dot-notation": 0,
+        "space-unary-ops": 0,
+        "semi": 0,
+        "no-console": 0,
+        "global-strict": 0
+    },
+    "plugins": [
+        "react"
+    ],
+    "ecmaFeatures": {
+        "jsx": true
+    },
+    "globals": {
+        "angular": false,
+        "React": false
+    },
+    "env": {
+        "browser": true
+    }
+}
diff --git a/.gitignore b/.gitignore
index 041c862dafb25eb7ee988e2dace5e1cbc17a2f54..8308bd954d53b85be3743c03d1dac283deb64bb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,7 @@ profiles.clj
 /*.mv.db
 /*.lock.db
 /*.trace.db
-/resources/frontend_client/app/bower_components
 /resources/frontend_client/app/dist/
 /node_modules/
 /.js_hint_output/
+/.babel_cache
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 5fec253525d7255e86fd5584711d3857cdf694c0..0000000000000000000000000000000000000000
--- a/.jshintrc
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "globalstrict": true,
-  "globals": {
-      "angular": false,
-      "console": false,
-      "React": false,
-      "Set": false
-  }
-}
diff --git a/Gulpfile.js b/Gulpfile.js
deleted file mode 100644
index 26fe35a6e304e708dd42e534cb5763688c2b9670..0000000000000000000000000000000000000000
--- a/Gulpfile.js
+++ /dev/null
@@ -1,62 +0,0 @@
-'use strict';
-
-var gulp = require('gulp'),
-    concat = require('gulp-concat'),
-    react = require('gulp-react'),
-    myth = require('gulp-myth');
-
-var basePath = 'resources/frontend_client/app/';
-
-var SRC = {
-    css: [basePath + 'css/**/*.css', basePath + 'components/**/*.css'],
-    jsx: [basePath + 'query_builder/*.js'],
-    appJS: [basePath + '**/*.js', '!' + basePath + 'bower_components/**/*.js', '!' + basePath + 'dist/*.js', '!' + basePath + 'query_builder/*.js', '!' + basePath + '/test/**/*.js']
-};
-
-var DEST = {
-    css: '' + basePath + '/dist',
-    js: '' + basePath + '/dist',
-};
-
-
-/*
-  CSS compilation
-  1. get all css files in components directory specified in SRC.css
-     --------------------
-     this way we don't need to have a single 'app.css' or similar
-     to combine all our css (our build system should do this for us)
-
-  2. run the css through myth to generate browse compatible css
-  3. minify the css
-  4. write to DEST.css directory
-*/
-
-gulp.task('css', function(){
-    return gulp.src(SRC.css)
-            .pipe(concat('corvus.css'))
-            .pipe(myth())
-            .pipe(gulp.dest(DEST.css));
-});
-
-gulp.task('jsx', function () {
-    return gulp.src(SRC.jsx)
-        .pipe(concat('query_builder.js'))
-        .pipe(react())
-        .pipe(gulp.dest(DEST.js));
-});
-
-gulp.task('build-js', function () {
-    return gulp.src(SRC.appJS)
-        .pipe(concat('app.js'))
-        .pipe(gulp.dest(DEST.js));
-});
-
-gulp.task('watch', function(){
-    gulp.watch(SRC.css, ['css']);
-    gulp.watch(SRC.jsx, ['jsx']);
-    gulp.watch(SRC.appJS, ['build-js']);
-});
-
-gulp.task('build', ['css', 'jsx', 'build-js']);
-
-gulp.task('default', ['build', 'watch']);
diff --git a/bower.json b/bower.json
deleted file mode 100644
index c78fffe3006d97a75d4ec8b34b324c1d3f8b4f9e..0000000000000000000000000000000000000000
--- a/bower.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
-    "name": "corvus",
-    "description": "Expa Analytics",
-    "version": "0.0.0",
-    "homepage": "https://github.com/expa/data",
-    "license": "private",
-    "private": true,
-    "dependencies": {
-        "ace-builds": "1.1.7",
-        "angular": "1.2.26",
-        "angular-animate": "1.2.26",
-        "angular-bootstrap": "0.11.0",
-        "angular-cookie": "4.0.6",
-        "angular-cookies": "1.2.26",
-        "angular-gridster": "0.11.7",
-        "angular-http-auth": "1.2.1",
-        "angular-loader": "1.2.26",
-        "angular-mocks": "1.2.26",
-        "angular-readable-time": "~0.1.1",
-        "angular-resource": "1.2.26",
-        "angular-route": "1.2.26",
-        "angular-sanitize": "1.2.26",
-        "angular-ui-ace": "0.1.1",
-        "angular-xeditable": "0.1.8",
-        "angularytics": "0.3.0",
-        "crossfilter": "1.3.11",
-        "d3": "3.5.3",
-        "dc.js": "2.0.0-beta.1",
-        "fastclick": "~1.0.3",
-        "jquery": "2.1.1",
-        "moment": "~2.9.0",
-        "ng-sortable": "1.1.6",
-        "ngReact": "~0.1.3",
-        "react": "~0.12.2",
-        "react-date-picker": "~0.5.1",
-        "react-onclickoutside": "~0.2.2",
-        "underscore": "1.8.2"
-    },
-    "resolutions": {
-        "react-onclickoutside": "~0.2.2",
-        "moment": "2.8.2"
-    }
-}
diff --git a/docs/DEVELOPERS.md b/docs/DEVELOPERS.md
index 3c3fdbe5be1957277d6b92a7b487d0886e1fc643..211e86ef6d1997390cc244addc994dd56fa28e7b 100644
--- a/docs/DEVELOPERS.md
+++ b/docs/DEVELOPERS.md
@@ -9,20 +9,21 @@
 
 ## Build
 
-Install clojure + npm/bower requirements with
+Install clojure + npm requirements with
 
     lein deps
     lein npm
 
 Build the application JS and CSS with
 
-    lein gulp
+    lein webpack
 
 When developing the frontend client, you'll want to watch for changes,
-so run the default gulp task.
+so run webpack with the '-w' flag.
 
-    ./node_modules/gulp/bin/gulp.js
+    ./node_modules/webpack/bin/webpack.js -w
 
+Note that changes to CSS variables will only be picked up when webpack is restarted.
 
 ## Usage
 
diff --git a/lein_tasks/leiningen/gulp.clj b/lein_tasks/leiningen/gulp.clj
deleted file mode 100644
index 79769423c704543a30ad6991228da94c036799c0..0000000000000000000000000000000000000000
--- a/lein_tasks/leiningen/gulp.clj
+++ /dev/null
@@ -1,11 +0,0 @@
-(ns leiningen.gulp
-  (:use clojure.java.shell))
-
-
-(defn gulp [projects & args]
-  ;; TODO - some better validations such as checking that we have gulp available
-  (println "Running `gulp build` to assemble frontend assets into a better format")
-  (let [result (sh (str (:root projects) "/node_modules/gulp/bin/gulp.js") "build")]
-    (if (= 0 (:exit result))
-      (println (:out result))
-      (println (:err result)))))
\ No newline at end of file
diff --git a/lein_tasks/leiningen/webpack.clj b/lein_tasks/leiningen/webpack.clj
new file mode 100644
index 0000000000000000000000000000000000000000..5b5b5c6884061a1862835375f66221e2fe2a9889
--- /dev/null
+++ b/lein_tasks/leiningen/webpack.clj
@@ -0,0 +1,11 @@
+(ns leiningen.webpack
+  (:use clojure.java.shell))
+
+
+(defn webpack [projects & args]
+  ;; TODO - some better validations such as checking that we have webpack available
+  (println "Running `webpack -p` to assemble and minify frontend assets")
+  (let [result (sh (str (:root projects) "/node_modules/webpack/bin/webpack.js") "-p")]
+    (if (= 0 (:exit result))
+      (println (:out result))
+      (println (:err result)))))
diff --git a/lint_js.sh b/lint_js.sh
index 7048eaebdf1641c969b315e67c2856509ec3f049..23b64e6141a6a556042801dbabc474fa39a1a5e1 100755
--- a/lint_js.sh
+++ b/lint_js.sh
@@ -2,8 +2,7 @@
 
 # Simple shell script for running jshint and nicely formatting the output :heart_eyes_cat:
 
-JS_HINT=./node_modules/jsxhint/cli.js
-JS_HINT_OPTS='--config .jshintrc'
+ESLINT='./node_modules/eslint/bin/eslint.js'
 JS_FILES=`find resources/frontend_client/app -name "*.js" | grep -v bower_components | grep -v 'app/test/' | grep -v '\#' | grep -v 'app/dist/'`
 
 BOLD='\033[1;30m'
@@ -47,7 +46,7 @@ file_modified () {
 run_js_lint () {
     file=$1
     output_file=$2
-    $JS_HINT $JS_HINT_OPTS $file | perl -pe 's/^.*(line.*)$/$1/' | sort > $output_file
+    $ESLINT $file | perl -pe 's/^.*(line.*)$/$1/' | sort > $output_file
 }
 
 for file in $JS_FILES; do
diff --git a/package.json b/package.json
index 1ae2d45295baf1af193a9d009334e91d589e320e..6bee8ff11e8e9c3472e12cbd2d47a822a4922207 100644
--- a/package.json
+++ b/package.json
@@ -1,30 +1,65 @@
 {
-    "name": "corvus",
-    "private": true,
-    "version": "0.0.0",
-    "description": "Expa Analytics",
-    "repository": "https://github.com/expa/data",
-    "license": "private",
-    "engines": {
-        "node": "0.10.25"
-    },
-    "dependencies": {
-        "bower": "1.3.12",
-        "gulp": "^3.8.8",
-        "gulp-concat": "^2.4.0",
-        "gulp-myth": "^1.0.1",
-        "gulp-jsx": "^0.7.0",
-        "gulp-react": "^2.0.0"
-    },
-    "devDependencies": {
-        "http-server": "^0.6.1",
-        "jsxhint": "0.13.3",
-        "karma": "~0.10",
-        "karma-junit-reporter": "^0.2.2",
-        "protractor": "~0.20.1",
-        "shelljs": "^0.2.6"
-    },
-    "scripts": {
-        "postinstall": "./node_modules/bower/bin/bower install --allow-root --config.interactive=false"
-    }
+  "name": "metabase",
+  "private": true,
+  "version": "0.0.0",
+  "description": "Metabase Analytics Report Server",
+  "repository": "https://github.com/metabase/metabase-init",
+  "license": "private",
+  "engines": {
+    "node": "0.10.25"
+  },
+  "dependencies": {
+    "ace-builds": "git://github.com/ajaxorg/ace-builds",
+    "angular": "^1.2.28",
+    "angular-animate": "^1.2.28",
+    "angular-bootstrap": "^0.12.0",
+    "angular-cookie": "git://github.com/ivpusic/angular-cookie#v4.0.6",
+    "angular-cookies": "^1.2.28",
+    "angular-gridster": "^0.11.7",
+    "angular-http-auth": "git://github.com/tamlyn/angular-http-auth#1eb4a08d08b400956c0848f84d8fcaa9ad1357a2",
+    "angular-readable-time": "git://github.com/wildlyinaccurate/angular-readable-time#0.1.1",
+    "angular-resource": "^1.2.28",
+    "angular-route": "^1.2.28",
+    "angular-sanitize": "^1.2.28",
+    "angular-ui-ace": "^0.2.3",
+    "angular-xeditable": "git://github.com/vitalets/angular-xeditable#0.1.9",
+    "angularytics": "^0.3.0",
+    "crossfilter": "^1.3.11",
+    "d3": "^3.5.3",
+    "dc": "^2.0.0-beta.1",
+    "jquery": "^2.1.4",
+    "moment": "^2.9.0",
+    "ng-sortable": "^1.2.0",
+    "ngreact": "^0.1.5",
+    "react": "^0.12.2",
+    "react-datepicker": "^0.5.1",
+    "react-onclickoutside": "^0.2.2",
+    "tether": "^0.6.5",
+    "underscore": "^1.8.3"
+  },
+  "devDependencies": {
+    "angular-mocks": "^1.2.28",
+    "babel": "^5.4.7",
+    "babel-core": "^5.4.7",
+    "babel-eslint": "^3.1.14",
+    "babel-loader": "^5.1.3",
+    "css-loader": "^0.14.4",
+    "cssnext-loader": "^1.0.1",
+    "eslint": "^0.22.1",
+    "eslint-loader": "^0.12.0",
+    "eslint-plugin-react": "^2.5.0",
+    "extract-text-webpack-plugin": "^0.8.1",
+    "glob": "^5.0.10",
+    "http-server": "^0.6.1",
+    "karma": "~0.10",
+    "karma-junit-reporter": "^0.2.2",
+    "ng-annotate-webpack-plugin": "^0.1.2",
+    "node-libs-browser": "^0.5.2",
+    "protractor": "~0.20.1",
+    "shelljs": "^0.2.6",
+    "style-loader": "^0.12.3",
+    "webpack": "^1.9.10",
+    "webpack-postcss-tools": "^1.1.1"
+  },
+  "scripts": {}
 }
diff --git a/project.clj b/project.clj
index 341867fb069e73e03ea3e98daebb72a1f712d270..51708e6f32157fa08641051cf16d59ca8bd102ed 100644
--- a/project.clj
+++ b/project.clj
@@ -87,4 +87,4 @@
                                        "-Dmb.jetty.port=3001"
                                        "-Dmb.api.key=test-api-key"]}
              :uberjar {:aot :all
-                       :prep-tasks ["npm" "gulp" "javac" "compile"]}})
+                       :prep-tasks ["npm" "webpack" "javac" "compile"]}})
diff --git a/resources/frontend_client/app/auth/auth.controllers.js b/resources/frontend_client/app/auth/auth.controllers.js
index 4d64fa9294137c4119ff867fd0f8e3d485d279d5..c59b37310bb80913b3afcbf3d82d21cd3ddd3eeb 100644
--- a/resources/frontend_client/app/auth/auth.controllers.js
+++ b/resources/frontend_client/app/auth/auth.controllers.js
@@ -2,7 +2,6 @@
 /*jslint browser:true*/
 /*jslint devel:true */
 /*global _*/
-/*global $*/
 
 var AuthControllers = angular.module('corvus.auth.controllers', [
     'ipCookie',
@@ -127,4 +126,4 @@ AuthControllers.controller('PasswordReset', ['$scope', '$routeParams', '$locatio
         });
     };
 
-}]);
\ No newline at end of file
+}]);
diff --git a/resources/frontend_client/app/card/card.charting.js b/resources/frontend_client/app/card/card.charting.js
index 665281431c74145d949ced2acd3eea1957e020ac..edb4c8d709fc24fd9cc5ae6026a1721ed812d0c7 100644
--- a/resources/frontend_client/app/card/card.charting.js
+++ b/resources/frontend_client/app/card/card.charting.js
@@ -1,6 +1,11 @@
 'use strict';
 /*jslint browser:true */
-/*global document,$,jQuery,_,google,d3,crossfilter,dc,console,vs*/
+/*global document,_,google,console,vs*/
+
+import $ from 'jquery';
+import crossfilter from 'crossfilter';
+import d3 from 'd3';
+import dc from 'dc';
 
 // ---------------------------------------- TODO - Maybe. Lots of these things never worked in the first place. ----------------------------------------
 // IMPORTANT
@@ -565,7 +570,7 @@ function GeoHeatmapChartRenderer(id, card, result) {
         });
 }
 
-var CardRenderer = {
+export var CardRenderer = {
     /// get the size render settings for card if applicable
     _getSizeSettings: function(cardOrDimension) {
         if (typeof cardOrDimension === "object") {
diff --git a/resources/frontend_client/app/card/card.controllers.js b/resources/frontend_client/app/card/card.controllers.js
index 2d44aff654d1da7288a5eb35ddd30acd24b9d27d..e6909bf262e27ed89314736b477d5fb9ba86e84c 100644
--- a/resources/frontend_client/app/card/card.controllers.js
+++ b/resources/frontend_client/app/card/card.controllers.js
@@ -1,5 +1,10 @@
 'use strict';
-/*global _, document, confirm, QueryHeader, NativeQueryEditor, GuiQueryEditor, ResultQueryEditor, QueryVisualization*/
+/*global _, document, confirm*/
+
+import GuiQueryEditor from '../query_builder/gui_query_editor.react';
+import NativeQueryEditor from '../query_builder/native_query_editor.react';
+import QueryHeader from '../query_builder/header.react';
+import QueryVisualization from '../query_builder/visualization.react';
 
 //  Card Controllers
 var CardControllers = angular.module('corvus.card.controllers', []);
diff --git a/resources/frontend_client/app/card/card.directives.js b/resources/frontend_client/app/card/card.directives.js
index 34b15166e8a34abb78975fb379dad0c5c06062ba..aebb5dfd763885ad6bd886f9e7c60acf7d60c903 100644
--- a/resources/frontend_client/app/card/card.directives.js
+++ b/resources/frontend_client/app/card/card.directives.js
@@ -1,11 +1,7 @@
 'use strict';
-/*global setTimeout*/
-/*global $*/
-/*global CardRenderer*/
-/* global React */
-/* global document */
-/* global QueryBuilder */
+/*global setTimeout, React */
 
+import { CardRenderer } from './card.charting';
 
 var CardDirectives = angular.module('corvus.card.directives', []);
 
@@ -458,9 +454,7 @@ CardDirectives.directive('cvLatlongHeatmap', ['CardRenderer', function(CardRende
 
         scope.$watch('cvLatlongHeatmap', function(value) {
             if (value) {
-                $(function() {
-                    CardRenderer.latlongHeatmap('map-canvas', 'whatever', value);
-                });
+                CardRenderer.latlongHeatmap('map-canvas', 'whatever', value);
             }
         });
     }
diff --git a/resources/frontend_client/app/components/card/card.css b/resources/frontend_client/app/components/card/card.css
index 787de6c095e452959f5bf69a7f17b038cc7a937e..85d8ac7cdbb670507c637fbc6d14a2366e8d09bc 100644
--- a/resources/frontend_client/app/components/card/card.css
+++ b/resources/frontend_client/app/components/card/card.css
@@ -103,4 +103,4 @@
 
 .Dash-card .Card {
   overflow-y: scroll;
-}
\ No newline at end of file
+}
diff --git a/resources/frontend_client/app/components/icons/icons.js b/resources/frontend_client/app/components/icons/icons.js
index c461c3639a9ee13535cab8dacdea08086b534e7c..1a162ab3e982fba03bce4f5661aab7d31354d7fb 100644
--- a/resources/frontend_client/app/components/icons/icons.js
+++ b/resources/frontend_client/app/components/icons/icons.js
@@ -1,5 +1,6 @@
 'use strict';
-/* global ICON_PATHS */
+
+import ICON_PATHS from 'metabase/icon_paths';
 
 /*
     GENERIC ICONS
diff --git a/resources/frontend_client/app/controllers.js b/resources/frontend_client/app/controllers.js
index a018e9e6d649f008bd5563b643c6d5948fdaf08f..0f963735a2bc8992cb5024cde2097e44bb932f40 100644
--- a/resources/frontend_client/app/controllers.js
+++ b/resources/frontend_client/app/controllers.js
@@ -1,8 +1,6 @@
 'use strict';
 /*jslint browser:true*/
 /*jslint devel:true */
-/*global _*/
-/*global $*/
 
 // Global Controllers
 var CorvusControllers = angular.module('corvus.controllers', ['corvus.services', 'corvus.navbar.directives']);
diff --git a/resources/frontend_client/app/css/core/grid.css b/resources/frontend_client/app/css/core/grid.css
index 0032e8f30ca7dfdcaa02adb1fc277e4d353eef71..1e40fc9090a46211e7bc4d102add46f85cea0935 100644
--- a/resources/frontend_client/app/css/core/grid.css
+++ b/resources/frontend_client/app/css/core/grid.css
@@ -70,7 +70,7 @@
   flex: 0 0 25%;
 }
 
-@media (break-sm) {
+@media (--breakpoint-min-sm) {
   .small-Grid--fit > .Grid-cell {
     flex: 1;
   }
@@ -88,7 +88,7 @@
   }
 }
 
-@media (--break-md) {
+@media (--breakpoint-min-md) {
   .med-Grid--fit > .Grid-cell {
     flex: 1;
   }
@@ -106,7 +106,7 @@
   }
 }
 
-@media (--break-lg) {
+@media (--breakpoint-min-lg) {
   .large-Grid--fit > .Grid-cell {
     flex: 1;
   }
@@ -145,7 +145,7 @@
   padding: 2em 0 0 2em;
 }
 
-@media (break-sm) {
+@media (--breakpoint-min-sm) {
   .small-Grid--gutters {
     margin: -1em 0 1em -1em;
   }
@@ -166,7 +166,7 @@
   }
 }
 
-@media (--break-md) {
+@media (--breakpoint-min-md) {
   .med-Grid--gutters {
     margin: -1em 0 1em -1em;
   }
@@ -187,7 +187,7 @@
   }
 }
 
-@media (--break-lg) {
+@media (--breakpoint-min-lg) {
   .large-Grid--gutters {
     margin: -1em 0 1em -1em;
   }
diff --git a/resources/frontend_client/app/directives.js b/resources/frontend_client/app/directives.js
index 6cd4f2971e846a3a01f19e485236deb955a75901..a2325fe0f7b62a991b09d816b39501110ea80ec6 100644
--- a/resources/frontend_client/app/directives.js
+++ b/resources/frontend_client/app/directives.js
@@ -1,14 +1,10 @@
+'use strict';
+
 /*jslint browser:true */
 /*jslint devel:true */
-/*global _*/
-/*global CardRenderer*/
-/*global $*/
 /*global ace*/
 
-'use strict';
 /* Directives */
-
-
 var CorvusDirectives = angular.module('corvus.directives', []);
 
 CorvusDirectives.directive('deleteConfirm', [function() {
diff --git a/resources/frontend_client/app/icon_paths.js b/resources/frontend_client/app/icon_paths.js
index 4a4543178d7164396c321c8446b98db309ff9899..1672bbf40f8dfc4f779752512864c871ee2034ed 100644
--- a/resources/frontend_client/app/icon_paths.js
+++ b/resources/frontend_client/app/icon_paths.js
@@ -11,7 +11,7 @@
 
 */
 
-var ICON_PATHS = {
+export default {
     add: 'M19,13 L19,2 L14,2 L14,13 L2,13 L2,18 L14,18 L14,30 L19,30 L19,18 L30,18 L30,13 L19,13 Z',
     addtodash: [
         'M16,31 L16,31 C24.2842712,31 31,24.2842712 31,16 C31,7.71572875 24.2842712,1 16,1 C7.71572875,1 1,7.71572875 1,16 C1,24.2842712 7.71572875,31 16,31 L16,31 Z M16,32 L16,32 C7.163444,32 0,24.836556 0,16 C0,7.163444 7.163444,0 16,0 C24.836556,0 32,7.163444 32,16 C32,24.836556 24.836556,32 16,32 L16,32 Z',
@@ -36,5 +36,3 @@ var ICON_PATHS = {
     search: 'M12 0 A12 12 0 0 0 0 12 A12 12 0 0 0 12 24 A12 12 0 0 0 18.5 22.25 L28 32 L32 28 L22.25 18.5 A12 12 0 0 0 24 12 A12 12 0 0 0 12 0 M12 4 A8 8 0 0 1 12 20 A8 8 0 0 1 12 4  ',
     star: 'M16 0 L21 11 L32 12 L23 19 L26 31 L16 25 L6 31 L9 19 L0 12 L11 11',
 };
-
-/* module.exports = ICON_PATHS */
diff --git a/resources/frontend_client/app/js/google_maps.js b/resources/frontend_client/app/js/google_maps.js
index 6ab79307fa5a1431cddddc8ba346a385bec1da2e..95eef295681bfebaa7009892742a41d3bce9b7e8 100644
--- a/resources/frontend_client/app/js/google_maps.js
+++ b/resources/frontend_client/app/js/google_maps.js
@@ -134,4 +134,4 @@ google.maps = google.maps || {};
     };
     var loadScriptTime = (new Date()).getTime();
     getScript("https://maps.gstatic.com/cat_js/intl/en_us/mapfiles/api-3/17/7/%7Bmain,visualization%7D.js");
-})();
\ No newline at end of file
+})();
diff --git a/resources/frontend_client/app/query_builder/action_button.react.js b/resources/frontend_client/app/query_builder/action_button.react.js
index f8df8975be10621f6f52845f5444b636de8e0d3f..db1c7627cae952809df5a1c8d839d85aced09aee 100644
--- a/resources/frontend_client/app/query_builder/action_button.react.js
+++ b/resources/frontend_client/app/query_builder/action_button.react.js
@@ -1,7 +1,11 @@
 'use strict';
-/*global cx, setTimeout, clearTimeout, OnClickOutside, SelectionModule, Icon*/
+/*global setTimeout, clearTimeout*/
 
-var ActionButton = React.createClass({
+import Icon from './icon.react';
+
+var cx = React.addons.classSet;
+
+export default React.createClass({
     displayName: 'ActionButton',
     propTypes: {
         actionFn: React.PropTypes.func.isRequired
diff --git a/resources/frontend_client/app/query_builder/add_to_dashboard.react.js b/resources/frontend_client/app/query_builder/add_to_dashboard.react.js
index a278fb754bff897c7610b277fac4a5363c927e3f..e8f5ba03e2bedded3830667eec85cd42292704e6 100644
--- a/resources/frontend_client/app/query_builder/add_to_dashboard.react.js
+++ b/resources/frontend_client/app/query_builder/add_to_dashboard.react.js
@@ -1,7 +1,12 @@
 'use strict';
-/*global cx, OnClickOutside, Popover, AddToDashboardPopover, SelectionModule, Icon, ReactCSSTransitionGroup*/
 
-var AddToDashboard = React.createClass({
+import AddToDashboardPopover from './add_to_dashboard_popover.react';
+import Icon from './icon.react';
+import Popover from './popover.react';
+
+var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+
+export default React.createClass({
     displayName: 'AddToDashboard',
     propTypes: {
         card: React.PropTypes.object.isRequired,
diff --git a/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js b/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js
index 928a25a130cea7d09f90f8edad5959be4753f05a..ba6fa05e63ac2590798fead55605a647b0996299 100644
--- a/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js
+++ b/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js
@@ -1,7 +1,14 @@
 'use strict';
-/*global cx, ReactCSSTransitionGroup, OnClickOutside, FormField, SelectionModule, Icon */
 
-var AddToDashboardPopover = React.createClass({
+import OnClickOutside from 'react-onclickoutside';
+
+import FormField from './form_field.react';
+import Icon from './icon.react';
+
+var cx = React.addons.classSet;
+var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+
+export default React.createClass({
     displayName: 'AddToDashboardPopover',
     propTypes: {
         card: React.PropTypes.object.isRequired,
diff --git a/resources/frontend_client/app/query_builder/aggregation_widget.react.js b/resources/frontend_client/app/query_builder/aggregation_widget.react.js
index 04f9a76f94c51f7e597d25a801a243bb82dc1d95..6cdd2cb319a3953072e656041047c1d56140da20 100644
--- a/resources/frontend_client/app/query_builder/aggregation_widget.react.js
+++ b/resources/frontend_client/app/query_builder/aggregation_widget.react.js
@@ -1,7 +1,9 @@
 'use strict';
-/*global _, DateFilter, SelectionModule, Icon */
+/*global _ */
 
-var AggregationWidget = React.createClass({
+import SelectionModule from './selection_module.react';
+
+export default React.createClass({
     displayName: 'AggregationWidget',
     propTypes: {
         aggregation: React.PropTypes.array.isRequired,
diff --git a/resources/frontend_client/app/query_builder/database_selector.react.js b/resources/frontend_client/app/query_builder/database_selector.react.js
index 8509c7629b1f0f6b65f7d9d60b27927e4cd57ded..656dceed37b1b631b78558770bd23c42c28a74d1 100644
--- a/resources/frontend_client/app/query_builder/database_selector.react.js
+++ b/resources/frontend_client/app/query_builder/database_selector.react.js
@@ -1,7 +1,8 @@
 'use strict';
-/*global SelectionModule*/
 
-var DatabaseSelector = React.createClass({
+import SelectionModule from './selection_module.react';
+
+export default React.createClass({
     displayName: 'DatabaseSelector',
     propTypes: {
         currentDatabaseId: React.PropTypes.number.isRequired,
diff --git a/resources/frontend_client/app/query_builder/date_filter.react.js b/resources/frontend_client/app/query_builder/date_filter.react.js
index e0da7715a8753ee781d3b226e97ddb0cf36719f6..da331ad2f6389cfe7442556a7b2273b8a99f9101 100644
--- a/resources/frontend_client/app/query_builder/date_filter.react.js
+++ b/resources/frontend_client/app/query_builder/date_filter.react.js
@@ -1,7 +1,18 @@
 'use strict';
-/*global moment, DatePicker*/
 
-var DateFilter = React.createClass({
+/*global window*/
+
+// import compiled version, webpack doesn't seem to be running JSX transforms on node_modules
+// css imported in init.css
+import DatePicker from 'react-datepicker';
+import Tether from 'tether';
+import moment from 'moment';
+
+// DatePicker depedencies :(
+window.Tether = Tether;
+window.moment = moment;
+
+export default React.createClass({
     displayName: 'DateFilter',
     propTypes: {
         date: React.PropTypes.string,
diff --git a/resources/frontend_client/app/query_builder/filter_widget.react.js b/resources/frontend_client/app/query_builder/filter_widget.react.js
index c8e9c077a1fa790247381ae9dc83b2898307059c..0caccd461660dd35ec1ab935c9574ee1cc560f7c 100644
--- a/resources/frontend_client/app/query_builder/filter_widget.react.js
+++ b/resources/frontend_client/app/query_builder/filter_widget.react.js
@@ -1,7 +1,10 @@
 'use strict';
-/*global DateFilter, SelectionModule, Icon */
 
-var FilterWidget = React.createClass({
+import DateFilter from './date_filter.react';
+import Icon from './icon.react';
+import SelectionModule from './selection_module.react';
+
+export default React.createClass({
     displayName: 'FilterWidget',
     propTypes: {
         filter: React.PropTypes.array.isRequired,
diff --git a/resources/frontend_client/app/query_builder/form_field.react.js b/resources/frontend_client/app/query_builder/form_field.react.js
index 7020380f7d9cfee544fb08c4cbf9dbcad59febd2..1aa6e030ab8ee7d4929f373f0dc4cc5de00b1022 100644
--- a/resources/frontend_client/app/query_builder/form_field.react.js
+++ b/resources/frontend_client/app/query_builder/form_field.react.js
@@ -1,7 +1,8 @@
 'use strict';
-/*global cx, OnClickOutside, SelectionModule*/
 
-var FormField = React.createClass({
+var cx = React.addons.classSet;
+
+export default React.createClass({
     displayName: 'FormField',
     propTypes: {
         fieldName: React.PropTypes.string.isRequired,
diff --git a/resources/frontend_client/app/query_builder/gui_query_editor.react.js b/resources/frontend_client/app/query_builder/gui_query_editor.react.js
index 623745a775dcb6289c545af219d1c9525ce983d5..861da034cb06a48a2df612d95bc0cb31310b1059 100644
--- a/resources/frontend_client/app/query_builder/gui_query_editor.react.js
+++ b/resources/frontend_client/app/query_builder/gui_query_editor.react.js
@@ -1,9 +1,19 @@
 'use strict';
-/*global _, cx, AggregationWidget, FilterWidget, LimitWidget, SortWidget, RunButton, SelectionModule, DatabaseSelector, Icon*/
-
+/*global _*/
+
+import AggregationWidget from './aggregation_widget.react';
+import DatabaseSelector from './database_selector.react';
+import FilterWidget from './filter_widget.react';
+import Icon from './icon.react';
+import LimitWidget from './limit_widget.react';
+import RunButton from './run_button.react';
+import SelectionModule from './selection_module.react';
+import SortWidget from './sort_widget.react';
+
+var cx = React.addons.classSet;
 var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
 
-var GuiQueryEditor = React.createClass({
+export default React.createClass({
     displayName: 'GuiQueryEditor',
     propTypes: {
         databases: React.PropTypes.array.isRequired,
@@ -111,11 +121,10 @@ var GuiQueryEditor = React.createClass({
     },
 
     canRun: function() {
-        var canRun = false;
         if (this.hasValidAggregation()) {
-            canRun = true;
+            return true;
         }
-        return canRun;
+        return false;
     },
 
     runQuery: function() {
diff --git a/resources/frontend_client/app/query_builder/header.react.js b/resources/frontend_client/app/query_builder/header.react.js
index 6e33db8b1832df22c4da2178b2e1588aa2096502..98b1860910209d379893a34366c3809707ee88e3 100644
--- a/resources/frontend_client/app/query_builder/header.react.js
+++ b/resources/frontend_client/app/query_builder/header.react.js
@@ -1,10 +1,16 @@
 'use strict';
-/*global setTimeout, clearTimeout, Saver, ActionButton, Popover, Icon, QueryModeToggle, AddToDashboard*/
+/*global setTimeout, clearTimeout*/
 
-var cx = React.addons.classSet,
-    ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+import ActionButton from './action_button.react';
+import AddToDashboard from './add_to_dashboard.react';
+import Icon from './icon.react';
+import Popover from './popover.react';
+import QueryModeToggle from './query_mode_toggle.react';
+import Saver from './saver.react';
 
-var QueryHeader = React.createClass({
+var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+
+export default React.createClass({
     displayName: 'QueryHeader',
     propTypes: {
         card: React.PropTypes.object.isRequired,
diff --git a/resources/frontend_client/app/query_builder/icon.react.js b/resources/frontend_client/app/query_builder/icon.react.js
index d94d63977200bff59d46a33dbf59f3fdc2d0a2ca..0a639c1f901e152070dd13397c2f734327ff6b4b 100644
--- a/resources/frontend_client/app/query_builder/icon.react.js
+++ b/resources/frontend_client/app/query_builder/icon.react.js
@@ -1,7 +1,8 @@
 'use strict';
-/* global ICON_PATHS */
 
-var Icon = React.createClass({
+import ICON_PATHS from 'metabase/icon_paths';
+
+export default React.createClass({
     displayName: 'Icon',
     getDefaultProps: function () {
        return {
diff --git a/resources/frontend_client/app/query_builder/limit_widget.react.js b/resources/frontend_client/app/query_builder/limit_widget.react.js
index 0c15923f65800567fea229fc294543dc967cbf77..d5988296fc47118205290d80003c1b892df90a83 100644
--- a/resources/frontend_client/app/query_builder/limit_widget.react.js
+++ b/resources/frontend_client/app/query_builder/limit_widget.react.js
@@ -1,7 +1,9 @@
 'use strict';
-/*global DateFilter, SelectionModule, Icon */
 
-var LimitWidget = React.createClass({
+import Icon from './icon.react';
+import SelectionModule from './selection_module.react';
+
+export default React.createClass({
     displayName: 'LimitWidget',
     propTypes: {
         limit: React.PropTypes.number,
diff --git a/resources/frontend_client/app/query_builder/native_query_editor.react.js b/resources/frontend_client/app/query_builder/native_query_editor.react.js
index d35eb3bb95ea48a144d9874390f7ff2aaba2e91b..fe1d8f7044d98adab32baeb2d640eaa2043cae36 100644
--- a/resources/frontend_client/app/query_builder/native_query_editor.react.js
+++ b/resources/frontend_client/app/query_builder/native_query_editor.react.js
@@ -1,7 +1,10 @@
 'use strict';
-/*global ace, RunButton, SelectionModule, DatabaseSelector*/
+/*global ace*/
 
-var NativeQueryEditor = React.createClass({
+import RunButton from './run_button.react';
+import DatabaseSelector from './database_selector.react';
+
+export default React.createClass({
     displayName: 'NativeQueryEditor',
     propTypes: {
         databases: React.PropTypes.array.isRequired,
diff --git a/resources/frontend_client/app/query_builder/popover.react.js b/resources/frontend_client/app/query_builder/popover.react.js
index 50dcd34c869f772f2e35bc530caead762d73d436..f299b87c1d2e80f53917e8d200cb37cbe9b92a64 100644
--- a/resources/frontend_client/app/query_builder/popover.react.js
+++ b/resources/frontend_client/app/query_builder/popover.react.js
@@ -1,7 +1,7 @@
 'use strict';
-/*global document, cx, Tether*/
+/*global document, Tether*/
 
-var Popover = React.createClass({
+export default React.createClass({
     displayName: 'Popover',
 
     componentWillMount: function() {
@@ -72,4 +72,4 @@ var Popover = React.createClass({
     render: function() {
         return <span/>;
     }
-});
\ No newline at end of file
+});
diff --git a/resources/frontend_client/app/query_builder/popover_content.react.js b/resources/frontend_client/app/query_builder/popover_content.react.js
index fdd21f52359d6ea15295d75ecdb524b93486a2b6..e4967684e9c2ce6d774958356e1ab6e4a0450859 100644
--- a/resources/frontend_client/app/query_builder/popover_content.react.js
+++ b/resources/frontend_client/app/query_builder/popover_content.react.js
@@ -1,10 +1,11 @@
 'use strict';
-/*global cx, OnClickOutside, Popover, AddToDashboardPopover, SelectionModule, AddToDashIcon, ReactCSSTransitionGroup*/
+
+import OnClickOutside from 'react-onclickoutside';
 
 // this feels a little silly, but we have this component ONLY so that we can add the OnClickOutside functionality on an
 // arbitrary set of html content.  I wish we could do that more easily
 
-var PopoverContent = React.createClass({
+export default React.createClass({
     displayName: 'PopoverContent',
     mixins: [OnClickOutside],
 
diff --git a/resources/frontend_client/app/query_builder/popover_with_trigger.react.js b/resources/frontend_client/app/query_builder/popover_with_trigger.react.js
index b4935c4b3e9c69b18a75c1d88c34b5b1edcfcc55..050c56a1cc54cb0c184fdcec6191b570d75589f2 100644
--- a/resources/frontend_client/app/query_builder/popover_with_trigger.react.js
+++ b/resources/frontend_client/app/query_builder/popover_with_trigger.react.js
@@ -1,7 +1,9 @@
 'use strict';
-/*global document, cx, PopoverContent, Tether*/
+/*global document, Tether*/
 
-var PopoverWithTrigger = React.createClass({
+import PopoverContent from './popover_content.react'
+
+export default React.createClass({
     displayName: 'PopoverWithTrigger',
 
     getInitialState: function() {
@@ -99,4 +101,4 @@ var PopoverWithTrigger = React.createClass({
             </span>
         );
     }
-});
\ No newline at end of file
+});
diff --git a/resources/frontend_client/app/query_builder/query_mode_toggle.react.js b/resources/frontend_client/app/query_builder/query_mode_toggle.react.js
index 4314f8644e37ef0c7499638612888eca40fe747b..58af8cb40db06aecacbcc561ff9d360ee488d31a 100644
--- a/resources/frontend_client/app/query_builder/query_mode_toggle.react.js
+++ b/resources/frontend_client/app/query_builder/query_mode_toggle.react.js
@@ -1,7 +1,10 @@
 'use strict';
-/*global cx, OnClickOutside, SelectionModule*/
 
-var QueryModeToggle = React.createClass({
+import SelectionModule from './selection_module.react';
+
+var cx = React.addons.classSet;
+
+export default React.createClass({
     displayName: 'QueryModeToggle',
     propTypes: {
         currentQueryMode: React.PropTypes.string.isRequired,
diff --git a/resources/frontend_client/app/query_builder/run_button.react.js b/resources/frontend_client/app/query_builder/run_button.react.js
index 6b7a2e2e3345c814aaf582dc3fc084eeb72413a4..1fcfd7e49af7ea950e1e2390561bead3d8185436 100644
--- a/resources/frontend_client/app/query_builder/run_button.react.js
+++ b/resources/frontend_client/app/query_builder/run_button.react.js
@@ -1,6 +1,6 @@
 'use strict';
 
-var RunButton = React.createClass({
+export default React.createClass({
     displayName: 'RunButton',
     propTypes: {
         canRun: React.PropTypes.bool.isRequired,
diff --git a/resources/frontend_client/app/query_builder/saver.react.js b/resources/frontend_client/app/query_builder/saver.react.js
index e88d5890feaa5366f934e16e204a9fa87756fc71..d72f6fe5ff84e6027b6c93a0d8788ad3629ad53c 100644
--- a/resources/frontend_client/app/query_builder/saver.react.js
+++ b/resources/frontend_client/app/query_builder/saver.react.js
@@ -1,7 +1,12 @@
 'use strict';
-/*global cx, OnClickOutside, FormField, SelectionModule*/
 
-var Saver = React.createClass({
+import OnClickOutside from 'react-onclickoutside';
+
+import FormField from './form_field.react';
+
+var cx = React.addons.classSet;
+
+export default React.createClass({
     displayName: 'Saver',
     propTypes: {
         card: React.PropTypes.object.isRequired,
diff --git a/resources/frontend_client/app/query_builder/search_bar.react.js b/resources/frontend_client/app/query_builder/search_bar.react.js
index 94249d84cbe78af145fb0df87c9507f48b5b0ad2..ca9c126475417d50dfb27cab589f7333c7f72054 100644
--- a/resources/frontend_client/app/query_builder/search_bar.react.js
+++ b/resources/frontend_client/app/query_builder/search_bar.react.js
@@ -1,6 +1,6 @@
 'use strict';
 
-var SearchBar = React.createClass({
+export default React.createClass({
     displayName: 'SearchBar',
     propTypes: {
         filter: React.PropTypes.string.isRequired,
diff --git a/resources/frontend_client/app/query_builder/selection_module.react.js b/resources/frontend_client/app/query_builder/selection_module.react.js
index b84e4be1cd9de048da51f704d30859d6ceb76fc9..19a4c4138731660d7877c0f0a8d75ade74b04dad 100644
--- a/resources/frontend_client/app/query_builder/selection_module.react.js
+++ b/resources/frontend_client/app/query_builder/selection_module.react.js
@@ -1,7 +1,13 @@
 'use strict';
-/*global cx, OnClickOutside, SearchBar, Icon*/
 
-var SelectionModule = React.createClass({
+import OnClickOutside from 'react-onclickoutside';
+
+import Icon from './icon.react';
+import SearchBar from './search_bar.react';
+
+var cx = React.addons.classSet;
+
+export default React.createClass({
     displayName:'SelectionModule',
     propTypes: {
         action: React.PropTypes.func.isRequired,
diff --git a/resources/frontend_client/app/query_builder/sort_widget.react.js b/resources/frontend_client/app/query_builder/sort_widget.react.js
index 4466ab05119c9ced5eda8f8f2df6dd07d54ac43d..acfc609f28ab58b6cff63bd06c29202ac9700eda 100644
--- a/resources/frontend_client/app/query_builder/sort_widget.react.js
+++ b/resources/frontend_client/app/query_builder/sort_widget.react.js
@@ -1,7 +1,10 @@
 'use strict';
-/*global DateFilter, SelectionModule, Icon */
 
-var SortWidget = React.createClass({
+import DateFilter from './date_filter.react';
+import Icon from './icon.react';
+import SelectionModule from './selection_module.react';
+
+export default React.createClass({
     displayName: 'SortWidget',
     propTypes: {
         sort: React.PropTypes.array.isRequired,
diff --git a/resources/frontend_client/app/query_builder/visualization.react.js b/resources/frontend_client/app/query_builder/visualization.react.js
index a2e8a77a3bc6ece9ce8a1ee93be5abd3d90ebb81..7645a2977d866e7d43d518d10a72518f6d3026c7 100644
--- a/resources/frontend_client/app/query_builder/visualization.react.js
+++ b/resources/frontend_client/app/query_builder/visualization.react.js
@@ -1,9 +1,14 @@
 'use strict';
-/*global cx, CardRenderer, PopoverWithTrigger, QueryVisualizationTable, QueryVisualizationChart*/
 
+import { CardRenderer } from '../card/card.charting';
+import PopoverWithTrigger from './popover_with_trigger.react';
+import QueryVisualizationTable from './visualization_table.react';
+import QueryVisualizationChart from './visualization_chart.react';
+
+var cx = React.addons.classSet;
 var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
 
-var QueryVisualization = React.createClass({
+export default React.createClass({
     displayName: 'QueryVisualization',
     propTypes: {
         visualizationSettingsApi: React.PropTypes.object.isRequired,
diff --git a/resources/frontend_client/app/query_builder/visualization_chart.react.js b/resources/frontend_client/app/query_builder/visualization_chart.react.js
index 456f113dfefd6a5fe76aab74592f613682675cad..6b88f34483c3dbc35c7ffd9e32b0fb8ae56a4b39 100644
--- a/resources/frontend_client/app/query_builder/visualization_chart.react.js
+++ b/resources/frontend_client/app/query_builder/visualization_chart.react.js
@@ -1,7 +1,8 @@
 'use strict';
-/*global CardRenderer*/
 
-var QueryVisualizationChart = React.createClass({
+import { CardRenderer } from '../card/card.charting';
+
+export default React.createClass({
     displayName: 'QueryVisualizationChart',
     propTypes: {
         visualizationSettingsApi: React.PropTypes.object.isRequired,
diff --git a/resources/frontend_client/app/query_builder/visualization_table.react.js b/resources/frontend_client/app/query_builder/visualization_table.react.js
index 4be1dda0b45f168336ad8fc3e33679d101a15273..ad1a6cb7eb010b505a3eb660dc10174a31086435 100644
--- a/resources/frontend_client/app/query_builder/visualization_table.react.js
+++ b/resources/frontend_client/app/query_builder/visualization_table.react.js
@@ -1,7 +1,6 @@
 'use strict';
-/*global */
 
-var QueryVisualizationTable = React.createClass({
+export default React.createClass({
     displayName: 'QueryVisualizationTable',
     propTypes: {
         data: React.PropTypes.object
diff --git a/resources/frontend_client/index.html b/resources/frontend_client/index.html
index f3993006c8efba643e198fef5d6c5d0887cc9650..bd332b8c044fdfbb206096eba2af97dc39c2f5cb 100644
--- a/resources/frontend_client/index.html
+++ b/resources/frontend_client/index.html
@@ -7,12 +7,11 @@
         <meta name="apple-mobile-web-app-capable" content="yes" />
         <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
         <title>Metabase</title>
-        <link href="/app/bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet" />
-        <link href="/app/bower_components/angular-gridster/dist/angular-gridster.min.css" rel="stylesheet" />
-        <link href="/app/bower_components/react-date-picker/react-datepicker.css" rel="stylesheet" />
-        <link href="/app/bower_components/dc.js/dc.css" rel="stylesheet" />
-        <link rel="stylesheet" href="/app/dist/corvus.css" />
-        <script src="/app/bower_components/angular/angular.min.js"></script>
+
+        <link rel="stylesheet" href="/app/dist/styles.css"/>
+        <script charset="utf-8" src="/app/dist/styles.js"></script>
+        <script src="/app/dist/vendor.js"></script>
+        <script src="/app/dist/app.js"></script>
     </head>
 
     <body ng-controller="Corvus">
@@ -99,47 +98,6 @@
         <div class="MainContent flex flex-column full-height" ng-view></div>
     </body>
 
-    <!-- JS INCLUDES -->
-    <!-- vendor js -->
-    <script src="/app/bower_components/react/react-with-addons.min.js"></script>
-    <script src="/app/bower_components/react-onclickoutside/index.js"></script>
-    <script src="/app/bower_components/moment/min/moment.min.js"></script>
-    <script src="/app/bower_components/tether/tether.min.js"></script>
-    <script src="/app/bower_components/react-date-picker/react-datepicker.js"></script>
-
-
-    <script src="/app/bower_components/jquery/dist/jquery.min.js"></script>
-    <script src="/app/bower_components/underscore/underscore-min.js"></script>
-    <script src="/app/bower_components/fastclick/lib/fastclick.js"></script>
-
-    <script src="/app/bower_components/angular-route/angular-route.min.js"></script>
-    <script src="/app/bower_components/angular-resource/angular-resource.min.js"></script>
-    <script src="/app/bower_components/angular-cookies/angular-cookies.min.js"></script>
-    <script src="/app/bower_components/angular-animate/angular-animate.min.js"></script>
-    <script src="/app/bower_components/angular-sanitize/angular-sanitize.min.js"></script>
-    <script src="/app/bower_components/angular-xeditable/dist/js/xeditable.min.js"></script>
-    <script src="/app/bower_components/angular-cookie/angular-cookie.min.js"></script>
-    <script src="/app/bower_components/javascript-detect-element-resize/detect-element-resize.js"></script>
-    <script src="/app/bower_components/angular-gridster/dist/angular-gridster.min.js"></script>
-    <script src="/app/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>
-    <script src="/app/bower_components/angular-http-auth/src/http-auth-interceptor.js"></script>
-    <script src="/app/bower_components/ace-builds/src-min-noconflict/ace.js"></script>
-    <script src="/app/bower_components/ace-builds/src-min-noconflict/mode-sql.js"></script>
-    <script src="/app/bower_components/ace-builds/src-min-noconflict/ext-language_tools.js"></script>
-    <script src="/app/bower_components/angular-ui-ace/ui-ace.min.js"></script>
-    <script src="/app/bower_components/angularytics/dist/angularytics.min.js"></script>
-    <script src="/app/bower_components/ng-sortable/dist/ng-sortable.min.js"></script>
-    <script src="/app/bower_components/angular-readable-time/angular-readable-time.min.js"></script>
-
-    <script src="/app/bower_components/d3/d3.min.js"></script>
-    <script src="/app/bower_components/crossfilter/crossfilter.min.js"></script>
-    <script src="/app/bower_components/dc.js/dc.min.js"></script>
-
-    <script src="/app/js/google_maps.js"></script>
-
-    <!-- dist app -->
-    <script src="/app/dist/app.js"></script>
-    <script src="/app/dist/query_builder.js" type="text/javascript"></script>
 
     <script src="//ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"></script>
     <script>
diff --git a/resources/frontend_client/vendor.css b/resources/frontend_client/vendor.css
new file mode 100644
index 0000000000000000000000000000000000000000..1d7c86a15a706a892728c472ad5e2e4cb3307a31
--- /dev/null
+++ b/resources/frontend_client/vendor.css
@@ -0,0 +1,10 @@
+/* angular 3rd-party */
+@import 'angular-xeditable/dist/css/xeditable.css';
+@import 'angular-gridster/dist/angular-gridster.min.css';
+@import 'ng-sortable/dist/ng-sortable.css';
+
+/* d3 */
+@import 'dc/dc.css';
+
+/* react */
+@import 'react-datepicker/react-datepicker.css';
diff --git a/resources/frontend_client/vendor.js b/resources/frontend_client/vendor.js
new file mode 100644
index 0000000000000000000000000000000000000000..67a47aac61a8913734299bf9bf6fb5034aff178e
--- /dev/null
+++ b/resources/frontend_client/vendor.js
@@ -0,0 +1,36 @@
+/*global window*/
+
+"use strict";
+
+// angular:
+import 'angular';
+import 'angular-animate';
+import 'angular-cookies';
+import 'angular-resource';
+import 'angular-route';
+import 'angular-sanitize';
+
+// angular 3rd-party:
+import 'angular-bootstrap';
+import 'angular-cookie';
+import 'angular-gridster';
+import 'angular-http-auth'; // currently pulled from unofficial fork: https://github.com/witoldsz/angular-http-auth/pull/100
+import 'angular-readable-time';
+import 'angular-xeditable';
+import 'ng-sortable';
+import 'angularytics';
+
+// ace:
+import 'angular-ui-ace';
+import 'ace/ace';
+import 'ace/ext-language_tools';
+import 'ace/mode-sql';
+import 'ace/snippets/sql';
+
+// react:
+import React from 'react';
+window.React = React;
+
+// misc:
+import _ from 'underscore';
+window._ = _;
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..66dd2c3b11a2824da13f85cb976c997c3710cfb5
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,129 @@
+"use strict";
+/* global __dirname */
+
+var webpack = require('webpack');
+var webpackPostcssTools = require('webpack-postcss-tools');
+
+var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
+var NgAnnotatePlugin = require('ng-annotate-webpack-plugin');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+var _ = require('underscore');
+var glob = require('glob');
+
+var BASE_PATH = __dirname + '/resources/frontend_client/app/';
+
+// All JS files except dist and test
+var JS_SRC = glob.sync(BASE_PATH + '**/*.js', { ignore: BASE_PATH + '{bower_components,dist,test}/**/*.js' });
+// All CSS files in app/css and app/components
+var CSS_SRC = glob.sync(BASE_PATH + 'css/**/*.css').concat(glob.sync(BASE_PATH + 'components/**/*.css'));
+
+// Need to scan the CSS files for variable and custom media used across files
+// NOTE: this requires "webpack -w" (watch mode) to be restarted when variables change :(
+console.warn("Warning: in weback watch mode you must restart webpack if you change any CSS variables or custom media queries");
+var cssMaps = { vars: {}, media: {}, selector: {} };
+CSS_SRC.map(webpackPostcssTools.makeVarMap).forEach(function(map) {
+    for (var name in cssMaps) _.extend(cssMaps[name], map[name]);
+});
+
+module.exports = {
+    // output a bundle for the app JS and a bundle for styles
+    // eventually we should have multiple (single file) entry points for various pieces of the app to enable code splitting
+    entry: {
+        vendor: __dirname + '/resources/frontend_client/vendor.js',
+        app: JS_SRC,
+        styles: [
+            __dirname + '/resources/frontend_client/vendor.css'
+        ].concat(CSS_SRC)
+    },
+
+    // output to "dist"
+    output: {
+        path: __dirname + '/resources/frontend_client/app/dist',
+        filename: '[name].js'
+    },
+
+    module: {
+        loaders: [
+            // JavaScript
+            { test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { cacheDirectory: '.babel_cache' }},
+            { test: /\.js$/, exclude: /node_modules/, loader: 'eslint' },
+            // CSS
+            { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?sourceMap!cssnext-loader') }
+            // { test: /\.css$/, loader: 'style-loader!css-loader!cssnext-loader' }
+        ],
+        noParse: [
+            /node_modules\/(angular|ng-|ace|react|moment|underscore|jquery|d3|crossfilter)/ // doesn't include 'dc' and 'tether' due to use of 'require'
+        ]
+    },
+
+    resolve: {
+        modulesDirectories: [],
+        alias: {
+            'metabase':             __dirname + '/resources/frontend_client/app',
+
+            // angular
+            'angular':              __dirname + '/node_modules/angular/angular.min.js',
+            'angular-animate':      __dirname + '/node_modules/angular-animate/angular-animate.min.js',
+            'angular-cookies':      __dirname + '/node_modules/angular-cookies/angular-cookies.min.js',
+            'angular-resource':     __dirname + '/node_modules/angular-resource/angular-resource.min.js',
+            'angular-route':        __dirname + '/node_modules/angular-route/angular-route.min.js',
+            'angular-sanitize':     __dirname + '/node_modules/angular-sanitize/angular-sanitize.min.js',
+            // angular 3rd-party
+            'angular-bootstrap':    __dirname + '/node_modules/angular-bootstrap/dist/ui-bootstrap-tpls.min.js',
+            'angular-cookie':       __dirname + '/node_modules/angular-cookie/angular-cookie.min.js',
+            'angular-gridster':     __dirname + '/node_modules/angular-gridster/dist/angular-gridster.min.js',
+            'angular-http-auth':    __dirname + '/node_modules/angular-http-auth/src/http-auth-interceptor.js',
+            'angular-readable-time':__dirname + '/node_modules/angular-readable-time/angular-readable-time.min.js',
+            'angular-xeditable':    __dirname + '/node_modules/angular-xeditable/dist/js/xeditable.min.js',
+            'ng-sortable':          __dirname + '/node_modules/ng-sortable/dist/ng-sortable.min.js',
+            'angularytics':         __dirname + '/node_modules/angularytics/dist/angularytics.min.js',
+            'angular-ui-ace':       __dirname + '/node_modules/angular-ui-ace/src/ui-ace.js',
+            // ace
+            'ace/ace':              __dirname + '/node_modules/ace-builds/src-min-noconflict/ace.js',
+            'ace/ext-language_tools':__dirname+ '/node_modules/ace-builds/src-min-noconflict/ext-language_tools.js',
+            'ace/mode-sql':         __dirname + '/node_modules/ace-builds/src-min-noconflict/mode-sql.js',
+            'ace/snippets/sql':     __dirname + '/node_modules/ace-builds/src-min-noconflict/snippets/sql.js',
+            // react
+            'react':                __dirname + '/node_modules/react/dist/react-with-addons.js',
+            'react-onclickoutside': __dirname + '/node_modules/react-onclickoutside/index.js',
+            'react-datepicker':     __dirname + '/node_modules/react-datepicker/react-datepicker.js',
+            'moment':               __dirname + '/node_modules/moment/min/moment.min.js',
+            'tether':               __dirname + '/node_modules/tether/tether.min.js',
+            'underscore':           __dirname + '/node_modules/underscore/underscore-min.js',
+            'jquery':               __dirname + '/node_modules/jquery/dist/jquery.min.js',
+            'd3':                   __dirname + '/node_modules/d3/d3.min.js',
+            'crossfilter':          __dirname + '/node_modules/crossfilter/crossfilter.min.js',
+            'dc':                   __dirname + '/node_modules/dc/dc.min.js',
+        }
+    },
+
+    plugins: [
+        // Automatically annotates angular functions (from "function($foo) {}" to "['$foo', function($foo) {}]")
+        // so minification doesn't break dependency injections
+        // new NgAnnotatePlugin({ add: true }),
+        // Separates out modules common to multiple entry points into a single common file that should be loaded first.
+        // Not currently useful but necessary for code-splitting
+        // new CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
+        // Extracts initial CSS into a standard stylesheet that can be loaded in parallel with JavaScript
+        new ExtractTextPlugin('styles.css')
+    ],
+
+    // CSSNext configuration
+    cssnext: {
+        features: {
+            // pass in the variables and custom media we scanned for before
+            customProperties: { variables: cssMaps.vars },
+            customMedia: { extensions: cssMaps.media }
+        },
+        import: {
+            path: ['resources/frontend_client/app/css']
+        }
+    },
+
+    // SourceMaps
+    // Normal source map works better but takes longer to build
+    // devtool: 'source-map'
+    // Eval source map doesn't work with CSS but is faster to build
+    // devtool: 'eval-source-map'
+};