Commit 73fd426d authored by Mike Greiling's avatar Mike Greiling Committed by Clement Ho

Upgrade to Webpack 4

parent 52df6f78
...@@ -4,7 +4,9 @@ import ide from './components/ide.vue'; ...@@ -4,7 +4,9 @@ import ide from './components/ide.vue';
import store from './stores'; import store from './stores';
import router from './ide_router'; import router from './ide_router';
function initIde(el) { Vue.use(Translate);
export function initIde(el) {
if (!el) return null; if (!el) return null;
return new Vue({ return new Vue({
...@@ -26,8 +28,12 @@ function initIde(el) { ...@@ -26,8 +28,12 @@ function initIde(el) {
}); });
} }
const ideElement = document.getElementById('ide'); // tell webpack to load assets from origin so that web workers don't break
export function resetServiceWorkersPublicPath() {
Vue.use(Translate); // __webpack_public_path__ is a global variable that can be used to adjust
// the webpack publicPath setting at runtime.
initIde(ideElement); // see: https://webpack.js.org/guides/public-path/
const relativeRootPath = (gon && gon.relative_url_root) || '';
const webpackAssetPath = `${relativeRootPath}/assets/webpack/`;
__webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase
}
/* eslint-disable import/first */
/* global $ */ /* global $ */
import jQuery from 'jquery'; import jQuery from 'jquery';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import svg4everybody from 'svg4everybody'; import svg4everybody from 'svg4everybody';
// expose common libraries as globals (TODO: remove these) // bootstrap webpack, common libs, polyfills, and behaviors
window.jQuery = jQuery; import './webpack';
window.$ = jQuery; import './commons';
import './behaviors';
// lib/utils // lib/utils
import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils'; import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils';
import { localTimeAgo } from './lib/utils/datetime_utility'; import { localTimeAgo } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility'; import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
import './behaviors/';
// everything else // everything else
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import bp from './breakpoints'; import bp from './breakpoints';
...@@ -31,11 +28,14 @@ import initLogoAnimation from './logo'; ...@@ -31,11 +28,14 @@ import initLogoAnimation from './logo';
import './milestone_select'; import './milestone_select';
import './projects_dropdown'; import './projects_dropdown';
import initBreadcrumbs from './breadcrumb'; import initBreadcrumbs from './breadcrumb';
import initDispatcher from './dispatcher';
// EE-only scripts // EE-only scripts
import 'ee/main'; import 'ee/main'; // eslint-disable-line import/first
import initDispatcher from './dispatcher'; // expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
// inject test utilities if necessary // inject test utilities if necessary
if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) { if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) {
...@@ -55,10 +55,14 @@ document.addEventListener('beforeunload', () => { ...@@ -55,10 +55,14 @@ document.addEventListener('beforeunload', () => {
}); });
window.addEventListener('hashchange', handleLocationHash); window.addEventListener('hashchange', handleLocationHash);
window.addEventListener('load', function onLoad() { window.addEventListener(
window.removeEventListener('load', onLoad, false); 'load',
handleLocationHash(); function onLoad() {
}, false); window.removeEventListener('load', onLoad, false);
handleLocationHash();
},
false,
);
gl.lazyLoader = new LazyLoader({ gl.lazyLoader = new LazyLoader({
scrollContainer: window, scrollContainer: window,
...@@ -92,9 +96,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -92,9 +96,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (bootstrapBreakpoint === 'xs') { if (bootstrapBreakpoint === 'xs') {
const $rightSidebar = $('aside.right-sidebar, .layout-page'); const $rightSidebar = $('aside.right-sidebar, .layout-page');
$rightSidebar $rightSidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed');
} }
// prevent default action for disabled buttons // prevent default action for disabled buttons
...@@ -111,7 +113,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -111,7 +113,8 @@ document.addEventListener('DOMContentLoaded', () => {
addSelectOnFocusBehaviour('.js-select-on-focus'); addSelectOnFocusBehaviour('.js-select-on-focus');
$('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() { $('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() {
$(this).tooltip('destroy') $(this)
.tooltip('destroy')
.closest('li') .closest('li')
.fadeOut(); .fadeOut();
}); });
...@@ -121,7 +124,9 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -121,7 +124,9 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
$('.js-remove-tr').on('ajax:success', function removeTRAjaxSuccessCallback() { $('.js-remove-tr').on('ajax:success', function removeTRAjaxSuccessCallback() {
$(this).closest('tr').fadeOut(); $(this)
.closest('tr')
.fadeOut();
}); });
// Initialize select2 selects // Initialize select2 selects
...@@ -158,7 +163,9 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -158,7 +163,9 @@ document.addEventListener('DOMContentLoaded', () => {
// Form submitter // Form submitter
$('.trigger-submit').on('change', function triggerSubmitCallback() { $('.trigger-submit').on('change', function triggerSubmitCallback() {
$(this).parents('form').submit(); $(this)
.parents('form')
.submit();
}); });
localTimeAgo($('abbr.timeago, .js-timeago'), true); localTimeAgo($('abbr.timeago, .js-timeago'), true);
...@@ -207,9 +214,15 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -207,9 +214,15 @@ document.addEventListener('DOMContentLoaded', () => {
$this.toggleClass('active'); $this.toggleClass('active');
if ($this.hasClass('active')) { if ($this.hasClass('active')) {
notesHolders.show().find('.hide, .content').show(); notesHolders
.show()
.find('.hide, .content')
.show();
} else { } else {
notesHolders.hide().find('.content').hide(); notesHolders
.hide()
.find('.content')
.hide();
} }
$(document).trigger('toggle.comments'); $(document).trigger('toggle.comments');
...@@ -250,9 +263,11 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -250,9 +263,11 @@ document.addEventListener('DOMContentLoaded', () => {
const flashContainer = document.querySelector('.flash-container'); const flashContainer = document.querySelector('.flash-container');
if (flashContainer && flashContainer.children.length) { if (flashContainer && flashContainer.children.length) {
flashContainer.querySelectorAll('.flash-alert, .flash-notice, .flash-success').forEach((flashEl) => { flashContainer
removeFlashClickListener(flashEl); .querySelectorAll('.flash-alert, .flash-notice, .flash-success')
}); .forEach(flashEl => {
removeFlashClickListener(flashEl);
});
} }
initDispatcher(); initDispatcher();
......
import { initIde, resetServiceWorkersPublicPath } from '~/ide/index';
document.addEventListener('DOMContentLoaded', () => {
const ideElement = document.getElementById('ide');
if (ideElement) {
resetServiceWorkersPublicPath();
initIde(ideElement);
}
});
require 'webpack/rails/manifest' require 'gitlab/webpack/manifest'
module WebpackHelper module WebpackHelper
def webpack_bundle_tag(bundle, force_same_domain: false) def webpack_bundle_tag(bundle)
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain)) javascript_include_tag(*webpack_entrypoint_paths(bundle))
end end
def webpack_controller_bundle_tags def webpack_controller_bundle_tags
bundles = [] chunks = []
action = case controller.action_name action = case controller.action_name
when 'create' then 'new' when 'create' then 'new'
...@@ -16,37 +16,44 @@ module WebpackHelper ...@@ -16,37 +16,44 @@ module WebpackHelper
route = [*controller.controller_path.split('/'), action].compact route = [*controller.controller_path.split('/'), action].compact
until route.empty? until chunks.any? || route.empty?
entrypoint = "pages.#{route.join('.')}"
begin begin
asset_paths = gitlab_webpack_asset_paths("pages.#{route.join('.')}", extension: 'js') chunks = webpack_entrypoint_paths(entrypoint, extension: 'js')
bundles.unshift(*asset_paths) rescue Gitlab::Webpack::Manifest::AssetMissingError
rescue Webpack::Rails::Manifest::EntryPointMissingError
# no bundle exists for this path # no bundle exists for this path
end end
route.pop route.pop
end end
javascript_include_tag(*bundles) if chunks.empty?
chunks = webpack_entrypoint_paths("default", extension: 'js')
end
javascript_include_tag(*chunks)
end end
# override webpack-rails gem helper until changes can make it upstream def webpack_entrypoint_paths(source, extension: nil, exclude_duplicates: true)
def gitlab_webpack_asset_paths(source, extension: nil, force_same_domain: false)
return "" unless source.present? return "" unless source.present?
paths = Webpack::Rails::Manifest.asset_paths(source) paths = Gitlab::Webpack::Manifest.entrypoint_paths(source)
if extension if extension
paths.select! { |p| p.ends_with? ".#{extension}" } paths.select! { |p| p.ends_with? ".#{extension}" }
end end
unless force_same_domain force_host = webpack_public_host
force_host = webpack_public_host if force_host
if force_host paths.map! { |p| "#{force_host}#{p}" }
paths.map! { |p| "#{force_host}#{p}" }
end
end end
paths if exclude_duplicates
@used_paths ||= []
new_paths = paths - @used_paths
@used_paths += new_paths
new_paths
else
paths
end
end end
def webpack_public_host def webpack_public_host
......
- @body_class = 'ide' - @body_class = 'ide'
- page_title 'IDE' - page_title 'IDE'
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'ide', force_same_domain: true
#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'), #ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'),
"no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'), "no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'),
"committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg') } } "committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg') } }
......
...@@ -38,9 +38,6 @@ ...@@ -38,9 +38,6 @@
= yield :library_javascripts = yield :library_javascripts
= javascript_include_tag locale_path unless I18n.locale == :en = javascript_include_tag locale_path unless I18n.locale == :en
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if Gitlab::CurrentSettings.clientside_sentry_enabled = webpack_bundle_tag "raven" if Gitlab::CurrentSettings.clientside_sentry_enabled
- if content_for?(:page_specific_javascripts) - if content_for?(:page_specific_javascripts)
......
...@@ -34,7 +34,7 @@ if app.config.serve_static_files ...@@ -34,7 +34,7 @@ if app.config.serve_static_files
) )
app.config.middleware.insert_before( app.config.middleware.insert_before(
Gitlab::Middleware::Static, Gitlab::Middleware::Static,
Gitlab::Middleware::WebpackProxy, Gitlab::Webpack::DevServerMiddleware,
proxy_path: app.config.webpack.public_path, proxy_path: app.config.webpack.public_path,
proxy_host: dev_server.host, proxy_host: dev_server.host,
proxy_port: dev_server.port proxy_port: dev_server.port
......
...@@ -12,16 +12,14 @@ function fatalError(message) { ...@@ -12,16 +12,14 @@ function fatalError(message) {
process.exit(1); process.exit(1);
} }
// remove problematic plugins // disable problematic options
if (webpackConfig.plugins) { webpackConfig.entry = undefined;
webpackConfig.plugins = webpackConfig.plugins.filter(function(plugin) { webpackConfig.mode = 'development';
return !( webpackConfig.optimization.runtimeChunk = false;
plugin instanceof webpack.optimize.CommonsChunkPlugin || webpackConfig.optimization.splitChunks = false;
plugin instanceof webpack.optimize.ModuleConcatenationPlugin ||
plugin instanceof webpack.DefinePlugin // use quicker sourcemap option
); webpackConfig.devtool = 'cheap-inline-source-map';
});
}
const specFilters = argumentsParser const specFilters = argumentsParser
.option( .option(
...@@ -77,9 +75,6 @@ if (specFilters.length) { ...@@ -77,9 +75,6 @@ if (specFilters.length) {
); );
} }
webpackConfig.entry = undefined;
webpackConfig.devtool = 'cheap-inline-source-map';
// Karma configuration // Karma configuration
module.exports = function(config) { module.exports = function(config) {
process.env.TZ = 'Etc/UTC'; process.env.TZ = 'Etc/UTC';
......
const crypto = require('crypto');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const glob = require('glob'); const glob = require('glob');
...@@ -6,9 +5,7 @@ const webpack = require('webpack'); ...@@ -6,9 +5,7 @@ const webpack = require('webpack');
const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin');
const NameAllModulesPlugin = require('name-all-modules-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ROOT_PATH = path.resolve(__dirname, '..'); const ROOT_PATH = path.resolve(__dirname, '..');
const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_PRODUCTION = process.env.NODE_ENV === 'production';
...@@ -21,10 +18,12 @@ const NO_COMPRESSION = process.env.NO_COMPRESSION; ...@@ -21,10 +18,12 @@ const NO_COMPRESSION = process.env.NO_COMPRESSION;
let autoEntriesCount = 0; let autoEntriesCount = 0;
let watchAutoEntries = []; let watchAutoEntries = [];
const defaultEntries = ['./main'];
function generateEntries() { function generateEntries() {
// generate automatic entry points // generate automatic entry points
const autoEntries = {}; const autoEntries = {};
const autoEntriesMap = {};
const pageEntries = glob.sync('pages/**/index.js', { const pageEntries = glob.sync('pages/**/index.js', {
cwd: path.join(ROOT_PATH, 'app/assets/javascripts'), cwd: path.join(ROOT_PATH, 'app/assets/javascripts'),
}); });
...@@ -33,7 +32,7 @@ function generateEntries() { ...@@ -33,7 +32,7 @@ function generateEntries() {
function generateAutoEntries(path, prefix = '.') { function generateAutoEntries(path, prefix = '.') {
const chunkPath = path.replace(/\/index\.js$/, ''); const chunkPath = path.replace(/\/index\.js$/, '');
const chunkName = chunkPath.replace(/\//g, '.'); const chunkName = chunkPath.replace(/\//g, '.');
autoEntries[chunkName] = `${prefix}/${path}`; autoEntriesMap[chunkName] = `${prefix}/${path}`;
} }
pageEntries.forEach(path => generateAutoEntries(path)); pageEntries.forEach(path => generateAutoEntries(path));
...@@ -43,22 +42,35 @@ function generateEntries() { ...@@ -43,22 +42,35 @@ function generateEntries() {
cwd: path.join(ROOT_PATH, 'ee/app/assets/javascripts'), cwd: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
}); });
eePageEntries.forEach(path => generateAutoEntries(path, 'ee')); eePageEntries.forEach(path => generateAutoEntries(path, 'ee'));
watchAutoEntries.concat(path.join(ROOT_PATH, 'ee/app/assets/javascripts/pages/')); watchAutoEntries.push(path.join(ROOT_PATH, 'ee/app/assets/javascripts/pages/'));
autoEntriesCount = Object.keys(autoEntries).length; const autoEntryKeys = Object.keys(autoEntriesMap);
autoEntriesCount = autoEntryKeys.length;
// import ancestor entrypoints within their children
autoEntryKeys.forEach(entry => {
const entryPaths = [autoEntriesMap[entry]];
const segments = entry.split('.');
while (segments.pop()) {
const ancestor = segments.join('.');
if (autoEntryKeys.includes(ancestor)) {
entryPaths.unshift(autoEntriesMap[ancestor]);
}
}
autoEntries[entry] = defaultEntries.concat(entryPaths);
});
const manualEntries = { const manualEntries = {
common: './commons/index.js', default: defaultEntries,
main: './main.js',
raven: './raven/index.js', raven: './raven/index.js',
webpack_runtime: './webpack.js',
ide: './ide/index.js',
}; };
return Object.assign(manualEntries, autoEntries); return Object.assign(manualEntries, autoEntries);
} }
const config = { const config = {
mode: IS_PRODUCTION ? 'production' : 'development',
context: path.join(ROOT_PATH, 'app/assets/javascripts'), context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: generateEntries, entry: generateEntries,
...@@ -66,8 +78,36 @@ const config = { ...@@ -66,8 +78,36 @@ const config = {
output: { output: {
path: path.join(ROOT_PATH, 'public/assets/webpack'), path: path.join(ROOT_PATH, 'public/assets/webpack'),
publicPath: '/assets/webpack/', publicPath: '/assets/webpack/',
filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js', filename: IS_PRODUCTION ? '[name].[chunkhash:8].bundle.js' : '[name].bundle.js',
chunkFilename: IS_PRODUCTION ? '[name].[chunkhash].chunk.js' : '[name].chunk.js', chunkFilename: IS_PRODUCTION ? '[name].[chunkhash:8].chunk.js' : '[name].chunk.js',
globalObject: 'this', // allow HMR and web workers to play nice
},
optimization: {
nodeEnv: false,
runtimeChunk: 'single',
splitChunks: {
maxInitialRequests: 4,
cacheGroups: {
default: false,
common: () => ({
priority: 20,
name: 'main',
chunks: 'initial',
minChunks: autoEntriesCount * 0.9,
}),
vendors: {
priority: 10,
chunks: 'async',
test: /[\\/](node_modules|vendor[\\/]assets[\\/]javascripts)[\\/]/,
},
commons: {
chunks: 'all',
minChunks: 2,
reuseExistingChunk: true,
},
},
},
}, },
module: { module: {
...@@ -99,10 +139,10 @@ const config = { ...@@ -99,10 +139,10 @@ const config = {
{ {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
inline: true, name: '[name].[hash:8].worker.js',
}, },
}, },
{ loader: 'babel-loader' }, 'babel-loader',
], ],
}, },
{ {
...@@ -110,7 +150,7 @@ const config = { ...@@ -110,7 +150,7 @@ const config = {
exclude: /node_modules/, exclude: /node_modules/,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: '[name].[hash].[ext]', name: '[name].[hash:8].[ext]',
}, },
}, },
{ {
...@@ -121,7 +161,7 @@ const config = { ...@@ -121,7 +161,7 @@ const config = {
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
name: '[name].[hash].[ext]', name: '[name].[hash:8].[ext]',
}, },
}, },
], ],
...@@ -131,7 +171,7 @@ const config = { ...@@ -131,7 +171,7 @@ const config = {
include: /node_modules\/katex\/dist\/fonts/, include: /node_modules\/katex\/dist\/fonts/,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: '[name].[hash].[ext]', name: '[name].[hash:8].[ext]',
}, },
}, },
{ {
...@@ -173,54 +213,6 @@ const config = { ...@@ -173,54 +213,6 @@ const config = {
jQuery: 'jquery', jQuery: 'jquery',
}), }),
// assign deterministic module ids
new webpack.NamedModulesPlugin(),
new NameAllModulesPlugin(),
// assign deterministic chunk ids
new webpack.NamedChunksPlugin(chunk => {
if (chunk.name) {
return chunk.name;
}
const moduleNames = [];
function collectModuleNames(m) {
// handle ConcatenatedModule which does not have resource nor context set
if (m.modules) {
m.modules.forEach(collectModuleNames);
return;
}
const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
if (m.resource.indexOf(pagesBase) === 0) {
moduleNames.push(
path
.relative(pagesBase, m.resource)
.replace(/\/index\.[a-z]+$/, '')
.replace(/\//g, '__')
);
} else {
moduleNames.push(path.relative(m.context, m.resource));
}
}
chunk.forEachModule(collectModuleNames);
const hash = crypto
.createHash('sha256')
.update(moduleNames.join('_'))
.digest('hex');
return `${moduleNames[0]}-${hash.substr(0, 6)}`;
}),
// create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'common', 'webpack_runtime'],
}),
// copy pre-compiled vendor libraries verbatim // copy pre-compiled vendor libraries verbatim
new CopyWebpackPlugin([ new CopyWebpackPlugin([
{ {
...@@ -273,20 +265,6 @@ const config = { ...@@ -273,20 +265,6 @@ const config = {
if (IS_PRODUCTION) { if (IS_PRODUCTION) {
config.devtool = 'source-map'; config.devtool = 'source-map';
config.plugins.push(
new webpack.NoEmitOnErrorsPlugin(),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false,
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
}),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify('production') },
})
);
// compression can require a lot of compute time and is disabled in CI // compression can require a lot of compute time and is disabled in CI
if (!NO_COMPRESSION) { if (!NO_COMPRESSION) {
...@@ -305,29 +283,30 @@ if (IS_DEV_SERVER) { ...@@ -305,29 +283,30 @@ if (IS_DEV_SERVER) {
hot: DEV_SERVER_LIVERELOAD, hot: DEV_SERVER_LIVERELOAD,
inline: DEV_SERVER_LIVERELOAD, inline: DEV_SERVER_LIVERELOAD,
}; };
config.plugins.push( config.plugins.push({
// watch node_modules for changes if we encounter a missing module compile error apply(compiler) {
new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules')), compiler.hooks.emit.tapAsync('WatchForChangesPlugin', (compilation, callback) => {
const missingDeps = Array.from(compilation.missingDependencies);
// watch for changes to our automatic entry point modules const nodeModulesPath = path.join(ROOT_PATH, 'node_modules');
{ const hasMissingNodeModules = missingDeps.some(
apply(compiler) { file => file.indexOf(nodeModulesPath) !== -1
compiler.plugin('emit', (compilation, callback) => { );
compilation.contextDependencies = [
...compilation.contextDependencies, // watch for changes to missing node_modules
...watchAutoEntries, if (hasMissingNodeModules) compilation.contextDependencies.add(nodeModulesPath);
];
// watch for changes to automatic entrypoints
// report our auto-generated bundle count watchAutoEntries.forEach(watchPath => compilation.contextDependencies.add(watchPath));
console.log(
`${autoEntriesCount} entries from '/pages' automatically added to webpack output.` // report our auto-generated bundle count
); console.log(
`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`
callback(); );
});
}, callback();
} });
); },
});
if (DEV_SERVER_LIVERELOAD) { if (DEV_SERVER_LIVERELOAD) {
config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.plugins.push(new webpack.HotModuleReplacementPlugin());
} }
......
...@@ -5,7 +5,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -5,7 +5,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelector('.js-service-desk-issues').dataset.supportBot, document.querySelector('.js-service-desk-issues').dataset.supportBot,
); );
this.filteredSearchManager = new FilteredSearchServiceDesk(supportBotData); const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
this.filteredSearchManager.setup(); filteredSearchManager.setup();
}); });
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
# :nocov: # :nocov:
module Gitlab module Gitlab
module Middleware module Webpack
class WebpackProxy < Rack::Proxy class DevServerMiddleware < Rack::Proxy
def initialize(app = nil, opts = {}) def initialize(app = nil, opts = {})
@proxy_host = opts.fetch(:proxy_host, 'localhost') @proxy_host = opts.fetch(:proxy_host, 'localhost')
@proxy_port = opts.fetch(:proxy_port, 3808) @proxy_port = opts.fetch(:proxy_port, 3808)
......
require 'webpack/rails/manifest'
module Gitlab
module Webpack
class Manifest < ::Webpack::Rails::Manifest
# Raised if a supplied asset does not exist in the webpack manifest
AssetMissingError = Class.new(StandardError)
class << self
def entrypoint_paths(source)
raise ::Webpack::Rails::Manifest::WebpackError, manifest["errors"] unless manifest_bundled?
entrypoint = manifest["entrypoints"][source]
if entrypoint && entrypoint["assets"]
# Can be either a string or an array of strings.
# Do not include source maps as they are not javascript
[entrypoint["assets"]].flatten.reject { |p| p =~ /.*\.map$/ }.map do |p|
"/#{::Rails.configuration.webpack.public_path}/#{p}"
end
else
raise AssetMissingError, "Can't find entry point '#{source}' in webpack manifest"
end
end
end
end
end
end
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
"@gitlab-org/gitlab-svgs": "^1.18.0", "@gitlab-org/gitlab-svgs": "^1.18.0",
"autosize": "^4.0.0", "autosize": "^4.0.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"babel-core": "^6.26.0", "babel-core": "^6.26.3",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.4",
"babel-plugin-transform-define": "^1.3.0", "babel-plugin-transform-define": "^1.3.0",
"babel-preset-latest": "^6.24.1", "babel-preset-latest": "^6.24.1",
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.24.1",
...@@ -30,11 +30,11 @@ ...@@ -30,11 +30,11 @@
"chart.js": "1.0.2", "chart.js": "1.0.2",
"classlist-polyfill": "^1.2.0", "classlist-polyfill": "^1.2.0",
"clipboard": "^1.7.1", "clipboard": "^1.7.1",
"compression-webpack-plugin": "^1.1.7", "compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.4.1", "copy-webpack-plugin": "^4.5.1",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"cropper": "^2.3.0", "cropper": "^2.3.0",
"css-loader": "^0.28.9", "css-loader": "^0.28.11",
"d3-array": "^1.2.1", "d3-array": "^1.2.1",
"d3-axis": "^1.0.8", "d3-axis": "^1.0.8",
"d3-brush": "^1.0.4", "d3-brush": "^1.0.4",
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
"dropzone": "^4.2.0", "dropzone": "^4.2.0",
"emoji-unicode-version": "^0.2.1", "emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"file-loader": "^1.1.8", "file-loader": "^1.1.11",
"fuzzaldrin-plus": "^0.5.0", "fuzzaldrin-plus": "^0.5.0",
"glob": "^7.1.2", "glob": "^7.1.2",
"imports-loader": "^0.8.0", "imports-loader": "^0.8.0",
...@@ -66,24 +66,22 @@ ...@@ -66,24 +66,22 @@
"marked": "^0.3.12", "marked": "^0.3.12",
"monaco-editor": "0.10.0", "monaco-editor": "0.10.0",
"mousetrap": "^1.4.6", "mousetrap": "^1.4.6",
"name-all-modules-plugin": "^1.0.1",
"pikaday": "^1.6.1", "pikaday": "^1.6.1",
"prismjs": "^1.6.0", "prismjs": "^1.6.0",
"raphael": "^2.2.7", "raphael": "^2.2.7",
"raven-js": "^3.22.1", "raven-js": "^3.22.1",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"react-dev-utils": "^5.0.0",
"sanitize-html": "^1.16.1", "sanitize-html": "^1.16.1",
"select2": "3.5.2-browserify", "select2": "3.5.2-browserify",
"sql.js": "^0.4.0", "sql.js": "^0.4.0",
"style-loader": "^0.20.2", "style-loader": "^0.21.0",
"svg4everybody": "2.1.9", "svg4everybody": "2.1.9",
"three": "^0.84.0", "three": "^0.84.0",
"three-orbit-controls": "^82.1.0", "three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4", "three-stl-loader": "^1.0.4",
"timeago.js": "^3.0.2", "timeago.js": "^3.0.2",
"underscore": "^1.8.3", "underscore": "^1.8.3",
"url-loader": "^0.6.2", "url-loader": "^1.0.1",
"visibilityjs": "^1.2.4", "visibilityjs": "^1.2.4",
"vue": "^2.5.16", "vue": "^2.5.16",
"vue-loader": "^14.1.1", "vue-loader": "^14.1.1",
...@@ -92,10 +90,11 @@ ...@@ -92,10 +90,11 @@
"vue-template-compiler": "^2.5.16", "vue-template-compiler": "^2.5.16",
"vue-virtual-scroll-list": "^1.2.5", "vue-virtual-scroll-list": "^1.2.5",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"webpack": "^3.11.0", "webpack": "^4.7.0",
"webpack-bundle-analyzer": "^2.10.0", "webpack-bundle-analyzer": "^2.11.1",
"webpack-stats-plugin": "^0.1.5", "webpack-cli": "^2.1.2",
"worker-loader": "^1.1.0" "webpack-stats-plugin": "^0.2.1",
"worker-loader": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"axios-mock-adapter": "^1.10.0", "axios-mock-adapter": "^1.10.0",
...@@ -126,8 +125,8 @@ ...@@ -126,8 +125,8 @@
"karma-mocha-reporter": "^2.2.5", "karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "3.0.0", "karma-webpack": "3.0.0",
"nodemon": "^1.15.1", "nodemon": "^1.17.3",
"prettier": "1.11.1", "prettier": "1.11.1",
"webpack-dev-server": "^2.11.2" "webpack-dev-server": "^3.1.4"
} }
} }
require 'spec_helper' require 'spec_helper'
feature 'RavenJS' do feature 'RavenJS' do
let(:raven_path) { '/raven.bundle.js' } let(:raven_path) { '/raven.chunk.js' }
it 'should not load raven if sentry is disabled' do it 'should not load raven if sentry is disabled' do
visit new_user_session_path visit new_user_session_path
......
...@@ -5,7 +5,7 @@ import mockData from './linked_pipelines_mock_data'; ...@@ -5,7 +5,7 @@ import mockData from './linked_pipelines_mock_data';
const LinkedPipeline = Vue.extend(LinkedPipelineComponent); const LinkedPipeline = Vue.extend(LinkedPipelineComponent);
const mockPipeline = mockData.triggered[0]; const mockPipeline = mockData.triggered[0];
describe('Linked pipeline', () => { describe('Linked pipeline', function() {
beforeEach(() => { beforeEach(() => {
this.propsData = { this.propsData = {
pipelineId: mockPipeline.id, pipelineId: mockPipeline.id,
...@@ -38,7 +38,9 @@ describe('Linked pipeline', () => { ...@@ -38,7 +38,9 @@ describe('Linked pipeline', () => {
}); });
it('should render the project name', () => { it('should render the project name', () => {
const projectNameElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-project-name'); const projectNameElement = this.linkedPipeline.$el.querySelector(
'.linked-pipeline-project-name',
);
expect(projectNameElement.innerText).toContain(this.propsData.projectName); expect(projectNameElement.innerText).toContain(this.propsData.projectName);
}); });
...@@ -55,7 +57,9 @@ describe('Linked pipeline', () => { ...@@ -55,7 +57,9 @@ describe('Linked pipeline', () => {
it('should render the correct pipeline status icon style selector', () => { it('should render the correct pipeline status icon style selector', () => {
const pipelineStatusElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-status'); const pipelineStatusElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-status');
expect(pipelineStatusElement.firstChild.classList.contains('ci-status-icon-running')).toBe(true); expect(pipelineStatusElement.firstChild.classList.contains('ci-status-icon-running')).toBe(
true,
);
}); });
it('should have a ci-status child component', () => { it('should have a ci-status child component', () => {
......
...@@ -4,7 +4,7 @@ import mockData from './linked_pipelines_mock_data'; ...@@ -4,7 +4,7 @@ import mockData from './linked_pipelines_mock_data';
const LinkedPipelinesColumnComponent = Vue.extend(LinkedPipelinesColumn); const LinkedPipelinesColumnComponent = Vue.extend(LinkedPipelinesColumn);
describe('Linked Pipelines Column', () => { describe('Linked Pipelines Column', function() {
beforeEach(() => { beforeEach(() => {
this.propsData = { this.propsData = {
columnTitle: 'Upstream', columnTitle: 'Upstream',
...@@ -22,7 +22,9 @@ describe('Linked Pipelines Column', () => { ...@@ -22,7 +22,9 @@ describe('Linked Pipelines Column', () => {
}); });
it('renders the pipeline orientation', () => { it('renders the pipeline orientation', () => {
const titleElement = this.linkedPipelinesColumn.$el.querySelector('.linked-pipelines-column-title'); const titleElement = this.linkedPipelinesColumn.$el.querySelector(
'.linked-pipelines-column-title',
);
expect(titleElement.innerText).toContain(this.propsData.columnTitle); expect(titleElement.innerText).toContain(this.propsData.columnTitle);
}); });
...@@ -31,8 +33,9 @@ describe('Linked Pipelines Column', () => { ...@@ -31,8 +33,9 @@ describe('Linked Pipelines Column', () => {
}); });
it('renders the correct number of linked pipelines', () => { it('renders the correct number of linked pipelines', () => {
const linkedPipelineElements = this.linkedPipelinesColumn.$el.querySelectorAll('.linked-pipeline'); const linkedPipelineElements = this.linkedPipelinesColumn.$el.querySelectorAll(
'.linked-pipeline',
);
expect(linkedPipelineElements.length).toBe(this.propsData.linkedPipelines.length); expect(linkedPipelineElements.length).toBe(this.propsData.linkedPipelines.length);
}); });
}); });
...@@ -6,7 +6,7 @@ import CESidebarStore from '~/sidebar/stores/sidebar_store'; ...@@ -6,7 +6,7 @@ import CESidebarStore from '~/sidebar/stores/sidebar_store';
import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarService from '~/sidebar/services/sidebar_service';
import Mock from './ee_mock_data'; import Mock from './ee_mock_data';
describe('EE Sidebar mediator', () => { describe('EE Sidebar mediator', function() {
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor); Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
this.mediator = new SidebarMediator(Mock.mediator); this.mediator = new SidebarMediator(Mock.mediator);
...@@ -20,7 +20,8 @@ describe('EE Sidebar mediator', () => { ...@@ -20,7 +20,8 @@ describe('EE Sidebar mediator', () => {
}); });
it('processes fetched data', () => { it('processes fetched data', () => {
const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; const mockData =
Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
this.mediator.processFetchedData(mockData); this.mediator.processFetchedData(mockData);
expect(this.mediator.store.weight).toEqual(mockData.weight); expect(this.mediator.store.weight).toEqual(mockData.weight);
......
...@@ -4,7 +4,7 @@ import mockData from 'spec/pipelines/graph/linked_pipelines_mock_data'; ...@@ -4,7 +4,7 @@ import mockData from 'spec/pipelines/graph/linked_pipelines_mock_data';
const ListComponent = Vue.extend(LinkedPipelinesMiniList); const ListComponent = Vue.extend(LinkedPipelinesMiniList);
describe('Linked pipeline mini list', () => { describe('Linked pipeline mini list', function() {
describe('when passed an upstream pipeline as prop', () => { describe('when passed an upstream pipeline as prop', () => {
beforeEach(() => { beforeEach(() => {
this.component = new ListComponent({ this.component = new ListComponent({
...@@ -68,7 +68,11 @@ describe('Linked pipeline mini list', () => { ...@@ -68,7 +68,11 @@ describe('Linked pipeline mini list', () => {
}); });
it('should render one linked pipeline item', () => { it('should render one linked pipeline item', () => {
expect(this.component.$el.querySelectorAll('.linked-pipeline-mini-item:not(.linked-pipelines-counter)').length).toBe(3); expect(
this.component.$el.querySelectorAll(
'.linked-pipeline-mini-item:not(.linked-pipelines-counter)',
).length,
).toBe(3);
}); });
it('should render three ci status icons', () => { it('should render three ci status icons', () => {
...@@ -110,11 +114,17 @@ describe('Linked pipeline mini list', () => { ...@@ -110,11 +114,17 @@ describe('Linked pipeline mini list', () => {
}); });
it('should set the correct pipeline path', () => { it('should set the correct pipeline path', () => {
expect(this.component.$el.querySelector('.linked-pipelines-counter').getAttribute('href')).toBe('my/pipeline/path'); expect(
this.component.$el.querySelector('.linked-pipelines-counter').getAttribute('href'),
).toBe('my/pipeline/path');
}); });
it('should render the correct counterTooltipText', () => { it('should render the correct counterTooltipText', () => {
expect(this.component.$el.querySelector('.linked-pipelines-counter').getAttribute('data-original-title')).toBe(this.component.counterTooltipText); expect(
this.component.$el
.querySelector('.linked-pipelines-counter')
.getAttribute('data-original-title'),
).toBe(this.component.counterTooltipText);
}); });
}); });
}); });
This source diff could not be displayed because it is too large. You can view the blob instead.
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