moved Angular routes to separate module; load Webpack bundle before other main scripts

This commit is contained in:
alecmerdler 2017-01-20 16:24:55 -08:00
parent 8dc9cf21d7
commit 615e233671
14 changed files with 186 additions and 167 deletions

View file

@ -166,6 +166,9 @@ def render_page_template(name, route_data=None, **kwargs):
file_lists = [library_styles, main_styles, library_scripts, main_scripts] file_lists = [library_styles, main_styles, library_scripts, main_scripts]
for file_list in file_lists: for file_list in file_lists:
file_list.sort() file_list.sort()
# Ensure Webpack bundle is first script on page
if 'js/build/bundle.js' in main_scripts: main_scripts.remove('js/build/bundle.js')
main_scripts = ['js/build/bundle.js'] + main_scripts
else: else:
library_styles = [] library_styles = []
main_styles = ['dist/quay-frontend.css'] main_styles = ['dist/quay-frontend.css']

View file

@ -39,27 +39,9 @@ module.exports = function(config) {
preprocessors: { preprocessors: {
'static/lib/ngReact/react.ngReact.min.js': ['webpack'], 'static/lib/ngReact/react.ngReact.min.js': ['webpack'],
'static/lib/angular-moment.min.js': ['webpack'], 'static/lib/angular-moment.min.js': ['webpack'],
// 'static/js/**/*.ts*': ['karma-typescript'],
'static/js/**/*.ts*': ['webpack'], 'static/js/**/*.ts*': ['webpack'],
}, },
webpack: { webpack: webpackConfig,
resolve: webpackConfig.resolve,
externals: webpackConfig.externals,
module: {
loaders: [
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/
},
{
test: /\.scss$/,
loaders: ['style', 'css', 'sass'],
exclude: /node_modules/
},
]
}
},
webpackMiddleware: { webpackMiddleware: {
stats: 'errors-only' stats: 'errors-only'
}, },

View file

