Skip to content

Instantly share code, notes, and snippets.

@trotzig
Created September 16, 2016 11:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trotzig/2a95f39f83b2330f85d8b9fdaec67e2e to your computer and use it in GitHub Desktop.
Save trotzig/2a95f39f83b2330f85d8b9fdaec67e2e to your computer and use it in GitHub Desktop.
commit 7b5f5c739eefb8c5bca2981b007ab09b8df9b63a
Author: Henric Trotzig + Kevin Kehl <henric.trotzig+kevin.kehl@brigade.com>
Date: Mon Jul 11 21:46:09 2016 +0200
Load partner tools asynchronously
This is the first commit in a serious of commits aiming to split up our
application bundle into smaller pieces. By using the `lazy` support of
the bundle-loader webpack plugin [1], we can make require statements
resolve a function that can be used to asynchronously load a module
instead of resolving the module itself. We can then use that
lazy-loading function in our <App> router to dynamically load routes for
a specific part of our app.
The technique is inspired by
http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack/
which in turn is based on the Dynamic Routing technique:
https://github.com/reactjs/react-router/blob/master/docs/guides/DynamicRouting.md
When you now load a partner tools page (e.g. /partners/1), you'll see
two HTTP requests. Something like this:
- application.js
- assets11.11.js
Reducing the size of our application.js bundle should help make our
application faster, because there will be less to download and less code
to initialize.
[1]: https://github.com/webpack/bundle-loader
Change-Id: Ie694f3dae6d1f369b0d0e58b76a5632e630710ed
Reviewed-on: https://code.brigade.zone/15035
Reviewed-by: Kevin Kehl <kevin.kehl@brigade.com>
Reviewed-by: Tom Dooner <tom.dooner@brigade.com>
Tested-by: Leeroy Jenkins <jenkins@brigade.com>
diff --git a/app/assets/components/App/index.jsx b/app/assets/components/App/index.jsx
index b4319f5..a20c16d 100644
--- a/app/assets/components/App/index.jsx
+++ b/app/assets/components/App/index.jsx
@@ -1,4 +1,3 @@
-const IndexRoute = require('react-router/lib/IndexRoute');
const React = require('react');
const Redirect = require('react-router/lib/Redirect');
const Route = require('react-router/lib/Route');
@@ -6,8 +5,6 @@ const Router = require('react-router/lib/Router');
const AccountFormPage = require('components/pages/AccountFormPage');
const AlertsPage = require('components/pages/AlertsPage');
-const CampaignScreenerPositionsPage =
- require('components/partners/CampaignScreenerPositionsPage');
const CausesSignUpImmediatelyPage =
require('components/pages/CausesSignUpImmediatelyPage');
const CommPrefsPage = require('components/pages/CommPrefsPage');
@@ -43,13 +40,8 @@ const OnboardingFlowTopicPage =
require('components/pages/OnboardingFlowTopicPage');
const OnboardingFlowVoterIdentityPage =
require('components/pages/OnboardingFlowVoterIdentityPage');
-const PartnerCampaignPage = require('components/partners/PartnerCampaignPage');
-const PartnerCampaignUpdateTable =
- require('components/partners/PartnerCampaignUpdateTable');
-const PartnerHomePage = require('components/partners/PartnerHomePage');
-const PartnerPetitionTable =
- require('components/partners/PartnerPetitionTable');
-const PartnerToolsLayout = require('components/partners/Layout');
+const PartnerToolsRoutes =
+ require('bundle?lazy!components/routes/PartnerToolsRoutes');
const PartnersPage = require('components/pages/PartnersPage');
const PoliticianPage = require('components/pages/PoliticianPage');
const PopularPage = require('components/pages/PopularPage');
@@ -150,6 +142,20 @@ function redirectToHomeIfLoggedIn(nextState, replaceState) {
replaceState({}, Urls.loggedInHome());
}
+/**
+ * Utility function to lazy-load a module imported with the `bundle?lazy!`
+ * loader. Can be used in e.g. `getChildRoutes`, `getComponent` to make the
+ * module asynchronously load through a separate HTTP GET.
+ *
+ * @param {Function} lazyModule imported with the `bundle?lazy!` prefix.
+ * @return {Function}
+ */
+function lazyLoad(lazyModule) {
+ return (_, done) => {
+ lazyModule(module => done(null, module));
+ };
+}
+
let rendered = false;
const App = React.createClass({
@@ -310,18 +316,10 @@ const App = React.createClass({
path='/partners'
component={PartnersPage}
/>
- <Route path='/partners/:organizationId' component={PartnerToolsLayout}>
- <IndexRoute component={PartnerHomePage} />
- <Redirect
- from='campaigns/:campaignId'
- to='campaigns/:campaignId/updates'
- />
- <Route path='campaigns/:campaignId' component={PartnerCampaignPage}>
- <Route path='updates' component={PartnerCampaignUpdateTable} />
- <Route path='petitions' component={PartnerPetitionTable} />
- <Route path='screeners' component={CampaignScreenerPositionsPage} />
- </Route>
- </Route>
+ <Route
+ path='/partners'
+ getChildRoutes={lazyLoad(PartnerToolsRoutes)}
+ />
<Redirect
from='/petitions/:petitionId'
to='/'
diff --git a/app/assets/components/routes/PartnerToolsRoutes.jsx b/app/assets/components/routes/PartnerToolsRoutes.jsx
new file mode 100644
index 0000000..1ccc45b
--- /dev/null
+++ b/app/assets/components/routes/PartnerToolsRoutes.jsx
@@ -0,0 +1,29 @@
+const IndexRoute = require('react-router/lib/IndexRoute');
+const React = require('react');
+const Redirect = require('react-router/lib/Redirect');
+const Route = require('react-router/lib/Route');
+
+const CampaignScreenerPositionsPage =
+ require('components/partners/CampaignScreenerPositionsPage');
+const PartnerCampaignPage = require('components/partners/PartnerCampaignPage');
+const PartnerCampaignUpdateTable =
+ require('components/partners/PartnerCampaignUpdateTable');
+const PartnerHomePage = require('components/partners/PartnerHomePage');
+const PartnerPetitionTable =
+ require('components/partners/PartnerPetitionTable');
+const PartnerToolsLayout = require('components/partners/Layout');
+
+module.exports = [
+ <Route path=':organizationId' component={PartnerToolsLayout}>
+ <IndexRoute component={PartnerHomePage} />
+ <Redirect
+ from='campaigns/:campaignId'
+ to='campaigns/:campaignId/updates'
+ />
+ <Route path='campaigns/:campaignId' component={PartnerCampaignPage}>
+ <Route path='updates' component={PartnerCampaignUpdateTable} />
+ <Route path='petitions' component={PartnerPetitionTable} />
+ <Route path='screeners' component={CampaignScreenerPositionsPage} />
+ </Route>
+ </Route>
+];
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index ba724b7..63585af 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1476,6 +1476,11 @@
"from": "builtin-modules@https://npm.brigade.zone/builtin-modules/-/builtin-modules-1.1.1.tgz",
"resolved": "https://npm.brigade.zone/builtin-modules/-/builtin-modules-1.1.1.tgz"
},
+ "bundle-loader": {
+ "version": "0.5.4",
+ "from": "bundle-loader@https://npm.brigade.zone/bundle-loader/-/bundle-loader-0.5.4.tgz",
+ "resolved": "https://npm.brigade.zone/bundle-loader/-/bundle-loader-0.5.4.tgz"
+ },
"bytes": {
"version": "2.1.0",
"from": "bytes@https://npm.brigade.zone/bytes/-/bytes-2.1.0.tgz",
diff --git a/package.json b/package.json
index 170d4b2..1df3e81 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"babel-preset-stage-2": "^6.3.13",
"babel-runtime": "^6.3.19",
"branch-sdk": "^1.8.4",
+ "bundle-loader": "^0.5.4",
"classnames": "^2.2.3",
"compression-webpack-plugin": "^0.2.0",
"css-loader": "^0.23.0",
commit 8bd165c8b165268791e9c2620c8c7127376b4fbd
Author: Henric Trotzig <henric.trotzig@brigade.com>
Date: Thu Jul 14 12:15:01 2016 +0200
Load <DistricPage> asynchronously
Adding lazy-loading to our <App> component will help us keep the size
down of the main application.js bundle. Making <DistrictPage> load
asynchronously shaves off a significant chunk of code.
Change-Id: Ic0561bfc01b9901d0e5683fd8393acb7bb66ef19
Reviewed-on: https://code.brigade.zone/15163
Tested-by: Leeroy Jenkins <jenkins@brigade.com>
Reviewed-by: Kevin Kehl <kevin.kehl@brigade.com>
diff --git a/app/assets/components/App/index.jsx b/app/assets/components/App/index.jsx
index 6822941..f240b62 100644
--- a/app/assets/components/App/index.jsx
+++ b/app/assets/components/App/index.jsx
@@ -8,7 +8,7 @@ const AlertsPage = require('components/pages/AlertsPage');
const CausesSignUpImmediatelyPage =
require('components/pages/CausesSignUpImmediatelyPage');
const ConventionSignupPage = require('components/pages/ConventionSignupPage');
-const DistrictPage = require('components/pages/DistrictPage');
+const DistrictPage = require('bundle?lazy!components/pages/DistrictPage');
const GetVerifiedPage = require('components/pages/GetVerifiedPage');
const History = require('lib/history');
const HomePage = require('bundle?lazy!components/pages/HomePage');
@@ -162,7 +162,7 @@ const App = React.createClass({
/>
<Route
path='/districts/:districtId'
- component={DistrictPage.Controller}
+ getComponent={lazyLoad(DistrictPage)}
/>
<Redirect
from='/elections/:location'
commit 0d88af463990f3af90df9e5cc4f6192fecfae64b
Author: Henric Trotzig <henric.trotzig@brigade.com>
Date: Wed Jul 13 22:26:15 2016 +0200
Prepare webpack config for code splitting
I'm about to use a technique to split up our application bundle into
multiple pieces, loaded asynchronously based on what page you are
looking at. For that to work, webpack needs to be able to dynamically
fetch a script from the same origin as the original file. This will work
in dev, because we're setting the publicPath [1] to the location of the
webpack dev server. But for environments like production and test where
we use precompiled assets, we obviously can't fetch from the webpack dev
server.
I considered two approaches for this change:
A) introduce a new webpack.config.development.js config file
B) use an environment variable to special-case when the webpack
dev-server is used.
A would make our already complicated webpack configuration setup even
more complicated. B had a smaller footprint, so that's what I decided to
go with.
[1]: https://github.com/webpack/docs/wiki/configuration#outputpublicpath
Change-Id: I19b8b7e8d5c4ccec97a41474440493b299cc153e
Reviewed-on: https://code.brigade.zone/15117
Reviewed-by: Tom Dooner <tom.dooner@brigade.com>
Tested-by: Leeroy Jenkins <jenkins@brigade.com>
diff --git a/script/start-webpack b/script/start-webpack
index 3cbb346..b34f3dc 100755
--- a/script/start-webpack
+++ b/script/start-webpack
@@ -1,7 +1,9 @@
#!/bin/bash
+export WEBPACK_DEV_SERVER_ACTIVE=1
+
if [[ -n "$HOT_MODULES" ]]; then
- exec "$(npm bin)"/webpack-dev-server $@ --progress --colors --hot --host 0.0.0.0
+ exec "$(npm bin)"/webpack-dev-server "$@" --progress --colors --hot --host 0.0.0.0
else
- exec "$(npm bin)"/webpack-dev-server $@ --progress --colors --host 0.0.0.0
+ exec "$(npm bin)"/webpack-dev-server "$@" --progress --colors --host 0.0.0.0
fi
diff --git a/webpack.config.common.js b/webpack.config.common.js
index 22b1f5d..f52ca48 100644
--- a/webpack.config.common.js
+++ b/webpack.config.common.js
@@ -18,8 +18,17 @@ function appVersion() {
}
function appOrigin() {
- const appHost = process.env.APP_HOST || 'localhost';
- return 'http://' + appHost + ':8080';
+ if (process.env.WEBPACK_DEV_SERVER_ACTIVE) {
+ // In development, we need to specify a full URL so that the webpack dev
+ // server works correctly.
+ const appHost = process.env.APP_HOST || 'localhost';
+ return 'http://' + appHost + ':8080';
+ }
+
+ // In other environments (e.g. production, test), we have precompiled assets
+ // that we can resolve from the same host as where the generated bundle is
+ // located.
+ return '';
}
// Ensure the target directory exists.
@@ -76,7 +85,7 @@ module.exports = {
output: {
path: outputPath,
filename: '[name].js',
- publicPath: appOrigin() + '/assets'
+ publicPath: appOrigin() + '/assets/'
},
resolve: {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment