Merge pull request #2846 from alecmerdler/QUAY-682

Optimize Webpack JS Bundle Size
This commit is contained in:
Alec Merdler 2017-09-18 16:47:30 -04:00 committed by GitHub
commit ad61df66c5
28 changed files with 383 additions and 366 deletions

View File

@ -20,15 +20,14 @@ RUN virtualenv --distribute venv \
# Install front-end dependencies
# JS depedencies
COPY yarn.lock ./
RUN yarn install --ignore-engines
COPY yarn.lock package.json tsconfig.json webpack.config.js tslint.json ./
RUN yarn install --ignore-engines
# JS compile
COPY static static
COPY package.json tsconfig.json webpack.config.js tslint.json ./
RUN yarn build \
&& jpegoptim static/img/**/*.jpg \
&& optipng -clobber -quiet static/img/**/*.png
&& jpegoptim static/img/**/*.jpg \
&& optipng -clobber -quiet static/img/**/*.png
COPY . .

View File

@ -24,6 +24,9 @@ from _init import __version__
logger = logging.getLogger(__name__)
JS_BUNDLE_NAME = 'bundle'
def common_login(user_uuid, permanent_session=True):
""" Performs login of the given user, with optional non-permanence on the session. """
user = model.get_user(user_uuid)
@ -56,10 +59,10 @@ def common_login(user_uuid, permanent_session=True):
return False
def _list_files(path, extension):
def _list_files(path, extension, contains=""):
""" Returns a list of all the files with the given extension found under the given path. """
def matches(f):
return os.path.splitext(f)[1] == '.' + extension and f.split(os.path.extsep)[1] != 'spec'
return os.path.splitext(f)[1] == '.' + extension and contains in os.path.splitext(f)[0]
def join_path(dp, f):
# Remove the static/ prefix. It is added in the template.
@ -71,10 +74,7 @@ def _list_files(path, extension):
def render_page_template(name, route_data=None, **kwargs):
""" Renders the page template with the given name as the response and returns its contents. """
library_styles = []
main_styles = []
library_scripts = []
main_scripts = _list_files('build', 'js')
main_scripts = _list_files('build', 'js', JS_BUNDLE_NAME)
use_cdn = app.config.get('USE_CDN', True)
if request.args.get('use_cdn') is not None:
@ -118,10 +118,7 @@ def render_page_template(name, route_data=None, **kwargs):
route_data=route_data,
external_styles=external_styles,
external_scripts=external_scripts,
main_styles=main_styles,
library_styles=library_styles,
main_scripts=main_scripts,
library_scripts=library_scripts,
feature_set=features.get_features(),
config_set=frontend_visible_config(app.config),
oauth_set=get_oauth_config(),

View File

@ -4,13 +4,15 @@
"private": true,
"version": "1.0.0",
"scripts": {
"dev": "./node_modules/.bin/karma start --browsers ChromeHeadless",
"test": "./node_modules/.bin/karma start --single-run --browsers ChromeHeadless",
"test:node": "JASMINE_CONFIG_PATH=static/test/jasmine.json ./node_modules/.bin/jasmine-ts './static/js/**/*.spec.ts'",
"e2e": "./node_modules/.bin/ts-node ./node_modules/.bin/protractor static/test/protractor.conf.ts",
"build": "NODE_ENV=production ./node_modules/.bin/webpack --progress",
"watch": "./node_modules/.bin/webpack --watch",
"lint": "./node_modules/.bin/tslint --type-check -p tsconfig.json -e **/*.spec.ts"
"dev": "karma start --browsers ChromeHeadless",
"test": "karma start --single-run --browsers ChromeHeadless",
"test:node": "JASMINE_CONFIG_PATH=static/test/jasmine.json jasmine-ts './static/js/**/*.spec.ts'",
"e2e": "ts-node ./node_modules/.bin/protractor static/test/protractor.conf.ts",
"build": "npm run clean && NODE_ENV=production webpack --progress",
"watch": "npm run clean && webpack --watch",
"lint": "tslint --type-check -p tsconfig.json -e **/*.spec.ts",
"analyze": "NODE_ENV=production webpack --profile --json | awk '{if(NR>1)print}' > static/build/stats.json && webpack-bundle-analyzer --mode static -r static/build/report.html static/build/stats.json",
"clean": "rm -f static/build/*"
},
"repository": {
"type": "git",
@ -32,13 +34,13 @@
"d3": "^3.3.3",
"eonasdan-bootstrap-datetimepicker": "^4.17.43",
"file-saver": "^1.3.3",
"highlight.js": "^9.12.0",
"jquery": "1.12.4",
"ng-metadata": "^4.0.1",
"raven-js": "^3.1.0",
"restangular": "^1.2.0",
"rxjs": "^5.0.1",
"showdown": "^1.6.4",
"showdown-highlightjs-extension": "^0.1.2",
"underscore": "^1.5.2",
"urijs": "^1.18.10",
"zeroclipboard": "^2.3.0"
@ -51,8 +53,6 @@
"@types/core-js": "^0.9.39",
"@types/jasmine": "^2.5.41",
"@types/jquery": "^2.0.40",
"@types/react": "0.14.39",
"@types/react-dom": "0.14.17",
"@types/showdown": "^1.4.32",
"angular-mocks": "1.6.2",
"css-loader": "0.25.0",
@ -75,6 +75,7 @@
"ts-node": "^3.0.6",
"tslint": "^5.4.3",
"typescript": "^2.2.1",
"webpack": "^2.2"
"webpack": "^2.2",
"webpack-bundle-analyzer": "^2.8.3"
}
}

View File