@ -5,7 +5,7 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
"test:node": "./node_modules/.bin/jasmine-ts './static/js/**/*.spec.ts'", "test:node": "JASMINE_CONFIG_PATH=static/test/jasmine.json ./node_modules/.bin/jasmine-ts './static/js/**/*.spec.ts'",
"build": "./node_modules/.bin/webpack --progress -p -v", "build": "./node_modules/.bin/webpack --progress -p -v",
"watch": "./node_modules/.bin/webpack --watch" "watch": "./node_modules/.bin/webpack --watch"
}, },
@ -36,6 +36,7 @@
"devDependencies": { "devDependencies": {
"@types/angular": "1.5.16", "@types/angular": "1.5.16",
"@types/angular-mocks": "^1.5.8", "@types/angular-mocks": "^1.5.8",
"@types/angular-route": "^1.3.3",
"@types/jasmine": "^2.5.41", "@types/jasmine": "^2.5.41",
"@types/react": "0.14.39", "@types/react": "0.14.39",
"@types/react-dom": "0.14.17", "@types/react-dom": "0.14.17",

View file

@ -1,3 +1,6 @@
/**
* Regex patterns to for validating account names.
*/
export default { export default {
TEAM_PATTERN: '^[a-z][a-z0-9]+$', TEAM_PATTERN: '^[a-z][a-z0-9]+$',
ROBOT_PATTERN: '^[a-z][a-z0-9_]{1,254}$', ROBOT_PATTERN: '^[a-z][a-z0-9_]{1,254}$',

View file

@ -1,6 +1,18 @@
/**
* Manages the creation and retrieval of pages (route + controller)
* TODO: Convert to class/Angular service
*/
export default { export default {
'_pages': {}, '_pages': {},
/**
* Create a page.
* @param pageName The name of the page.
* @param templateName The file name of the template.
* @param opt_controller Controller for the page.
* @param opt_flags Additional flags passed to route provider.
* @param opt_profiles Available profiles.
*/
'create': function (pageName, templateName, opt_controller, opt_flags, opt_profiles) { 'create': function (pageName, templateName, opt_controller, opt_flags, opt_profiles) {
var profiles = opt_profiles || ['old-layout', 'layout']; var profiles = opt_profiles || ['old-layout', 'layout'];
for (var i = 0; i < profiles.length; ++i) { for (var i = 0; i < profiles.length; ++i) {
@ -13,6 +25,11 @@ export default {
} }
}, },
/**
* Retrieve a registered page.
* @param pageName The name of the page.
* @param profiles Available profiles to search.
*/
'get': function (pageName, profiles) { 'get': function (pageName, profiles) {
for (var i = 0; i < profiles.length; ++i) { for (var i = 0; i < profiles.length; ++i) {
var current = profiles[i]; var current = profiles[i];

View file

@ -12,8 +12,8 @@ angular.module('quay').directive('createRobotDialog', function () {
'info': '=info', 'info': '=info',
'robotCreated': '&robotCreated' 'robotCreated': '&robotCreated'
}, },
controller: function($scope, $element, ApiService, UserService, namePatterns) { controller: function($scope, $element, ApiService, UserService, NAME_PATTERNS) {
$scope.ROBOT_PATTERN = namePatterns.ROBOT_PATTERN; $scope.ROBOT_PATTERN = NAME_PATTERNS.ROBOT_PATTERN;
$scope.robotFinished = function(robot) { $scope.robotFinished = function(robot) {
$scope.robotCreated({'robot': robot}); $scope.robotCreated({'robot': robot});

View file

@ -12,8 +12,8 @@ angular.module('quay').directive('createTeamDialog', function () {
'info': '=info', 'info': '=info',
'teamCreated': '&teamCreated' 'teamCreated': '&teamCreated'
}, },
controller: function($scope, $element, ApiService, UserService, namePatterns) { controller: function($scope, $element, ApiService, UserService, NAME_PATTERNS) {
$scope.TEAM_PATTERN = namePatterns.TEAM_PATTERN; $scope.TEAM_PATTERN = NAME_PATTERNS.TEAM_PATTERN;
$scope.teamFinished = function(team) { $scope.teamFinished = function(team) {
$scope.teamCreated({'team': team}); $scope.teamCreated({'team': team});

View file

@ -15,9 +15,9 @@ angular.module('quay').directive('namespaceInput', function () {
'namespaceTitle': '@namespaceTitle', 'namespaceTitle': '@namespaceTitle',
}, },
controller: function($scope, $element, namePatterns) { controller: function($scope, $element, NAME_PATTERNS) {
$scope.USERNAME_PATTERN = namePatterns.USERNAME_PATTERN; $scope.USERNAME_PATTERN = NAME_PATTERNS.USERNAME_PATTERN;
$scope.usernamePattern = new RegExp(namePatterns.USERNAME_PATTERN); $scope.usernamePattern = new RegExp(NAME_PATTERNS.USERNAME_PATTERN);
$scope.$watch('binding', function(binding) { $scope.$watch('binding', function(binding) {
if (!binding) { if (!binding) {

View file

@ -1,5 +1,4 @@
import * as Raven from 'raven-js'; import * as Raven from 'raven-js';
import { RouteBuilder } from './route-builder/route-builder.service';
quayConfig.$inject = [ quayConfig.$inject = [
@ -7,10 +6,6 @@ quayConfig.$inject = [
'cfpLoadingBarProvider', 'cfpLoadingBarProvider',
'$tooltipProvider', '$tooltipProvider',
'$compileProvider', '$compileProvider',
'$routeProvider',
'$locationProvider',
'pages',
'RouteBuilderProvider',
'RestangularProvider', 'RestangularProvider',
'$analyticsProvider', '$analyticsProvider',
]; ];
@ -20,17 +15,13 @@ export function quayConfig(
cfpLoadingBarProvider, cfpLoadingBarProvider,
$tooltipProvider, $tooltipProvider,
$compileProvider, $compileProvider,
$routeProvider,
$locationProvider,
pages,
RouteBuilderProvider,
RestangularProvider, RestangularProvider,
$analyticsProvider) { $analyticsProvider) {
cfpLoadingBarProvider.includeSpinner = false; cfpLoadingBarProvider.includeSpinner = false;
// decorate the tooltip getter // decorate the tooltip getter
var tooltipFactory = $tooltipProvider.$get[$tooltipProvider.$get.length - 1]; var tooltipFactory = $tooltipProvider.$get[$tooltipProvider.$get.length - 1];
$tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window) { $tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window: ng.IWindowService) {
if ('ontouchstart' in $window) { if ('ontouchstart' in $window) {
var existing = tooltipFactory.apply(this, arguments); var existing = tooltipFactory.apply(this, arguments);
return function(element) { return function(element) {
@ -51,132 +42,6 @@ export function quayConfig(
$compileProvider.debugInfoEnabled(false); $compileProvider.debugInfoEnabled(false);
} }
$locationProvider.html5Mode(true);
// WARNING WARNING WARNING
// If you add a route here, you must add a corresponding route in thr endpoints/web.py
// index rule to make sure that deep links directly deep into the app continue to work.
// WARNING WARNING WARNING
var layoutProfile: string = 'layout';
var routeBuilder: RouteBuilder = new RouteBuilderProvider.$get()($routeProvider, pages, [
// Start with the old pages (if we asked for it).
{id: 'old-layout', templatePath: '/static/partials/'},
// Fallback back combined new/existing pages.
{id: 'layout', templatePath: '/static/partials/'}
], layoutProfile);
if ((<any>window).__features.SUPER_USERS) {
// QE Management
routeBuilder.route('/superuser/', 'superuser')
// QE Setup
.route('/setup/', 'setup');
}
routeBuilder
// Repository View
.route('/repository/:namespace/:name', 'repo-view')
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
// Image View
.route('/repository/:namespace/:name/image/:image', 'image-view')
// Repo Build View
.route('/repository/:namespace/:name/build/:buildid', 'build-view')
// Create repository notification
.route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
// Repo List
.route('/repository/', 'repo-list')
// Organizations
.route('/organizations/', 'organizations')
// New Organization
.route('/organizations/new/', 'new-organization')
// View Organization
.route('/organization/:orgname', 'org-view')
// View Organization Team
.route('/organization/:orgname/teams/:teamname', 'team-view')
// Organization View Application
.route('/organization/:orgname/application/:clientid', 'manage-application')
// View Organization Billing
.route('/organization/:orgname/billing', 'billing')
// View Organization Billing Invoices
.route('/organization/:orgname/billing/invoices', 'invoices')
// View User
.route('/user/:username', 'user-view')
// View User Billing
.route('/user/:username/billing', 'billing')
// View User Billing Invoices
.route('/user/:username/billing/invoices', 'invoices')
// Sign In
.route('/signin/', 'signin')
// New Repository
.route('/new/', 'new-repo')
// Plans
.route('/plans/', 'plans')
// Tutorial
.route('/tutorial/', 'tutorial')
// Contact
.route('/contact/', 'contact')
// About
.route('/about/', 'about')
// Security
.route('/security/', 'security')
// TOS
.route('/tos', 'tos')
// Privacy
.route('/privacy', 'privacy')
// Change username
.route('/updateuser', 'update-user')
// Landing Page
.route('/', 'landing')
// Tour
.route('/tour/', 'tour')
.route('/tour/features', 'tour')
.route('/tour/organizations', 'tour')
.route('/tour/enterprise', 'tour')
// Confirm Invite
.route('/confirminvite', 'confirm-invite')
// Enterprise marketing page
.route('/enterprise', 'enterprise')
// Public Repo Experiments
.route('/__exp/publicRepo', 'public-repo-exp')
// 404/403
.route('/:catchall', 'error-view')
.route('/:catch/:all', 'error-view')
.route('/:catch/:all/:things', 'error-view')
.route('/:catch/:all/:things/:here', 'error-view');
// Configure compile provider to add additional URL prefixes to the sanitization list. We use // Configure compile provider to add additional URL prefixes to the sanitization list. We use
// these on the Contact page. // these on the Contact page.
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/); $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/);

View file

@ -4,7 +4,8 @@ import quayPages from './quay-pages.module';
import quayRun from './quay.run'; import quayRun from './quay.run';
import { angularViewArrayFactory } from './services/angular-view-array/angular-view-array'; import { angularViewArrayFactory } from './services/angular-view-array/angular-view-array';
import { routeBuilderFactory } from './route-builder/route-builder.service.impl'; import { routeBuilderFactory } from './route-builder/route-builder.service.impl';
import namePatterns from './constants/name-patterns.constant'; import NAME_PATTERNS from './constants/name-patterns.constant';
import { routeConfig } from './quay.routes';
var quayDependencies: string[] = [ var quayDependencies: string[] = [
@ -46,7 +47,8 @@ if ((<any>window).__config && (<any>window).__config.RECAPTCHA_SITE_KEY) {
export default angular export default angular
.module('quay', quayDependencies) .module('quay', quayDependencies)
.config(quayConfig) .config(quayConfig)
.constant('namePatterns', namePatterns) .config(routeConfig)
.constant('NAME_PATTERNS', NAME_PATTERNS)
.factory('RouteBuilder', routeBuilderFactory) .factory('RouteBuilder', routeBuilderFactory)
.factory('AngularViewArray', angularViewArrayFactory) .factory('AngularViewArray', angularViewArrayFactory)
.run(quayRun) .run(quayRun)

141
static/js/quay.routes.ts Normal file
View file

@ -0,0 +1,141 @@
import { RouteBuilder } from './route-builder/route-builder.service';
import pages from './constants/pages.constant';
routeConfig.$inject = [
'pages',
'RouteBuilderProvider',
'$routeProvider',
'$locationProvider',
];
export function routeConfig(
pages: any,
RouteBuilderProvider: any,
$routeProvider: ng.route.IRouteProvider,
$locationProvider: ng.ILocationProvider) {
$locationProvider.html5Mode(true);
// WARNING WARNING WARNING
// If you add a route here, you must add a corresponding route in thr endpoints/web.py
// index rule to make sure that deep links directly deep into the app continue to work.
// WARNING WARNING WARNING
var layoutProfile: string = 'layout';
var routeBuilder: RouteBuilder = new RouteBuilderProvider.$get()($routeProvider, pages, [
// Start with the old pages (if we asked for it).
{id: 'old-layout', templatePath: '/static/partials/'},
// Fallback back combined new/existing pages.
{id: 'layout', templatePath: '/static/partials/'}
], layoutProfile);
if ((<any>window).__features.SUPER_USERS) {
// QE Management
routeBuilder.route('/superuser/', 'superuser')
// QE Setup
.route('/setup/', 'setup');
}
routeBuilder
// Repository View
.route('/repository/:namespace/:name', 'repo-view')
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
// Image View
.route('/repository/:namespace/:name/image/:image', 'image-view')
// Repo Build View
.route('/repository/:namespace/:name/build/:buildid', 'build-view')
// Create repository notification
.route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
// Repo List
.route('/repository/', 'repo-list')
// Organizations
.route('/organizations/', 'organizations')
// New Organization
.route('/organizations/new/', 'new-organization')
// View Organization
.route('/organization/:orgname', 'org-view')
// View Organization Team
.route('/organization/:orgname/teams/:teamname', 'team-view')
// Organization View Application
.route('/organization/:orgname/application/:clientid', 'manage-application')
// View Organization Billing
.route('/organization/:orgname/billing', 'billing')
// View Organization Billing Invoices
.route('/organization/:orgname/billing/invoices', 'invoices')
// View User
.route('/user/:username', 'user-view')
// View User Billing
.route('/user/:username/billing', 'billing')
// View User Billing Invoices
.route('/user/:username/billing/invoices', 'invoices')
// Sign In
.route('/signin/', 'signin')
// New Repository
.route('/new/', 'new-repo')
// Plans
.route('/plans/', 'plans')
// Tutorial
.route('/tutorial/', 'tutorial')
// Contact
.route('/contact/', 'contact')
// About
.route('/about/', 'about')
// Security
.route('/security/', 'security')
// TOS
.route('/tos', 'tos')
// Privacy
.route('/privacy', 'privacy')
// Change username
.route('/updateuser', 'update-user')
// Landing Page
.route('/', 'landing')
// Tour
.route('/tour/', 'tour')
.route('/tour/features', 'tour')
.route('/tour/organizations', 'tour')
.route('/tour/enterprise', 'tour')
// Confirm Invite
.route('/confirminvite', 'confirm-invite')
// Enterprise marketing page
.route('/enterprise', 'enterprise')
// Public Repo Experiments
.route('/__exp/publicRepo', 'public-repo-exp')
// 404/403
.route('/:catchall', 'error-view')
.route('/:catch/:all', 'error-view')
.route('/:catch/:all/:things', 'error-view')
.route('/:catch/:all/:things/:here', 'error-view');
}

6
static/test/jasmine.json Normal file
View file

@ -0,0 +1,6 @@
{
"spec_dir": "./static/js",
"spec_files": [
"**/*.spec.js"
]
}

View file

@ -64,8 +64,6 @@
{% endblock %} {% endblock %}
<script src="/static/js/build/bundle.js"></script>
{% for script_path, cache_buster in main_scripts %} {% for script_path, cache_buster in main_scripts %}
<script src="/static/{{ script_path }}?v={{ cache_buster }}"></script> <script src="/static/{{ script_path }}?v={{ cache_buster }}"></script>
{% endfor %} {% endfor %}

View file

@ -13,6 +13,7 @@ var config = {
"sass": path.resolve('./static/css/directives/components/pages/') "sass": path.resolve('./static/css/directives/components/pages/')
} }
}, },
// Use window.angular to maintain compatibility with non-Webpack components
externals: { externals: {
"angular": "angular", "angular": "angular",
}, },