summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Schatz <jschatz@gitlab.com>2017-06-30 20:55:09 +0000
committerJacob Schatz <jschatz@gitlab.com>2017-06-30 20:55:09 +0000
commite48f54b5b23b4b865a4462151d576d2ff09d543e (patch)
treeff73231294c1ac2c6793fc9a46f3e93c88cd9734
parent30c7fef03955bb4d2627b1de419be672c97a6df6 (diff)
parent598baa554c29c7f69191b4ea46a049c558c3dbe5 (diff)
downloadgitlab-ce-e48f54b5b23b4b865a4462151d576d2ff09d543e.tar.gz
Merge branch 'enable-webpack-code-splitting' into 'master'
Enable webpack code splitting Closes #32989 See merge request !12032
-rw-r--r--.eslintrc1
-rw-r--r--app/assets/javascripts/awards_handler.js79
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js17
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js18
-rw-r--r--app/assets/javascripts/main.js4
-rw-r--r--app/assets/javascripts/notes.js10
-rw-r--r--app/assets/javascripts/webpack.js9
-rw-r--r--app/helpers/webpack_helper.rb23
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--changelogs/unreleased/enable-webpack-code-splitting.yml5
-rw-r--r--config/webpack.config.js4
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--package.json1
-rw-r--r--spec/javascripts/awards_handler_spec.js15
-rw-r--r--yarn.lock15
15 files changed, 137 insertions, 69 deletions
diff --git a/.eslintrc b/.eslintrc
index 73cd7ecf66d..c72a5e0335b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -11,6 +11,7 @@
"gon": false,
"localStorage": false
},
+ "parser": "babel-eslint",
"plugins": [
"filenames",
"import",
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index c34d80f0601..18cd04b176a 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -2,7 +2,6 @@
/* global Flash */
import Cookies from 'js-cookie';
-import * as Emoji from './emoji';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@@ -24,27 +23,9 @@ const categoryLabelMap = {
flags: 'Flags',
};
-function renderCategory(name, emojiList, opts = {}) {
- return `
- <h5 class="emoji-menu-title">
- ${name}
- </h5>
- <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
- ${emojiList.map(emojiName => `
- <li class="emoji-menu-list-item">
- <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
- ${Emoji.glEmojiTag(emojiName, {
- sprite: true,
- })}
- </button>
- </li>
- `).join('\n')}
- </ul>
- `;
-}
-
-export default class AwardsHandler {
- constructor() {
+class AwardsHandler {
+ constructor(emoji) {
+ this.emoji = emoji;
this.eventListeners = [];
// If the user shows intent let's pre-build the menu
this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
@@ -78,10 +59,10 @@ export default class AwardsHandler {
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
- const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+ const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
$target.closest('.js-awards-block').addClass('current');
- this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
+ this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
});
}
@@ -139,16 +120,16 @@ export default class AwardsHandler {
this.isCreatingEmojiMenu = true;
// Render the first category
- const categoryMap = Emoji.getEmojiCategoryMap();
+ const categoryMap = this.emoji.getEmojiCategoryMap();
const categoryNameKey = Object.keys(categoryMap)[0];
const emojisInCategory = categoryMap[categoryNameKey];
- const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
+ const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
// Render the frequently used
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
let frequentlyUsedCatgegory = '';
if (frequentlyUsedEmojis.length > 0) {
- frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, {
+ frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, {
menuListClass: 'frequent-emojis',
});
}
@@ -179,7 +160,7 @@ export default class AwardsHandler {
}
this.isAddingRemainingEmojiMenuCategories = true;
- const categoryMap = Emoji.getEmojiCategoryMap();
+ const categoryMap = this.emoji.getEmojiCategoryMap();
// Avoid the jank and render the remaining categories separately
// This will take more time, but makes UI more responsive
@@ -191,7 +172,7 @@ export default class AwardsHandler {
promiseChain.then(() =>
new Promise((resolve) => {
const emojisInCategory = categoryMap[categoryNameKey];
- const categoryMarkup = renderCategory(
+ const categoryMarkup = this.renderCategory(
categoryLabelMap[categoryNameKey],
emojisInCategory,
);
@@ -216,6 +197,25 @@ export default class AwardsHandler {
});
}
+ renderCategory(name, emojiList, opts = {}) {
+ return `
+ <h5 class="emoji-menu-title">
+ ${name}
+ </h5>
+ <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
+ ${emojiList.map(emojiName => `
+ <li class="emoji-menu-list-item">
+ <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
+ ${this.emoji.glEmojiTag(emojiName, {
+ sprite: true,
+ })}
+ </button>
+ </li>
+ `).join('\n')}
+ </ul>
+ `;
+ }
+
positionMenu($menu, $addBtn) {
const position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element
@@ -234,7 +234,7 @@ export default class AwardsHandler {
}
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
- const normalizedEmoji = Emoji.normalizeEmojiName(emoji);
+ const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
@@ -249,7 +249,7 @@ export default class AwardsHandler {
this.checkMutuality(votesBlock, emoji);
}
this.addEmojiToFrequentlyUsedList(emoji);
- const normalizedEmoji = Emoji.normalizeEmojiName(emoji);
+ const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
if ($emojiButton.length > 0) {
if (this.isActive($emojiButton)) {
@@ -374,7 +374,7 @@ export default class AwardsHandler {
createAwardButtonForVotesBlock(votesBlock, emojiName) {
const buttonHtml = `
<button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
- ${Emoji.glEmojiTag(emojiName)}
+ ${this.emoji.glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span>
</button>
`;
@@ -440,7 +440,7 @@ export default class AwardsHandler {
}
addEmojiToFrequentlyUsedList(emoji) {
- if (Emoji.isEmojiNameValid(emoji)) {
+ if (this.emoji.isEmojiNameValid(emoji)) {
this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
}
@@ -450,7 +450,7 @@ export default class AwardsHandler {
return this.frequentlyUsedEmojis || (() => {
const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
- inputName => Emoji.isEmojiNameValid(inputName),
+ inputName => this.emoji.isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
@@ -493,7 +493,7 @@ export default class AwardsHandler {
}
findMatchingEmojiElements(query) {
- const emojiMatches = Emoji.filterEmojiNamesByAlias(query);
+ const emojiMatches = this.emoji.filterEmojiNamesByAlias(query);
const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
const $matchingElements = $emojiElements
.filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0);
@@ -507,3 +507,12 @@ export default class AwardsHandler {
$('.emoji-menu').remove();
}
}
+
+let awardsHandlerPromise = null;
+export default function loadAwardsHandler(reload = false) {
+ if (!awardsHandlerPromise || reload) {
+ awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji')
+ .then(Emoji => new AwardsHandler(Emoji));
+ }
+ return awardsHandlerPromise;
+}
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index 8156e491a42..7e98e04303a 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,5 +1,4 @@
import installCustomElements from 'document-register-element';
-import { emojiImageTag, emojiFallbackImageSrc } from '../emoji';
import isEmojiUnicodeSupported from '../emoji/support';
installCustomElements(window);
@@ -32,11 +31,19 @@ export default function installGlEmojiElement() {
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
- } else if (hasImageFallback) {
- this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
- const src = emojiFallbackImageSrc(name);
- this.innerHTML = emojiImageTag(name, src);
+ import(/* webpackChunkName: 'emoji' */ '../emoji')
+ .then(({ emojiImageTag, emojiFallbackImageSrc }) => {
+ if (hasImageFallback) {
+ this.innerHTML = emojiImageTag(name, fallbackSrc);
+ } else {
+ const src = emojiFallbackImageSrc(name);
+ this.innerHTML = emojiImageTag(name, src);
+ }
+ })
+ .catch(() => {
+ // do nothing
+ });
}
}
};
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 10a64f9032b..2c56b718212 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,4 +1,3 @@
-import { validEmojiNames, glEmojiTag } from './emoji';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
@@ -373,7 +372,12 @@ class GfmAutoComplete {
if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]);
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
- this.loadData($input, at, validEmojiNames);
+ import(/* webpackChunkName: 'emoji' */ './emoji')
+ .then(({ validEmojiNames, glEmojiTag }) => {
+ this.loadData($input, at, validEmojiNames);
+ GfmAutoComplete.glEmojiTag = glEmojiTag;
+ })
+ .catch(() => { this.isLoadingData[at] = false; });
} else {
AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true)
.then((data) => {
@@ -428,12 +432,14 @@ GfmAutoComplete.atTypeMap = {
};
// Emoji
+GfmAutoComplete.glEmojiTag = null;
GfmAutoComplete.Emoji = {
templateFunction(name) {
- return `<li>
- ${name} ${glEmojiTag(name)}
- </li>
- `;
+ // glEmojiTag helper is loaded on-demand in fetchData()
+ if (GfmAutoComplete.glEmojiTag) {
+ return `<li>${name} ${GfmAutoComplete.glEmojiTag(name)}</li>`;
+ }
+ return `<li>${name}</li>`;
},
};
// Team Members
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index de1b658f602..fe752d95b90 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -70,7 +70,7 @@ import './ajax_loading_spinner';
import './api';
import './aside';
import './autosave';
-import AwardsHandler from './awards_handler';
+import loadAwardsHandler from './awards_handler';
import './breakpoints';
import './broadcast_message';
import './build';
@@ -355,7 +355,7 @@ $(function () {
$window.off('resize.app').on('resize.app', function () {
return fitSidebarForSize();
});
- gl.awardsHandler = new AwardsHandler();
+ loadAwardsHandler();
new Aside();
gl.utils.renderTimeago();
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 194d1730f3d..555b8c8a65c 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -18,6 +18,7 @@ import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import CommentTypeToggle from './comment_type_toggle';
+import loadAwardsHandler from './awards_handler';
import './autosave';
import './dropzone_input';
import './task_list';
@@ -291,8 +292,13 @@ export default class Notes {
if ('emoji_award' in noteEntity.commands_changes) {
votesBlock = $('.js-awards-block').eq(0);
- gl.awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
- return gl.awardsHandler.scrollToAwards();
+
+ loadAwardsHandler().then((awardsHandler) => {
+ awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
+ awardsHandler.scrollToAwards();
+ }).catch(() => {
+ // ignore
+ });
}
}
}
diff --git a/app/assets/javascripts/webpack.js b/app/assets/javascripts/webpack.js
new file mode 100644
index 00000000000..9a9cf395fb8
--- /dev/null
+++ b/app/assets/javascripts/webpack.js
@@ -0,0 +1,9 @@
+/**
+ * This is the first script loaded by webpack's runtime. It is used to manually configure
+ * config.output.publicPath to account for relative_url_root or CDN settings which cannot be
+ * baked-in to our webpack bundles.
+ */
+
+if (gon && gon.webpack_public_path) {
+ __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line
+}
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 6bacda9fe75..0386df22374 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -11,20 +11,29 @@ module WebpackHelper
paths = Webpack::Rails::Manifest.asset_paths(source)
if extension
- paths = paths.select { |p| p.ends_with? ".#{extension}" }
+ paths.select! { |p| p.ends_with? ".#{extension}" }
end
- # include full webpack-dev-server url for rspec tests running locally
+ force_host = webpack_public_host
+ if force_host
+ paths.map! { |p| "#{force_host}#{p}" }
+ end
+
+ paths
+ end
+
+ def webpack_public_host
if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled
host = Rails.configuration.webpack.dev_server.host
port = Rails.configuration.webpack.dev_server.port
protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
-
- paths.map! do |p|
- "#{protocol}://#{host}:#{port}#{p}"
- end
+ "#{protocol}://#{host}:#{port}"
+ else
+ ActionController::Base.asset_host.try(:chomp, '/')
end
+ end
- paths
+ def webpack_public_path
+ "#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/"
end
end
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index cc710f4ec7d..abb6dc2e9f3 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -38,7 +38,7 @@
= Gon::Base.render_data
- = webpack_bundle_tag "runtime"
+ = webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "locale"
= webpack_bundle_tag "main"
diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml
new file mode 100644
index 00000000000..d61c3b97d11
--- /dev/null
+++ b/changelogs/unreleased/enable-webpack-code-splitting.yml
@@ -0,0 +1,5 @@
+---
+title: Enable support for webpack code-splitting by dynamically setting publicPath
+ at runtime
+merge_request: 12032
+author:
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 90ef6a5448b..cbb0a899638 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -71,6 +71,7 @@ var config = {
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
peek: './peek.js',
+ webpack_runtime: './webpack.js',
},
output: {
@@ -190,7 +191,7 @@ var config = {
// create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({
- names: ['main', 'locale', 'common', 'runtime'],
+ names: ['main', 'locale', 'common', 'webpack_runtime'],
}),
],
@@ -245,7 +246,6 @@ if (IS_DEV_SERVER) {
hot: DEV_SERVER_LIVERELOAD,
inline: DEV_SERVER_LIVERELOAD
};
- config.output.publicPath = '//' + DEV_SERVER_HOST + ':' + DEV_SERVER_PORT + config.output.publicPath;
config.plugins.push(
// watch node_modules for changes if we encounter a missing module compile error
new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules'))
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 319633656ff..2d1ae6a5925 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -2,11 +2,14 @@
module Gitlab
module GonHelper
+ include WebpackHelper
+
def add_gon_variables
gon.api_version = 'v4'
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
+ gon.webpack_public_path = webpack_public_path
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
diff --git a/package.json b/package.json
index 045f07ee2f9..5a997e813f8 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"dependencies": {
"babel-core": "^6.22.1",
+ "babel-eslint": "^7.2.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-define": "^1.2.0",
"babel-preset-latest": "^6.24.0",
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 3fc03324d16..8e056882108 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
import Cookies from 'js-cookie';
-import AwardsHandler from '~/awards_handler';
+import loadAwardsHandler from '~/awards_handler';
import '~/lib/utils/common_utils';
@@ -26,14 +26,13 @@ import '~/lib/utils/common_utils';
describe('AwardsHandler', function() {
preloadFixtures('issues/issue_with_comment.html.raw');
- beforeEach(function() {
+ beforeEach(function(done) {
loadFixtures('issues/issue_with_comment.html.raw');
- awardsHandler = new AwardsHandler;
- spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
- return function(button, url, emoji, cb) {
- return cb();
- };
- })(this));
+ loadAwardsHandler(true).then((obj) => {
+ awardsHandler = obj;
+ spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
+ done();
+ }).catch(fail);
let isEmojiMenuBuilt = false;
openAndWaitForEmojiMenu = function() {
diff --git a/yarn.lock b/yarn.lock
index b902d5235d0..b04eebe60af 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -265,6 +265,15 @@ babel-core@^6.22.1, babel-core@^6.23.0:
slash "^1.0.0"
source-map "^0.5.0"
+babel-eslint@^7.2.1:
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f"
+ dependencies:
+ babel-code-frame "^6.22.0"
+ babel-traverse "^6.23.1"
+ babel-types "^6.23.0"
+ babylon "^6.16.1"
+
babel-generator@^6.18.0, babel-generator@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5"
@@ -816,10 +825,14 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23
lodash "^4.2.0"
to-fast-properties "^1.0.1"
-babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0:
+babylon@^6.11.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
+babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1:
+ version "6.16.1"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
+
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"