@ -920,16 +920,6 @@ a:focus {
margin-top: 6px;
}
.realtime-area-chart, .realtime-line-chart {
margin: 10px;
text-align: center;
}
.rickshaw_graph {
overflow: hidden;
padding-bottom: 40px;
}
.cor-container {
padding-left: 15px;
padding-right: 15px;

View File

@ -1,6 +0,0 @@
<div class="realtime-area-chart-element">
<div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>

View File

@ -1,6 +0,0 @@
<div class="realtime-line-chart-element">
<div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>

View File

@ -0,0 +1,8 @@
[
"javascript",
"python",
"bash",
"nginx",
"xml",
"shell"
]

View File

@ -302,236 +302,6 @@ angular.module("core-ui", [])
return directiveDefinitionObject;
})
.directive('realtimeAreaChart', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/realtime-area-chart.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'data': '=data',
'labels': '=labels',
'colors': '=colors',
'counter': '=counter'
},
controller: function($scope, $element) {
var graph = null;
var series = [];
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
var colors = $scope.colors || [];
var setupGraph = function() {
for (var i = 0; i < $scope.labels.length; ++i) {
series.push({
name: $scope.labels[i],
color: i >= colors.length ? palette.color(): $scope.colors[i],
stroke: 'rgba(0,0,0,0.15)',
data: []
});
}
var options = {
element: $element.find('.chart')[0],
renderer: 'area',
stroke: true,
series: series,
min: 0,
padding: {
'top': 0.3,
'left': 0,
'right': 0,
'bottom': 0.3
}
};
if ($scope.minimum != null) {
options['min'] = $scope.minimum == 'auto' ? 'auto' : $scope.minimum * 1;
} else {
options['min'] = 0;
}
if ($scope.maximum != null) {
options['max'] = $scope.maximum == 'auto' ? 'auto' : $scope.maximum * 1;
}
graph = new Rickshaw.Graph(options);
xaxes = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
});
yaxes = new Rickshaw.Graph.Axis.Y({
graph: graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
});
hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
xFormatter: function(x) {
return new Date(x * 1000).toString();
}
});
};
var refresh = function(data) {
if (!data || $scope.counter < 0) { return; }
if (!graph) {
setupGraph();
}
var timecode = new Date().getTime() / 1000;
for (var i = 0; i < $scope.data.length; ++i) {
var arr = series[i].data;
arr.push(
{'x': timecode, 'y': $scope.data[i] }
);
if (arr.length > 10) {
series[i].data = arr.slice(arr.length - 10, arr.length);
}
}
graph.renderer.unstack = true;
graph.update();
};
$scope.$watch('counter', function() {
refresh($scope.data_raw);
});
$scope.$watch('data', function(data) {
$scope.data_raw = data;
refresh($scope.data_raw);
});
}
};
return directiveDefinitionObject;
})
.directive('realtimeLineChart', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/realtime-line-chart.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'data': '=data',
'labels': '=labels',
'counter': '=counter',
'labelTemplate': '@labelTemplate',
'minimum': '@minimum',
'maximum': '@maximum'
},
controller: function($scope, $element) {
var graph = null;
var xaxes = null;
var yaxes = null;
var hoverDetail = null;
var series = [];
var counter = 0;
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
var setupGraph = function() {
var options = {
element: $element.find('.chart')[0],
renderer: 'line',
series: series,
padding: {
'top': 0.3,
'left': 0,
'right': 0,
'bottom': 0.3
}
};
if ($scope.minimum != null) {
options['min'] = $scope.minimum == 'auto' ? 'auto' : $scope.minimum * 1;
} else {
options['min'] = 0;
}
if ($scope.maximum != null) {
options['max'] = $scope.maximum == 'auto' ? 'auto' : $scope.maximum * 1;
}
graph = new Rickshaw.Graph(options);
xaxes = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
});
yaxes = new Rickshaw.Graph.Axis.Y({
graph: graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
});
hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
xFormatter: function(x) {
return new Date(x * 1000).toString();
}
});
};
var refresh = function(data) {
if (data == null) { return; }
if (!graph) {
setupGraph();
}
if (typeof data == 'number') {
data = [data];
}
if ($scope.labels) {
data = data.slice(0, $scope.labels.length);
}
if (series.length == 0){
for (var i = 0; i < data.length; ++i) {
var title = $scope.labels ? $scope.labels[i] : $scope.labelTemplate.replace('{x}', i + 1);
series.push({
'color': palette.color(),
'data': [],
'name': title
})
}
}
counter++;
var timecode = new Date().getTime() / 1000;
for (var i = 0; i < data.length; ++i) {
var arr = series[i].data;
arr.push({
'x': timecode,
'y': data[i]
})
if (arr.length > 10) {
series[i].data = arr.slice(arr.length - 10, arr.length);
}
}
graph.update();
};
$scope.$watch('counter', function(counter) {
refresh($scope.data_raw);
});
$scope.$watch('data', function(data) {
$scope.data_raw = data;
});
}
};
return directiveDefinitionObject;
})
.directive('corProgressBar', function() {
var directiveDefinitionObject = {
priority: 4,

View File

@ -1,17 +0,0 @@
import { Inject } from './inject.decorator';
describe("Decorator: Inject", () => {
describe("parameter injection", () => {
it("adds given string to the 'inject' property of the annotated class", () => {
expect(ValidService.$inject).toContain('$scope');
});
});
});
class ValidService {
constructor(@Inject('$scope') private $scope: any) {}
}

View File

@ -1,11 +0,0 @@
/**
* Adds the given value to the inject property of the annotated class.
* Used to annotate the constructor parameters of an AngularJS service/component class.
* @param value The string name of the dependency.
*/
export function Inject(value: string) {
return (target: any, propertyKey: string | symbol, parameterIndex: number): void => {
target.$inject = target.$inject = [];
target.$inject[parameterIndex] = value;
};
}

View File

@ -8,15 +8,37 @@ describe("MarkdownEditorComponent", () => {
var component: MarkdownEditorComponent;
var textarea: Mock<ng.IAugmentedJQuery | any>;
var documentMock: Mock<HTMLElement & Document>;
var $windowMock: Mock<ng.IWindowService>;
beforeEach(() => {
textarea = new Mock<ng.IAugmentedJQuery | any>();
documentMock = new Mock<HTMLElement & Document>();
$windowMock = new Mock<ng.IWindowService>();
const $documentMock: any = [documentMock.Object];
component = new MarkdownEditorComponent($documentMock, 'chrome');
component = new MarkdownEditorComponent($documentMock, $windowMock.Object, 'chrome');
component.textarea = textarea.Object;
});
describe("onBeforeUnload", () => {
it("returns false to alert user about losing current changes", () => {
component.changeEditMode("write");
const allow: boolean = component.onBeforeUnload();
expect(allow).toBe(false);
});
});
describe("ngOnDestroy", () => {
it("removes 'beforeunload' event listener", () => {
$windowMock.setup(mock => mock.onbeforeunload).is(() => 1);
component.ngOnDestroy();
expect($windowMock.Object.onbeforeunload.call(this)).toEqual(null);
});
});
describe("changeEditMode", () => {
it("sets component's edit mode to given mode", () => {
@ -135,7 +157,15 @@ describe("MarkdownEditorComponent", () => {
describe("discardChanges", () => {
it("emits output event with no content", (done) => {
it("prompts user to confirm discarding changes", () => {
const confirmSpy: Spy = $windowMock.setup(mock => mock.confirm).is((message) => false).Spy;
component.discardChanges();
expect(confirmSpy.calls.argsFor(0)[0]).toEqual(`Are you sure you want to discard your changes?`);
});
it("emits output event with no content if user confirms discarding changes", (done) => {
$windowMock.setup(mock => mock.confirm).is((message) => true);
component.discard.subscribe((event: {}) => {
expect(event).toEqual({});
done();
@ -143,5 +173,16 @@ describe("MarkdownEditorComponent", () => {
component.discardChanges();
});
it("does not emit output event if user declines confirmation of discarding changes", (done) => {
$windowMock.setup(mock => mock.confirm).is((message) => false);
component.discard.subscribe((event: {}) => {
fail(`Should not emit output event`);
done();
});
component.discardChanges();
done();
});
});
});

View File

@ -1,4 +1,4 @@
import { Component, Inject, Input, Output, EventEmitter, ViewChild } from 'ng-metadata/core';
import { Component, Inject, Input, Output, EventEmitter, ViewChild, HostListener, OnDestroy } from 'ng-metadata/core';
import { MarkdownSymbol } from '../../../types/common.types';
import { BrowserPlatform } from '../../../constants/platform.constant';
import './markdown-editor.component.css';
@ -11,9 +11,10 @@ import './markdown-editor.component.css';
selector: 'markdown-editor',
templateUrl: '/static/js/directives/ui/markdown/markdown-editor.component.html'
})
export class MarkdownEditorComponent {
export class MarkdownEditorComponent implements OnDestroy {
@Input('<') public content: string;
@Output() public save: EventEmitter<{editedContent: string}> = new EventEmitter();
@Output() public discard: EventEmitter<any> = new EventEmitter();
@ -23,8 +24,18 @@ export class MarkdownEditorComponent {
private editMode: EditMode = "write";
constructor(@Inject('$document') private $document: ng.IDocumentService,
@Inject('$window') private $window: ng.IWindowService,
@Inject('BrowserPlatform') private browserPlatform: BrowserPlatform) {
this.$window.onbeforeunload = this.onBeforeUnload.bind(this);
}
@HostListener('window:beforeunload', [])
public onBeforeUnload(): boolean {
return false;
}
public ngOnDestroy(): void {
this.$window.onbeforeunload = () => null;
}
public changeEditMode(newMode: EditMode): void {
@ -103,7 +114,9 @@ export class MarkdownEditorComponent {
}
public discardChanges(): void {
this.discard.emit({});
if (this.$window.confirm(`Are you sure you want to discard your changes?`)) {
this.discard.emit({});
}
}
public get currentEditMode(): EditMode {

View File

@ -14,7 +14,9 @@ export class MarkdownInputComponent {
@Input('<') public content: string;
@Input('<') public canWrite: boolean;
@Input('@') public fieldTitle: string;
@Output() public contentChanged: EventEmitter<{content: string}> = new EventEmitter();
private isEditing: boolean = false;
public editContent(): void {

View File

@ -15,9 +15,7 @@ describe("MarkdownViewComponent", () => {
markdownConverterMock = new Mock<Converter>();
$sceMock = new Mock<ng.ISCEService>();
$sanitizeMock = jasmine.createSpy('$sanitizeSpy').and.callFake((html: string) => html);
component = new MarkdownViewComponent((options: ConverterOptions) => markdownConverterMock.Object,
$sceMock.Object,
$sanitizeMock);
component = new MarkdownViewComponent(markdownConverterMock.Object, $sceMock.Object, $sanitizeMock);
});
describe("ngOnChanges", () => {

View File

@ -1,7 +1,5 @@
import { Component, Input, Inject, OnChanges, SimpleChanges } from 'ng-metadata/core';
import { Converter, ConverterOptions } from 'showdown';
import 'showdown-highlightjs-extension';
import 'highlightjs/styles/vs.css';
import './markdown-view.component.css';
@ -17,15 +15,15 @@ export class MarkdownViewComponent implements OnChanges {
@Input('<') public content: string;
@Input('<') public firstLineOnly: boolean = false;
@Input('<') public placeholderNeeded: boolean = false;
private convertedHTML: string = '';
private readonly placeholder: string = `<p style="visibility:hidden">placeholder</p>`;
private readonly markdownChars: string[] = ['#', '-', '>', '`'];
private markdownConverter: Converter;
constructor(@Inject('markdownConverterFactory') private makeConverter: (options?: ConverterOptions) => Converter,
constructor(@Inject('markdownConverter') private markdownConverter: Converter,
@Inject('$sce') private $sce: ng.ISCEService,
@Inject('$sanitize') private $sanitize: ng.sanitize.ISanitizeService) {
this.markdownConverter = makeConverter({extensions: ['highlightjs']});
}
public ngOnChanges(changes: SimpleChanges): void {

View File

@ -0,0 +1,76 @@
import { NgModule } from 'ng-metadata/core';
import { Converter } from 'showdown';
import * as showdown from 'showdown';
import { registerLanguage, highlightAuto } from 'highlight.js/lib/highlight';
import 'highlight.js/styles/vs.css';
const highlightedLanguages: string[] = require('../../../constants/highlighted-languages.constant.json');
/**
* Dynamically fetch and register a new language with Highlight.js
*/
export const addHighlightedLanguage = (language: string): Promise<{}> => {
return new Promise(async(resolve, reject) => {
try {
// TODO(alecmerdler): Use `import()` here instead of `System.import()` after upgrading to TypeScript 2.4
const langModule = await System.import(`highlight.js/lib/languages/${language}`);
registerLanguage(language, langModule);
console.debug(`Language ${language} registered for syntax highlighting`);
resolve();
} catch (error) {
console.debug(`Language ${language} not supported for syntax highlighting`);
reject(error);
}
});
};
/**
* Showdown JS extension for syntax highlighting using Highlight.js. Will attempt to register detected languages.
*/
export const showdownHighlight = (): showdown.FilterExtension => {
const htmlunencode = (text: string) => {
return (text
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>'));
};
const left = '<pre><code\\b[^>]*>';
const right = '</code></pre>';
const flags = 'g';
const replacement = (wholeMatch: string, match: string, leftSide: string, rightSide: string) => {
const language: string = leftSide.slice(leftSide.indexOf('language-') + ('language-').length,
leftSide.indexOf('"', leftSide.indexOf('language-')));
addHighlightedLanguage(language).catch(error => null);
match = htmlunencode(match);
return leftSide + highlightAuto(match).value + rightSide;
};
return {
type: 'output',
filter: (text, converter, options) => {
return (<any>showdown).helper.replaceRecursiveRegExp(text, replacement, left, right, flags);
}
};
};
// Import default syntax-highlighting supported languages
highlightedLanguages.forEach((langName) => addHighlightedLanguage(langName));
/**
* Markdown editor and view module.
*/
@NgModule({
imports: [],
declarations: [],
providers: [
{provide: 'markdownConverter', useValue: new Converter({extensions: [<any>showdownHighlight]})},
],
})
export class MarkdownModule {
}

View File

@ -4,6 +4,7 @@ import { QuayModule } from './quay.module';
import { provideRun } from './quay-run';
import * as angular from 'angular';
// Load all JS/CSS files into bundle: http://stackoverflow.com/a/30652110
declare var require: any;
function requireAll(r) {

View File

@ -4,6 +4,7 @@ import { NAME_PATTERNS } from "./constants/name-patterns.constant";
import * as Raven from "raven-js";
var quayDependencies: string[] = [
'chieffancypants.loadingBar',
'cfp.hotkeys',

View File

@ -37,7 +37,7 @@ import { ManageTriggerComponent } from './directives/ui/manage-trigger/manage-tr
import { ClipboardCopyDirective } from './directives/ui/clipboard-copy/clipboard-copy.directive';
import { CorTabsModule } from './directives/ui/cor-tabs/cor-tabs.module';
import { TriggerDescriptionComponent } from './directives/ui/trigger-description/trigger-description.component';
import { Converter, ConverterOptions } from 'showdown';
import { MarkdownModule } from './directives/ui/markdown/markdown.module';
import * as Clipboard from 'clipboard';
@ -49,6 +49,7 @@ import * as Clipboard from 'clipboard';
QuayRoutesModule,
QuayConfigModule,
CorTabsModule,
MarkdownModule,
],
declarations: [
RegexMatchViewComponent,
@ -86,7 +87,6 @@ import * as Clipboard from 'clipboard';
DockerfileServiceImpl,
DataFileServiceImpl,
{provide: 'fileReaderFactory', useValue: () => new FileReader()},
{provide: 'markdownConverterFactory', useValue: (options?: ConverterOptions) => new Converter(options)},
{provide: 'BrowserPlatform', useValue: browserPlatform},
{provide: 'clipboardFactory', useValue: (trigger, options) => new Clipboard(trigger, options)},
],

View File

@ -1,3 +1,6 @@
import * as Raven from 'raven-js';
/**
* Service which monitors the current user session and provides methods for returning information
* about the user.

View File

@ -1,8 +1,8 @@
/**
* Service which exposes various utility methods.
*/
angular.module('quay').factory('UtilService', ['$sanitize', 'markdownConverterFactory',
function($sanitize, markdownConverterFactory) {
angular.module('quay').factory('UtilService', ['$sanitize', 'markdownConverter',
function($sanitize, markdownConverter) {
var utilService = {};
var adBlockEnabled = null;
@ -34,7 +34,7 @@ angular.module('quay').factory('UtilService', ['$sanitize', 'markdownConverterFa
};
utilService.getMarkedDown = function(string) {
return markdownConverterFactory().makeHtml(string || '');
return markdownConverter.makeHtml(string || '');
};
utilService.getFirstMarkdownLineAsText = function(commentString, placeholderNeeded) {

3
static/js/types/custom.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare var System: {
import: (module: string) => Promise<any>;
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
declare var require: any;
declare var require: NodeRequire;
// Require all modules ending in ".spec.ts" from the js directory and all subdirectories
var testsContext = (<any>require).context("../js", true, /\.spec\.ts$/);

View File

@ -22,14 +22,6 @@
<meta property="og:image" content="{{ preferred_scheme }}://{{ hostname }}/static/img/quay_preview.png" />
<!-- /Icons -->
{% for style_path in main_styles %}
<link rel="stylesheet" href="/static/{{ style_path }}" type="text/css">
{% endfor %}
{% for style_path in library_styles %}
<link rel="stylesheet" href="/static/{{ style_path }}" type="text/css">
{% endfor %}
{% block added_stylesheets %}
{% endblock %}
@ -57,10 +49,6 @@
<script src="{{ script_url }}"></script>
{% endfor %}
{% for script_path in library_scripts %}
<script src="/static/{{ script_path }}"></script>
{% endfor %}
{% block added_dependencies %}
{% endblock %}

View File

@ -1,22 +1,24 @@
var webpack = require('webpack');
var path = require("path");
const webpack = require('webpack');
const path = require('path');
var config = {
let config = {
entry: "./static/js/main.ts",
output: {
path: path.resolve(__dirname, "static/build"),
filename: 'quay-frontend.js'
publicPath: "/static/build/",
filename: '[name]-quay-frontend.bundle.js',
chunkFilename: '[name]-quay-frontend.chunk.js'
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".scss"],
alias: {
sass: path.resolve(__dirname, 'static/css/directives/components/pages/')
}
extensions: [".ts", ".js"],
},
// Use global variables to maintain compatibility with non-Webpack components
externals: {
angular: "angular",
jquery: "$",
moment: "moment",
"raven-js": "Raven",
},
module: {
rules: [
@ -47,7 +49,10 @@ var config = {
FileSaver: 'file-saver',
angular: "angular",
$: "jquery",
moment: "moment",
}),
// Restrict the extra locales that moment.js can load; en is always included
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
],
devtool: "cheap-module-source-map",
};
@ -57,14 +62,15 @@ var config = {
* Production settings
*/
if (process.env.NODE_ENV === 'production') {
config.plugins.push(
config.plugins.concat([
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
// Disable mangle to prevent AngularJS errors
mangle: false
})
);
config.output.filename = 'quay-frontend-[hash].js';
}),
new webpack.optimize.CommonsChunkPlugin({name: 'common'}),
]);
config.output.filename = '[name]-quay-frontend-[hash].bundle.js';
}
module.exports = config;

194
yarn.lock
View File

@ -70,7 +70,7 @@ abbrev@1, abbrev@1.0.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
accepts@1.3.3:
accepts@1.3.3, accepts@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
dependencies:
@ -87,6 +87,10 @@ acorn@^4.0.3, acorn@^4.0.4:
version "4.0.11"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
acorn@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
adm-zip@0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736"
@ -207,6 +211,10 @@ array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
array-slice@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
@ -779,10 +787,18 @@ constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
content-type@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
@ -1016,6 +1032,10 @@ des.js@^1.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
di@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@ -1045,6 +1065,10 @@ domain-browser@^1.1.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
ecc-jsbn@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@ -1055,6 +1079,10 @@ ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
ejs@^2.5.6:
version "2.5.7"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
electron-to-chromium@^1.2.3:
version "1.2.4"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.2.4.tgz#9751cbea89fa120bf88c226ba41eb8d0b6f1b597"
@ -1210,6 +1238,10 @@ esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
etag@~1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
eventemitter3@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
@ -1255,6 +1287,39 @@ expand-range@^1.8.1:
dependencies:
fill-range "^2.1.0"
express@^4.15.2:
version "4.15.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662"
dependencies:
accepts "~1.3.3"
array-flatten "1.1.1"
content-disposition "0.5.2"
content-type "~1.0.2"
cookie "0.3.1"
cookie-signature "1.0.6"
debug "2.6.7"
depd "~1.1.0"
encodeurl "~1.0.1"
escape-html "~1.0.3"
etag "~1.8.0"
finalhandler "~1.0.3"
fresh "0.5.0"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.1"
path-to-regexp "0.1.7"
proxy-addr "~1.1.4"
qs "6.4.0"
range-parser "~1.2.0"
send "0.15.3"
serve-static "1.12.3"
setprototypeof "1.0.3"
statuses "~1.3.1"
type-is "~1.6.15"
utils-merge "1.0.0"
vary "~1.1.1"
extend@3, extend@^3.0.0, extend@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
@ -1285,6 +1350,10 @@ filename-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
filesize@^3.5.9:
version "3.5.10"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f"
fill-range@^2.1.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
@ -1295,7 +1364,7 @@ fill-range@^2.1.0:
repeat-element "^1.1.2"
repeat-string "^1.5.2"
finalhandler@1.0.3:
finalhandler@1.0.3, finalhandler@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89"
dependencies:
@ -1340,6 +1409,14 @@ form-data@~2.1.1:
combined-stream "^1.0.5"
mime-types "^2.1.12"
forwarded@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
fresh@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
fs-access@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a"
@ -1474,6 +1551,12 @@ graceful-fs@^4.1.2:
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
gzip-size@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520"
dependencies:
duplexer "^0.1.1"
handlebars@^4.0.1:
version "4.0.6"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
@ -1542,9 +1625,9 @@ he@1.1.x:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
highlightjs@^9.8.0:
version "9.10.0"
resolved "https://registry.yarnpkg.com/highlightjs/-/highlightjs-9.10.0.tgz#fca9b78ddaa3b1abca89d6c3ee105ad270a80190"
highlight.js@^9.12.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
hmac-drbg@^1.0.0:
version "1.0.0"
@ -1678,6 +1761,10 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
ipaddr.js@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0"
is-absolute-url@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@ -2118,7 +2205,7 @@ lodash@^3.8.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.5.0, lodash@~4.17.0:
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.5.0, lodash@~4.17.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@ -2194,6 +2281,14 @@ meow@^3.3.0:
redent "^1.0.0"
trim-newlines "^1.0.0"
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
micromatch@^2.1.5:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@ -2229,7 +2324,7 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
dependencies:
mime-db "~1.27.0"
mime@^1.3.4:
mime@1.3.4, mime@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
@ -2456,6 +2551,10 @@ once@~1.3.3:
dependencies:
wrappy "1"
opener@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
optimist@^0.6.1, optimist@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@ -2571,6 +2670,10 @@ path-parse@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@ -2881,6 +2984,13 @@ protractor@^5.1.2:
webdriver-js-extender "^1.0.0"
webdriver-manager "^12.0.6"
proxy-addr@~1.1.4:
version "1.1.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918"
dependencies:
forwarded "~0.1.0"
ipaddr.js "1.4.0"
prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@ -2945,7 +3055,7 @@ randombytes@^2.0.0, randombytes@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
range-parser@^1.0.3, range-parser@^1.2.0:
range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
@ -3199,6 +3309,10 @@ safe-buffer@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223"
safe-buffer@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
saucelabs@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/saucelabs/-/saucelabs-1.3.0.tgz#d240e8009df7fa87306ec4578a69ba3b5c424fee"
@ -3254,6 +3368,33 @@ semver@~5.0.1:
version "5.0.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
send@0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309"
dependencies:
debug "2.6.7"
depd "~1.1.0"
destroy "~1.0.4"
encodeurl "~1.0.1"
escape-html "~1.0.3"
etag "~1.8.0"
fresh "0.5.0"
http-errors "~1.6.1"
mime "1.3.4"
ms "2.0.0"
on-finished "~2.3.0"
range-parser "~1.2.0"
statuses "~1.3.1"
serve-static@1.12.3:
version "1.12.3"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2"
dependencies:
encodeurl "~1.0.1"
escape-html "~1.0.3"
parseurl "~1.3.1"
send "0.15.3"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -3276,12 +3417,6 @@ sha.js@^2.3.6:
dependencies:
inherits "^2.0.1"
showdown-highlightjs-extension@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/showdown-highlightjs-extension/-/showdown-highlightjs-extension-0.1.2.tgz#0fc90190283c1ae03fc4cccce3f1be6a5a58e4ce"
dependencies:
highlightjs "^9.8.0"
showdown@^1.6.4:
version "1.6.4"
resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.6.4.tgz#056bbb654ecdb8d8643ae12d6d597893ccaf46c6"
@ -3740,6 +3875,10 @@ ultron@1.0.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
ultron@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
underscore@^1.5.2:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
@ -3819,6 +3958,10 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
vary@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
vendors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
@ -3870,6 +4013,22 @@ webdriver-manager@^12.0.6:
semver "^5.3.0"
xml2js "^0.4.17"
webpack-bundle-analyzer@^2.8.3:
version "2.8.3"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.8.3.tgz#8e7b3deb3832698c24b09c84dfe5b43902a83991"
dependencies:
acorn "^5.1.1"
chalk "^1.1.3"
commander "^2.9.0"
ejs "^2.5.6"
express "^4.15.2"
filesize "^3.5.9"
gzip-size "^3.0.0"
lodash "^4.17.4"
mkdirp "^0.5.1"
opener "^1.4.3"
ws "^2.3.1"
webpack-dev-middleware@^1.0.11:
version "1.10.1"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.1.tgz#c6b4cf428139cf1aefbe06a0c00fdb4f8da2f893"
@ -3965,6 +4124,13 @@ ws@1.1.2, ws@^1.0.1:
options ">=0.0.5"
ultron "1.0.x"
ws@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80"
dependencies:
safe-buffer "~5.0.1"
ultron "~1.1.0"
wtf-8@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"