Add webpack code splitting module

Add cold-module replacement
Add webpack module hashing
This commit is contained in:
Berkeley Martinez
2016-03-20 21:43:36 -07:00
parent c77fcedcbb
commit 844afb6e2f
8 changed files with 189 additions and 203 deletions

View File

@ -23,9 +23,9 @@ import {
Rx.config.longStackSupport = !!debug.enabled; Rx.config.longStackSupport = !!debug.enabled;
const log = debug('fcc:client'); const log = debug('fcc:client');
const DOMContainer = document.getElementById('fcc');
const hotReloadTimeout = 5000; const hotReloadTimeout = 5000;
const csrfToken = window.__fcc__.csrf.token; const csrfToken = window.__fcc__.csrf.token;
const DOMContainer = document.getElementById('fcc');
const initialState = isColdStored() ? const initialState = isColdStored() ?
getColdStorage() : getColdStorage() :
window.__fcc__.data; window.__fcc__.data;

View File

@ -1,11 +1,24 @@
import Hikes from './components/Hikes.jsx';
import Hike from './components/Hike.jsx';
export default { export default {
path: 'videos', path: 'videos',
component: Hikes, getComponent(_, cb) {
childRoutes: [{ require.ensure(
[ './components/Hikes.jsx' ],
require => {
cb(null, require('./components/Hikes.jsx').default);
},
'hikes'
);
},
getChildRoutes(_, cb) {
require.ensure(
[ './components/Hike.jsx' ],
require => {
cb(null, [{
path: ':dashedName', path: ':dashedName',
component: Hike component: require('./components/Hike.jsx').default
}] }]);
},
'hikes'
);
}
}; };

View File

@ -1,34 +1,36 @@
import Jobs from './components/Jobs.jsx';
import NewJob from './components/NewJob.jsx';
import Show from './components/Show.jsx';
import Preview from './components/Preview.jsx';
import JobTotal from './components/JobTotal.jsx';
import NewJobCompleted from './components/NewJobCompleted.jsx';
/*
* index: /jobs list jobs
* show: /jobs/:id show one job
* create /jobs/new create a new job
*/
export default { export default {
childRoutes: [{ getChildRoutes: (_, cb) => {
require.ensure(
[
'./components/Jobs.jsx',
'./components/NewJob.jsx',
'./components/Preview.jsx',
'./components/JobTotal.jsx',
'./components/NewJobCompleted.jsx',
'./components/Show.jsx'
],
require => {
cb(null, [{
path: '/jobs', path: '/jobs',
component: Jobs component: require('./components/Jobs.jsx').default
}, { }, {
path: 'jobs/new', path: 'jobs/new',
component: NewJob component: require('./components/NewJob.jsx').default
}, { }, {
path: 'jobs/new/preview', path: 'jobs/new/preview',
component: Preview component: require('./components/Preview.jsx').default
}, { }, {
path: 'jobs/new/check-out', path: 'jobs/new/check-out',
component: JobTotal component: require('./components/JobTotal.jsx').default
}, { }, {
path: 'jobs/new/completed', path: 'jobs/new/completed',
component: NewJobCompleted component: require('./components/NewJobCompleted.jsx').default
}, { }, {
path: 'jobs/:id', path: 'jobs/:id',
component: Show component: require('./components/Show.jsx').default
}] }]);
},
'jobs'
);
}
}; };

View File

@ -5,27 +5,28 @@ require('babel-core/register');
var Rx = require('rx'), var Rx = require('rx'),
gulp = require('gulp'), gulp = require('gulp'),
path = require('path'), path = require('path'),
debug = require('debug')('fcc:gulp'),
yargs = require('yargs'),
sortKeys = require('sort-keys'),
del = require('del'),
// utils // utils
plumber = require('gulp-plumber'), plumber = require('gulp-plumber'),
notify = require('gulp-notify'), notify = require('gulp-notify'),
gutil = require('gulp-util'), gutil = require('gulp-util'),
reduce = require('gulp-reduce-file'), reduce = require('gulp-reduce-file'),
sortKeys = require('sort-keys'),
debug = require('debug')('fcc:gulp'),
yargs = require('yargs'),
concat = require('gulp-concat'), concat = require('gulp-concat'),
uglify = require('gulp-uglify'), uglify = require('gulp-uglify'),
merge = require('merge-stream'), merge = require('merge-stream'),
babel = require('gulp-babel'), babel = require('gulp-babel'),
sourcemaps = require('gulp-sourcemaps'), sourcemaps = require('gulp-sourcemaps'),
gulpif = require('gulp-if'),
// react app // react app
webpack = require('webpack'), webpack = require('webpack'),
webpackStream = require('webpack-stream'), webpackStream = require('webpack-stream'),
WebpackDevServer = require('webpack-dev-server'), WebpackDevServer = require('webpack-dev-server'),
webpackConfig = require('./webpack.config.js'), webpackConfig = require('./webpack.config.js'),
webpackConfigNode = require('./webpack.config.node.js'),
// server process // server process
nodemon = require('gulp-nodemon'), nodemon = require('gulp-nodemon'),
@ -190,7 +191,7 @@ function delRev(dest, manifestName) {
}); });
} }
gulp.task('serve', ['build-manifest'], function(cb) { gulp.task('serve', function(cb) {
var called = false; var called = false;
nodemon({ nodemon({
script: paths.server, script: paths.server,
@ -225,8 +226,7 @@ var syncDepenedents = [
'serve', 'serve',
'js', 'js',
'less', 'less',
'dependents', 'dependents'
'build-manifest'
]; ];
gulp.task('sync', syncDepenedents, function() { gulp.task('sync', syncDepenedents, function() {
@ -264,29 +264,43 @@ gulp.task('test-challenges', ['lint-json']);
gulp.task('pack-client', function() { gulp.task('pack-client', function() {
if (!__DEV__) { console.log('\n\nbundling production\n\n'); } if (!__DEV__) { console.log('\n\nbundling production\n\n'); }
var manifestName = 'react-manifest.json'; function condition(file) {
var filepath = file.relative;
return __DEV__ || (/json$/).test('' + filepath);
}
var dest = webpackConfig.output.path; var dest = webpackConfig.output.path;
return gulp.src(webpackConfig.entry) return gulp.src(webpackConfig.entry.bundle)
.pipe(plumber({ errorHandler: errorHandler })) .pipe(plumber({ errorHandler: errorHandler }))
.pipe(webpackStream(Object.assign( .pipe(webpackStream(Object.assign(
{}, {},
webpackConfig, webpackConfig,
webpackOptions webpackOptions
))) )))
.pipe(__DEV__ ? gutil.noop() : uglify()) .pipe(gulpif(condition, gutil.noop(), uglify()))
.pipe(gulp.dest(dest)) .pipe(gulp.dest(dest));
.pipe(rev()) });
// copy files to public
.pipe(gulp.dest(dest)) var webpackManifestFiles = [ 'react-manifest.json', 'chunk-manifest.json' ];
// create manifest gulp.task('move-webpack-manifest', ['pack-client'], function() {
.pipe(rev.manifest(manifestName)) var files = webpackManifestFiles.map(function(filename) {
// delete old rev return path.join(webpackConfig.output.path, filename);
.pipe(delRev( });
dest, return gulp.src(files).pipe(gulp.dest(paths.manifest));
manifestName });
))
.pipe(gulp.dest(paths.manifest)); var cleanDeps = ['pack-client', 'move-webpack-manifest'];
gulp.task('clean-webpack-manifest', cleanDeps, function() {
return del(webpackManifestFiles.map(function(filename) {
return path.join(webpackConfig.output.path, filename);
}))
.then(function(pathsDeleted) {
gutil.log('[clean-webpack-manifest]', 'paths deleted' + pathsDeleted);
})
.catch(function(err) {
throw new gutil.PluginError('clean-webpack-manifest', err);
});
}); });
var webpackCalled = false; var webpackCalled = false;
@ -304,10 +318,10 @@ gulp.task('webpack-dev-server', function(cb) {
contentBase: false, contentBase: false,
publicPath: '/js' publicPath: '/js'
}; };
webpackConfig.entry = [ webpackConfig.entry.bundle = [
'webpack-dev-server/client?http://localhost:2999/', 'webpack-dev-server/client?http://localhost:2999/',
'webpack/hot/dev-server' 'webpack/hot/dev-server'
].concat(webpackConfig.entry); ].concat(webpackConfig.entry.bundle);
var compiler = webpack(webpackConfig); var compiler = webpack(webpackConfig);
var devServer = new WebpackDevServer(compiler, devServerOptions); var devServer = new WebpackDevServer(compiler, devServerOptions);
@ -329,31 +343,6 @@ gulp.task('webpack-dev-server', function(cb) {
}); });
}); });
gulp.task('pack-watch-manifest', function() {
var manifestName = 'react-manifest.json';
var dest = webpackConfig.output.path;
return gulp.src(dest + '/bundle.js')
.pipe(rev())
// copy files to public
.pipe(gulp.dest(dest))
// create manifest
.pipe(rev.manifest(manifestName))
.pipe(delRev(
dest,
manifestName
))
.pipe(gulp.dest(paths.manifest));
});
gulp.task('pack-node', function() {
return gulp.src(webpackConfigNode.entry)
.pipe(plumber({ errorHandler: errorHandler }))
.pipe(webpack(webpackConfigNode))
.pipe(gulp.dest(webpackConfigNode.output.path));
});
gulp.task('pack', ['pack-client', 'pack-node']);
gulp.task('less', function() { gulp.task('less', function() {
var manifestName = 'css-manifest.json'; var manifestName = 'css-manifest.json';
var dest = paths.css; var dest = paths.css;
@ -482,24 +471,18 @@ function done(manifest) {
return sortKeys(manifest); return sortKeys(manifest);
} }
function buildManifest() { var buildDependents = [
'less',
'js',
'dependents',
'pack-client',
'move-webpack-manifest'
];
gulp.task('build-manifest', buildDependents, function() {
return gulp.src(paths.manifest + '*.json') return gulp.src(paths.manifest + '*.json')
.pipe(reduce('rev-manifest.json', collector, done, {})) .pipe(reduce('rev-manifest.json', collector, done, {}))
.pipe(gulp.dest('server/')); .pipe(gulp.dest('server/'));
}
var buildDependents = ['less', 'js', 'dependents'];
if (__DEV__) {
buildDependents.push('pack-watch-manifest');
}
gulp.task('build-manifest', buildDependents, function() {
return buildManifest();
});
gulp.task('build-manifest-watch', function() {
return buildManifest();
}); });
gulp.task('build', [ gulp.task('build', [
@ -507,6 +490,8 @@ gulp.task('build', [
'js', 'js',
'dependents', 'dependents',
'pack-client', 'pack-client',
'move-webpack-manifest',
'clean-webpack-manifest',
'build-manifest' 'build-manifest'
]); ]);
@ -515,8 +500,7 @@ var watchDependents = [
'js', 'js',
'dependents', 'dependents',
'serve', 'serve',
'sync', 'sync'
'build-manifest'
]; ];
gulp.task('reload', function() { gulp.task('reload', function() {
@ -533,14 +517,12 @@ gulp.task('watch', watchDependents, function() {
formatCommonFrameworkPaths.call(paths.commonFramework), formatCommonFrameworkPaths.call(paths.commonFramework),
['dependents'] ['dependents']
); );
gulp.watch(paths.manifest + '/*.json', ['build-manifest-watch']);
}); });
gulp.task('default', [ gulp.task('default', [
'less', 'less',
'serve', 'serve',
'webpack-dev-server', 'webpack-dev-server',
'build-manifest-watch',
'watch', 'watch',
'sync' 'sync'
]); ]);

View File

@ -28,16 +28,7 @@
"license": "(BSD-3-Clause AND CC-BY-SA-4.0)", "license": "(BSD-3-Clause AND CC-BY-SA-4.0)",
"dependencies": { "dependencies": {
"accepts": "^1.3.0", "accepts": "^1.3.0",
"adler32": "~0.1.7",
"async": "^1.5.0", "async": "^1.5.0",
"babel-cli": "^6.3.17",
"babel-core": "^6.3.26",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
"babel-register": "^6.3.0", "babel-register": "^6.3.0",
"body-parser": "^1.13.2", "body-parser": "^1.13.2",
"cheerio": "~0.20.0", "cheerio": "~0.20.0",
@ -52,8 +43,6 @@
"emmet-codemirror": "^1.2.5", "emmet-codemirror": "^1.2.5",
"errorhandler": "^1.4.2", "errorhandler": "^1.4.2",
"es6-map": "~0.1.1", "es6-map": "~0.1.1",
"eslint": "^3.1.0",
"eslint-plugin-react": "^5.1.1",
"express": "^4.13.3", "express": "^4.13.3",
"express-flash": "~0.0.2", "express-flash": "~0.0.2",
"express-session": "^1.12.1", "express-session": "^1.12.1",
@ -61,32 +50,15 @@
"express-validator": "^2.18.0", "express-validator": "^2.18.0",
"fetchr": "~0.5.12", "fetchr": "~0.5.12",
"frameguard": "^2.0.0", "frameguard": "^2.0.0",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.1",
"gulp-concat": "^2.6.0",
"gulp-eslint": "^3.0.1",
"gulp-jsonlint": "^1.1.0",
"gulp-less": "^3.0.3",
"gulp-nodemon": "^2.0.3",
"gulp-notify": "^2.2.0",
"gulp-plumber": "^1.0.1",
"gulp-reduce-file": "0.0.1",
"gulp-rev": "^7.0.0",
"gulp-rev-replace": "~0.4.2",
"gulp-uglify": "^1.5.1",
"gulp-util": "^3.0.6",
"helmet": "^2.0.0", "helmet": "^2.0.0",
"helmet-csp": "^1.0.3", "helmet-csp": "^1.0.3",
"history": "^2.0.0", "history": "^2.0.0",
"jade": "^1.11.0", "jade": "^1.11.0",
"json-loader": "~0.5.2",
"less": "^2.5.1",
"lodash": "^4.1.0", "lodash": "^4.1.0",
"loopback": "^2.22.0", "loopback": "^2.22.0",
"loopback-boot": "^2.13.0", "loopback-boot": "^2.13.0",
"loopback-component-passport": "^2.0.0", "loopback-component-passport": "^2.0.0",
"loopback-connector-mongodb": "1.15.2", "loopback-connector-mongodb": "1.15.2",
"merge-stream": "^1.0.0",
"method-override": "^2.3.0", "method-override": "^2.3.0",
"moment": "^2.10.2", "moment": "^2.10.2",
"moment-timezone": "^0.5.0", "moment-timezone": "^0.5.0",
@ -112,6 +84,7 @@
"react-dom": "^15.0.2", "react-dom": "^15.0.2",
"react-addons-css-transition-group": "^0.14.7", "react-addons-css-transition-group": "^0.14.7",
"react-css-transition-replace": "^1.1.0", "react-css-transition-replace": "^1.1.0",
"react-fontawesome": "^0.3.3",
"react-motion": "~0.4.2", "react-motion": "~0.4.2",
"react-no-ssr": "^1.0.1", "react-no-ssr": "^1.0.1",
"react-pure-render": "^1.0.2", "react-pure-render": "^1.0.2",
@ -126,31 +99,65 @@
"redux-form": "^5.2.3", "redux-form": "^5.2.3",
"request": "^2.65.0", "request": "^2.65.0",
"reselect": "^2.0.2", "reselect": "^2.0.2",
"rev-del": "^1.0.5",
"rx": "^4.0.0", "rx": "^4.0.0",
"sanitize-html": "^1.11.1", "sanitize-html": "^1.11.1",
"sort-keys": "^1.1.1",
"stampit": "^2.1.1", "stampit": "^2.1.1",
"store": "https://github.com/berkeleytrue/store.js.git#feature/noop-server", "store": "https://github.com/berkeleytrue/store.js.git#feature/noop-server",
"url-regex": "^3.0.0", "url-regex": "^3.0.0",
"validator": "^5.0.0", "validator": "^5.0.0",
"webpack": "^1.9.12",
"webpack-dev-server": "^1.14.1",
"webpack-stream": "^3.1.0",
"xss-filters": "^1.2.6", "xss-filters": "^1.2.6",
"yargs": "^4.1.0",
"snyk": "^1.17.1" "snyk": "^1.17.1"
}, },
"devDependencies": { "devDependencies": {
"adler32": "~0.1.7",
"babel-cli": "^6.3.17",
"babel-core": "^6.3.26",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
"browser-sync": "^2.9.12", "browser-sync": "^2.9.12",
"chunk-manifest-webpack-plugin": "0.0.1",
"del": "^2.2.0",
"eslint": "^3.1.0",
"eslint-plugin-react": "^5.1.1",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.1",
"gulp-concat": "^2.6.0",
"gulp-dest": "^0.2.3",
"gulp-eslint": "^3.0.1",
"gulp-if": "^2.0.0",
"gulp-jsonlint": "^1.1.0",
"gulp-less": "^3.0.3",
"gulp-nodemon": "^2.0.3",
"gulp-notify": "^2.2.0",
"gulp-plumber": "^1.0.1",
"gulp-reduce-file": "0.0.1",
"gulp-rev": "^7.0.0",
"gulp-rev-replace": "~0.4.2",
"gulp-sourcemaps": "^1.6.0", "gulp-sourcemaps": "^1.6.0",
"gulp-tape": "0.0.9", "gulp-tape": "0.0.9",
"gulp-uglify": "^1.5.1",
"gulp-util": "^3.0.6",
"happy": "0.0.1",
"json-loader": "~0.5.2",
"jsonlint": "^1.6.2", "jsonlint": "^1.6.2",
"less": "^2.5.1",
"loopback-component-explorer": "^2.1.1", "loopback-component-explorer": "^2.1.1",
"loopback-testing": "^1.1.0", "loopback-testing": "^1.1.0",
"merge-stream": "^1.0.0",
"rev-del": "^1.0.5",
"sinon": "^1.17.3", "sinon": "^1.17.3",
"sort-keys": "^1.1.1",
"tap-spec": "^4.1.1", "tap-spec": "^4.1.1",
"tape": "^4.2.2" "tape": "^4.2.2"
"webpack": "^1.9.12",
"webpack-dev-server": "^1.14.0",
"webpack-manifest-plugin": "^1.0.0",
"webpack-stream": "^3.1.0",
"yargs": "^4.1.0"
}, },
"snyk": true "snyk": true
} }

View File

@ -10,6 +10,22 @@ var _ = require('lodash'),
path = require('path'), path = require('path'),
setupPassport = require('./component-passport'); setupPassport = require('./component-passport');
// polyfill for webpack bundle splitting
const requireProto = Object.getPrototypeOf(require);
if (!requireProto.hasOwnProperty('ensure')) {
Object.defineProperties(
requireProto,
{
'ensure': {
value: function ensure(modules, callback) {
callback(this);
},
writable: false,
enumarble: false
}
}
);
}
Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production'; Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production';
var app = loopback(); var app = loopback();
var isBeta = !!process.env.BETA; var isBeta = !!process.env.BETA;

View File

@ -1,13 +1,20 @@
var webpack = require('webpack'); var webpack = require('webpack');
var path = require('path'); var path = require('path');
var ManifestPlugin = require('webpack-manifest-plugin');
var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
var __DEV__ = process.env.NODE_ENV !== 'production'; var __DEV__ = process.env.NODE_ENV !== 'production';
module.exports = { module.exports = {
entry: './client', entry: {
devtool: 'inline-source-map', bundle: './client'
},
devtool: __DEV__ ? 'inline-source-map' : null,
output: { output: {
filename: 'bundle.js', filename: __DEV__ ? 'bundle.js' : 'bundle-[hash].js',
chunkFilename: __DEV__ ?
'bundle-[name].js' :
'bundle-[name]-[chunkhash].js',
path: path.join(__dirname, '/public/js'), path: path.join(__dirname, '/public/js'),
publicPath: __DEV__ ? 'http://localhost:2999/js' : '/js' publicPath: __DEV__ ? 'http://localhost:2999/js' : '/js'
}, },
@ -42,8 +49,21 @@ module.exports = {
'NODE_ENV': JSON.stringify(__DEV__ ? 'development' : 'production') 'NODE_ENV': JSON.stringify(__DEV__ ? 'development' : 'production')
}, },
'__DEVTOOLS__': !__DEV__ '__DEVTOOLS__': !__DEV__
}), })
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
] ]
}; };
if (!__DEV__) {
module.exports.plugins.push(
new ManifestPlugin({ fileName: 'react-manifest.json' }),
new ChunkManifestPlugin({
filename: 'chunk-manifest.json',
manifestVariable: 'webpackManifest'
})
);
} else {
module.exports.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
);
}

View File

@ -1,54 +0,0 @@
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
var nodeModules = fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1;
})
.reduce(function(nodeModules, module) {
nodeModules[module] = 'commonjs ' + module;
return nodeModules;
}, {});
module.exports = {
devtool: 'sourcemap',
target: 'node',
entry: './common/app',
// keeps webpack from bundling modules
externals: nodeModules,
output: {
filename: 'app-stream.bundle.js',
path: path.join(__dirname, '/server'),
publicPath: 'public/'
},
module: {
loaders: [
{
test: /\.jsx?$/,
include: [
path.join(__dirname, 'client/'),
path.join(__dirname, 'common/')
],
loaders: [
'babel-loader'
]
},
{
test: /\.json$/,
loaders: [
'json-loader'
]
}
]
},
plugins: [
new webpack.BannerPlugin(
'require("source-map-support").install();',
{
raw: true,
entryOnly: false
}
)
]
};