Commit 6ce4a60b authored by Eric Eastwood's avatar Eric Eastwood

Dynamic page scripts based on webpack build chunks

Part of !1322
parent ddf39f29
......@@ -44,6 +44,7 @@ gulp.task('process:assemble:copy-app:files', function() {
'npm-shrinkwrap.json',
'preinstall.sh',
'config/**',
'output/assets/js/webpack-manifest.json',
'public/templates/**',
'public/layouts/**',
'public/js/**',
......
......@@ -5,9 +5,8 @@ function create() {
var webpackMiddleware = require('webpack-dev-middleware');
return webpackMiddleware(webpack(require('../webpack.config')), {
noInfo: false,
quiet: true,
lazy: true,
logLevel: 'warn',
lazy: false,
watchOptions: {
aggregateTimeout: 400
},
......
'use strict';
const _ = require('lodash');
const cdn = require('gitter-web-cdn');
function cdnUrlGenerator(url, options = {}) {
if (options.root) {
return options.root + url;
}
return cdn(url, {});
}
let webpackBuildManifest;
function generateAssetsForChunk(chunkName) {
// We have this loaded just in time so we can wait for the initial Gitter boot-up that creates the manifest
try {
webpackBuildManifest =
// eslint-disable-next-line node/no-unpublished-require, node/no-missing-require
webpackBuildManifest || require('../../../output/assets/js/webpack-manifest.json');
} catch (err) {
throw new Error(
`You probably need to wait for the Gitter webpack build to finish. Error occured while requiring \`output/assets/js/webpack-manifest.json\`: ${err}\n${
err.stack
}`
);
}
const defaultAssets = webpackBuildManifest.entrypoints.default.assets || [];
const entryAssets = webpackBuildManifest.entrypoints[chunkName].assets;
const assets = Object.keys(
defaultAssets
.concat(entryAssets)
.filter(asset => !/.*\.map$/.test(asset))
.reduce((assetMap, asset) => {
assetMap[asset] = true;
return assetMap;
}, {})
);
return assets;
}
const bootScriptHelper = _.memoize(function(chunkName, parameters) {
const options = parameters.hash;
const jsRoot = (options && options.jsRoot) || 'js';
const assets = generateAssetsForChunk(chunkName);
const baseUrl = cdnUrlGenerator(jsRoot + '/', options);
const chunkScriptList = assets.map(asset => {
const cdnUrl = cdnUrlGenerator(`${jsRoot}/${asset}`, options);
return `<script type="text/javascript" src="${cdnUrl}"></script>`;
});
return `
<script type="text/javascript">window.webpackPublicPath = '${baseUrl}';</script>
${chunkScriptList.join('\n')}
`;
});
module.exports = {
generateAssetsForChunk,
bootScriptHelper
};
......@@ -8,11 +8,14 @@ var cdn = require('gitter-web-cdn');
var pluralize = require('../shared/helpers/pluralize');
var when = require('../shared/helpers/when');
exports.cdn = function(url, parameters) {
// eslint-disable-next-line node/no-unpublished-require, node/no-missing-require
const webpackBuildManifest = require('../../../output/assets/js/webpack-manifest.json');
function cdnHelper(url, parameters) {
return cdn(url, parameters ? parameters.hash : null);
};
}
function cdnUrlGenerator(url, options) {
function cdnUrlGenerator(url, options = {}) {
if (options.root) {
return options.root + url;
}
......@@ -20,29 +23,39 @@ function cdnUrlGenerator(url, options) {
return cdn(url, {});
}
exports.bootScript = function(url, parameters) {
var options = parameters.hash;
var jsRoot = (options && options.jsRoot) || 'js';
function generateAssetsForChunk(chunkName) {
const defaultAssets = webpackBuildManifest.entrypoints.default.assets || [];
const entryAssets = webpackBuildManifest.entrypoints[chunkName].assets;
const assets = Object.keys(
defaultAssets
.concat(entryAssets)
.filter(asset => !/.*\.map$/.test(asset))
.reduce((assetMap, asset) => {
assetMap[asset] = true;
return assetMap;
}, {})
);
var baseUrl = cdnUrlGenerator(jsRoot + '/', options);
var webpackRuntimeScriptUrl = cdnUrlGenerator(jsRoot + '/runtime.js', options);
var defaultScriptUrl = cdnUrlGenerator(jsRoot + '/default.chunk.js', options);
var vendorScriptUrl = cdnUrlGenerator(jsRoot + '/vendor.chunk.js', options);
var bootScriptUrl = cdnUrlGenerator(jsRoot + '/' + url + '.chunk.js', options);
return assets;
}
return util.format(
"<script type='text/javascript'>window.webpackPublicPath = '%s';</script>" +
"<script type='text/javascript' src='%s'></script>" +
"<script type='text/javascript' src='%s'></script>" +
"<script type='text/javascript' src='%s'></script>" +
"<script type='text/javascript' src='%s'></script>",
baseUrl,
webpackRuntimeScriptUrl,
defaultScriptUrl,
vendorScriptUrl,
bootScriptUrl
);
};
const bootScript = _.memoize(function(chunkName, parameters) {
const options = parameters.hash;
const jsRoot = (options && options.jsRoot) || 'js';
const assets = generateAssetsForChunk(chunkName);
const baseUrl = cdnUrlGenerator(jsRoot + '/', options);
const chunkScriptList = assets.map(asset => {
const cdnUrl = cdnUrlGenerator(`${jsRoot}/${asset}`, options);
return `<script type="text/javascript" src="${cdnUrl}"></script>`;
});
return `
<script type="text/javascript">window.webpackPublicPath = '${baseUrl}';</script>
${chunkScriptList.join('\n')}
`;
});
function createEnv(context, options) {
if (options) {
......@@ -56,7 +69,7 @@ function createEnv(context, options) {
}
return clientEnv;
}
exports.generateEnv = function(parameters) {
function generateEnv(parameters) {
var options = parameters.hash;
var env = createEnv(this, options);
......@@ -67,9 +80,9 @@ exports.generateEnv = function(parameters) {
';' +
'</script>'
);
};
}
exports.generateTroupeContext = function(troupeContext, parameters) {
function generateTroupeContext(troupeContext, parameters) {
var options = parameters.hash;
var env = createEnv(this, options);
......@@ -84,16 +97,13 @@ exports.generateTroupeContext = function(troupeContext, parameters) {
';' +
'</script>'
);
};
exports.pluralize = pluralize;
exports.when = when;
}
exports.toLowerCase = function(str) {
function toLowerCase(str) {
return str.toLowerCase();
};
}
exports.pad = function(options) {
function pad(options) {
var content = '' + options.fn(this);
var width = options.hash.width || 40;
var directionRight = options.hash.direction ? options.hash.direction === 'right' : true;
......@@ -106,10 +116,10 @@ exports.pad = function(options) {
}
}
return content;
};
}
// FIXME REMOVE THIS ONCE THE NEW ERRORS PAGES ARE DONE
exports.typewriter = function(el, str) {
function typewriter(el, str) {
return util.format(
'<script type="text/javascript">\n' +
'var text = "%s";' +
......@@ -128,25 +138,40 @@ exports.typewriter = function(el, str) {
str,
el
);
};
}
exports.formatNumber = function(n) {
function formatNumber(n) {
if (n < 1000) return n;
if (n < 1000000) return (n / 1000).toFixed(1) + 'k';
return (n / 100000).toFixed(1) + 'm';
};
}
/** FIXME we do not yet cover the ONE-TO-ONE case, also need to do better default values
* githubTypeToClass() takes a GitHub type and provdides a css class
*
*/
exports.githubTypeToClass = function(type) {
function githubTypeToClass(type) {
if (/_CHANNEL/.test(type)) return 'icon-hash';
else if (/REPO/.test(type)) return 'octicon-repo';
else if (/ORG/.test(type)) return 'octicon-organization';
else return 'default';
};
}
exports.getRoomName = function(name) {
function getRoomName(name) {
return name.split('/')[1] || 'general';
}
module.exports = {
cdn: cdnHelper,
bootScript,
generateEnv,
generateTroupeContext,
pluralize,
when,
toLowerCase,
pad,
typewriter,
formatNumber,
githubTypeToClass,
getRoomName
};
......@@ -11,7 +11,6 @@ const getPostcssStack = require('@gitterhq/styleguide/postcss-stack');
// Default to production unless we know for sure we are in dev
const IS_PRODUCTION = process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'dev';
const WEBPACK_DEV_MODE = process.env.WEBPACK_DEV_MODE === '1';
const WEBPACK_REPORT = process.env.WEBPACK_REPORT;
const ROOT_PATH = path.resolve(__dirname, '../../');
......@@ -51,8 +50,8 @@ const webpackConfig = {
},
output: {
path: path.resolve(__dirname, '../../output/assets/js/'),
filename: '[name].js',
chunkFilename: '[id].chunk.js',
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js',
publicPath: '/_s/l/js/',
devtoolModuleFilenameTemplate: '[resource-path]',
devtoolFallbackModuleFilenameTemplate: '[resource-path]?[hash]'
......@@ -132,10 +131,6 @@ const webpackConfig = {
}
},
plugins: [
// TODO: Use this in the future to for the bootScript in `hbs-helpers.js`
// and `sub-resources.js`. See how GitLab does it
// https://gitlab.com/gitlab-org/gitlab-ce/blob/dd26a9addc5dd654e3c8eecb58216f1f4449cfc1/lib/gitlab/webpack/manifest.rb
// https://gitlab.com/gitlab-org/gitlab-ce/blob/dd26a9addc5dd654e3c8eecb58216f1f4449cfc1/app/helpers/webpack_helper.rb
new StatsWriterPlugin({
filename: 'webpack-manifest.json',
transform: function(data, opts) {
......@@ -170,12 +165,12 @@ const webpackConfig = {
bail: true
};
if (WEBPACK_DEV_MODE) {
if (IS_PRODUCTION) {
webpackConfig.devtool = 'source-map';
} else {
// See http://webpack.github.io/docs/configuration.html#devtool
webpackConfig.devtool = 'cheap-source-map';
webpackConfig.cache = true;
} else {
webpackConfig.devtool = 'source-map';
}
if (process.env.WEBPACK_VISUALIZER) {
......
......@@ -3,9 +3,9 @@
var cdn = require('gitter-web-cdn');
function cdnSubResources(resources, jsRoot) {
var resourceList = ['runtime', 'default.chunk', 'vendor.chunk'];
var resourceList = [];
if (resources) {
resourceList = resourceList.concat(resources.map(resource => `${resource}.chunk`));
resourceList = resourceList.concat(resources);
}
return resourceList
......@@ -15,16 +15,7 @@ function cdnSubResources(resources, jsRoot) {
.concat(cdn('fonts/sourcesans/SourceSansPro-Regular.otf.woff'));
}
var SUBRESOURCE_MAPPINGS = {
'router-app': ['router-app', 'router-chat'],
'mobile-nli-chat': ['mobile-nli-chat', 'router-nli-chat'],
'mobile-userhome': ['mobile-userhome'],
userhome: ['userhome'],
'router-chat': ['router-chat'],
'router-nli-chat': ['router-nli-chat'],
'mobile-chat': ['mobile-chat'],
'router-mobile-app': ['mobile-app']
};
var SUBRESOURCE_MAPPINGS = {};
var CACHED_SUBRESOURCES = Object.keys(SUBRESOURCE_MAPPINGS).reduce(function(memo, key) {
memo[key] = cdnSubResources(SUBRESOURCE_MAPPINGS[key], 'js');
......
......@@ -8,8 +8,6 @@ exports.install = function(app) {
var webpackMiddleware = require('webpack-dev-middleware'); // eslint-disable-line node/no-unpublished-require
var webpack = require('webpack'); // eslint-disable-line node/no-unpublished-require
process.env.WEBPACK_DEV_MODE = '1';
app.use(
webpackMiddleware(webpack(require('../../public/js/webpack.config')), {
logLevel: 'warn',
......@@ -18,6 +16,11 @@ exports.install = function(app) {
aggregateTimeout: 400
},
publicPath: '/_s/l/js/',
writeToDisk: filePath => {
// We use the `webpack-manifest.json` in `hbs-helpers`
// to determine which dynamic/dependent chunks to serve
return /webpack-manifest\.json$/.test(filePath);
},
stats: {
colors: true
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment