diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 000000000..b4c0c93d9 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,8 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "block-no-empty": null, + "at-rule-empty-line-before": null, + "rule-non-nested-empty-line-before": null + } +} diff --git a/gulpfile.ts b/gulpfile.ts index 2791b3e52..2799a08ff 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -12,7 +12,9 @@ loadTasks(PROJECT_TASKS_DIR); gulp.task('build.dev', done => runSequence('clean.dev', 'tslint', + 'css-lint', 'build.assets.dev', + 'build.html_css', 'build.js.dev', 'build.index.dev', done)); @@ -39,8 +41,10 @@ gulp.task('build.e2e', done => gulp.task('build.prod', done => runSequence('clean.prod', 'tslint', + 'css-lint', 'build.assets.prod', - 'build.html_css.prod', + 'build.html_css', + 'copy.js.prod', 'build.js.prod', 'build.bundles', 'build.bundles.app', diff --git a/package.json b/package.json index f1142ce60..853d38925 100644 --- a/package.json +++ b/package.json @@ -33,23 +33,28 @@ "license": "MIT", "devDependencies": { "async": "^1.4.2", + "autoprefixer": "^6.3.3", "browser-sync": "^2.11.1", "chalk": "^1.1.1", + "colorguard": "^1.0.1", "connect": "^3.4.1", "connect-history-api-fallback": "^1.1.0", "connect-livereload": "^0.5.3", + "cssnano": "^3.5.2", "del": "^2.2.0", + "doiuse": "^2.3.0", "event-stream": "^3.3.2", "express": "~4.13.1", "extend": "^3.0.0", "gulp": "^3.9.0", + "gulp-cached": "^1.1.0", "gulp-concat": "^2.5.2", - "gulp-cssnano": "^2.0.0", "gulp-filter": "^4.0.0", "gulp-inject": "^3.0.0", "gulp-inline-ng2-template": "^1.1.0", "gulp-load-plugins": "^1.2.0", "gulp-plumber": "~1.1.0", + "gulp-postcss": "^6.1.0", "gulp-shell": "~0.5.2", "gulp-sourcemaps": "~1.6.0", "gulp-template": "^3.0.0", @@ -74,6 +79,7 @@ "ng2lint": "0.0.10", "open": "0.0.5", "phantomjs-prebuilt": "^2.1.4", + "postcss-reporter": "^1.3.3", "protractor": "^3.0.0", "remap-istanbul": "^0.5.1", "rimraf": "^2.5.2", @@ -82,6 +88,8 @@ "serve-static": "^1.10.2", "slash": "~1.0.0", "stream-series": "^0.1.1", + "stylelint": "^4.5.1", + "stylelint-config-standard": "^4.0.0", "systemjs-builder": "^0.15.10", "tiny-lr": "^0.2.1", "traceur": "^0.0.91", diff --git a/src/app/components/navbar.component.css b/src/app/components/navbar.component.css index aaae26d93..a888f3a8d 100644 --- a/src/app/components/navbar.component.css +++ b/src/app/components/navbar.component.css @@ -1,5 +1,5 @@ :host { - border-color: #E1E1E1; + border-color: #e1e1e1; border-style: solid; border-width: 0 0 1px; display: block; @@ -8,7 +8,7 @@ } nav a { - color: #8F8F8F; + color: #8f8f8f; font-size: 14px; font-weight: 500; line-height: 48px; @@ -18,5 +18,5 @@ nav a { } nav a.router-link-active { - color: #106CC8; + color: #106cc8; } diff --git a/src/app/components/toolbar.component.css b/src/app/components/toolbar.component.css index ce098ac81..77f061f95 100644 --- a/src/app/components/toolbar.component.css +++ b/src/app/components/toolbar.component.css @@ -1,6 +1,6 @@ :host { - background-color: #106CC8; - color: rgba(255,255,255,0.87); + background-color: #106cc8; + color: rgba(255, 255, 255, 0.87); display: block; height: 48px; padding: 0 16px; diff --git a/src/assets/main.css b/src/assets/main.css index 653298c60..0c3a28b01 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -1,12 +1,16 @@ /* Reset */ -html, body, div { +html, +body, +div { border: 0; margin: 0; padding: 0; } /* Box-sizing border-box */ -* { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } +* { + box-sizing: border-box; +} /* Set up a default font and some padding to provide breathing room */ body { @@ -18,7 +22,7 @@ body { p { font-weight: 400; - letter-spacing: 0.010em; + letter-spacing: 0.01em; line-height: 20px; margin-bottom: 1em; margin-top: 1em; @@ -35,7 +39,7 @@ li { } input { - border: 1px solid #106CC8; + border: 1px solid #106cc8; font-size: 14px; height: 40px; outline: none; @@ -43,9 +47,9 @@ input { } button { - background-color: #106CC8; + background-color: #106cc8; border-style: none; - color: rgba(255,255,255,0.87); + color: rgba(255, 255, 255, 0.87); cursor: pointer; display: inline-block; font-size: 14px; @@ -55,5 +59,5 @@ button { } button:hover { - background-color: #28739E; + background-color: #28739e; } diff --git a/tools/config/seed.config.interface.ts b/tools/config/seed.config.interface.ts index 080e2c4b7..17c1c4fce 100644 --- a/tools/config/seed.config.interface.ts +++ b/tools/config/seed.config.interface.ts @@ -1,4 +1,5 @@ export interface InjectableDependency { src: string; inject: string | boolean; + vendor?: boolean; } diff --git a/tools/config/seed.config.ts b/tools/config/seed.config.ts index 8bd8535a9..2ed4bd4bd 100644 --- a/tools/config/seed.config.ts +++ b/tools/config/seed.config.ts @@ -74,7 +74,7 @@ export class SeedConfig { // Declare local files that needs to be injected APP_ASSETS: InjectableDependency[] = [ - { src: `${this.ASSETS_SRC}/main.css`, inject: true } + { src: `${this.ASSETS_SRC}/main.css`, inject: true, vendor: false } ]; @@ -107,6 +107,20 @@ export class SeedConfig { '*': 'node_modules/*' } }; + + // ---------------- + // Autoprefixer configuration. + BROWSER_LIST = [ + 'ie >= 10', + 'ie_mob >= 10', + 'ff >= 30', + 'chrome >= 34', + 'safari >= 7', + 'opera >= 23', + 'ios >= 7', + 'android >= 4.4', + 'bb >= 10' + ]; } diff --git a/tools/manual_typings/seed/autoprefixer.d.ts b/tools/manual_typings/seed/autoprefixer.d.ts new file mode 100644 index 000000000..9108b40ba --- /dev/null +++ b/tools/manual_typings/seed/autoprefixer.d.ts @@ -0,0 +1,13 @@ +declare module 'autoprefixer' { + + interface IOptions { + browsers: string[]; + } + + interface IAutoprefixer { + (opts?: IOptions): NodeJS.ReadWriteStream; + } + + const autoprefixer: IAutoprefixer; + export = autoprefixer; +} diff --git a/tools/manual_typings/seed/colorguard.d.ts b/tools/manual_typings/seed/colorguard.d.ts new file mode 100644 index 000000000..717773caa --- /dev/null +++ b/tools/manual_typings/seed/colorguard.d.ts @@ -0,0 +1,15 @@ +declare module 'colorguard' { + + interface IOptions { + ignore?: string[]; + threshold?: number; + whitelist?: string[]; + } + + interface IColorguard { + (opts?: IOptions): NodeJS.ReadWriteStream; + } + + const colorguard: IColorguard; + export = colorguard; +} diff --git a/tools/manual_typings/seed/cssnano.d.ts b/tools/manual_typings/seed/cssnano.d.ts new file mode 100644 index 000000000..99390058f --- /dev/null +++ b/tools/manual_typings/seed/cssnano.d.ts @@ -0,0 +1,15 @@ +declare module 'cssnano' { + + interface IOptions { + discardComments: { + removeAll: boolean; + }; + } + + interface ICssnano { + (opts?: IOptions): NodeJS.ReadWriteStream; + } + + const cssnano: ICssnano; + export = cssnano; +} diff --git a/tools/manual_typings/seed/doiuse.d.ts b/tools/manual_typings/seed/doiuse.d.ts new file mode 100644 index 000000000..03be285c8 --- /dev/null +++ b/tools/manual_typings/seed/doiuse.d.ts @@ -0,0 +1,16 @@ +declare module 'doiuse' { + + interface IOptions { + browsers?: string[]; + ignore?: string[]; + ignoreFiles?: string[]; + onFeatureUsage?: Function; + } + + interface IDoiuse { + (opts?: IOptions): NodeJS.ReadWriteStream; + } + + const doiuse: IDoiuse; + export = doiuse; +} diff --git a/tools/manual_typings/seed/postcss-reporter.d.ts b/tools/manual_typings/seed/postcss-reporter.d.ts new file mode 100644 index 000000000..9bae4547f --- /dev/null +++ b/tools/manual_typings/seed/postcss-reporter.d.ts @@ -0,0 +1,20 @@ +declare module 'postcss-reporter' { + + interface IOptions { + clearMessages?: boolean; + formatter?: Function; + plugins?: string[]; + throwError?: boolean; + sortByPosition?: boolean; + positionless?: string; + noIcon?: boolean; + noPlugin?: boolean; + } + + interface IPostcssReporter { + (opts?: IOptions): NodeJS.ReadWriteStream; + } + + const postcssReporter: IPostcssReporter; + export = postcssReporter; +} diff --git a/tools/manual_typings/seed/stylelint.d.ts b/tools/manual_typings/seed/stylelint.d.ts new file mode 100644 index 000000000..d7a8da107 --- /dev/null +++ b/tools/manual_typings/seed/stylelint.d.ts @@ -0,0 +1,16 @@ +declare module 'stylelint' { + + interface IOptions { + config?: Object; + configFile?: string; + configBasedir?: string; + configOverrides?: Object; + } + + interface IStylelint { + (opts?: IOptions): NodeJS.ReadWriteStream; + } + + const stylelint: IStylelint; + export = stylelint; +} diff --git a/tools/tasks/seed/build.html_css.prod.ts b/tools/tasks/seed/build.html_css.prod.ts deleted file mode 100644 index eebb5d123..000000000 --- a/tools/tasks/seed/build.html_css.prod.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as gulp from 'gulp'; -import * as gulpLoadPlugins from 'gulp-load-plugins'; -import * as merge from 'merge-stream'; -import {join} from 'path'; -import {APP_SRC, TMP_DIR, PROD_DEPENDENCIES, CSS_PROD_BUNDLE, CSS_DEST} from '../../config'; -const plugins = gulpLoadPlugins(); - -export = () => merge(minifyComponentCss(), prepareTemplates(), processExternalCss()); - -function prepareTemplates() { - return gulp.src(join(APP_SRC, '**', '*.html')) - .pipe(gulp.dest(TMP_DIR)); -} - -function minifyComponentCss() { - return gulp.src([ - join(APP_SRC, '**', '*.css'), - '!' + join(APP_SRC, 'assets', '**', '*.css') - ]) - .pipe(plugins.cssnano()) - .pipe(gulp.dest(TMP_DIR)); -} - -function processExternalCss() { - return gulp.src(getExternalCss().map(r => r.src)) - .pipe(plugins.cssnano()) - .pipe(plugins.concat(CSS_PROD_BUNDLE)) - .pipe(gulp.dest(CSS_DEST)); -} - -function getExternalCss() { - return PROD_DEPENDENCIES.filter(d => /\.css$/.test(d.src)); -} diff --git a/tools/tasks/seed/build.html_css.ts b/tools/tasks/seed/build.html_css.ts new file mode 100644 index 000000000..c8f71b850 --- /dev/null +++ b/tools/tasks/seed/build.html_css.ts @@ -0,0 +1,54 @@ +import * as gulp from 'gulp'; +import * as gulpLoadPlugins from 'gulp-load-plugins'; +import * as merge from 'merge-stream'; +import * as autoprefixer from 'autoprefixer'; +import * as cssnano from 'cssnano'; +import {join} from 'path'; +import {APP_SRC, TMP_DIR, APP_ASSETS, CSS_PROD_BUNDLE, CSS_DEST, APP_DEST, BROWSER_LIST, ENV} from '../../config'; +const plugins = gulpLoadPlugins(); + +const processors = [ + autoprefixer({ + browsers: BROWSER_LIST + }) +]; + +const isProd = ENV === 'prod'; + +if (isProd) { + processors.push( + cssnano({ + discardComments: {removeAll: true} + }) + ); +} + +function prepareTemplates() { + return gulp.src(join(APP_SRC, '**', '*.html')) + .pipe(gulp.dest(TMP_DIR)); +} + +function processComponentCss() { + return gulp.src([ + join(APP_SRC, '**', '*.css'), + '!' + join(APP_SRC, 'assets', '**', '*.css') + ]) + .pipe(isProd ? plugins.cached('process-component-css') : plugins.util.noop()) + .pipe(plugins.postcss(processors)) + .pipe(gulp.dest(isProd ? TMP_DIR: APP_DEST)); +} + +function processExternalCss() { + return gulp.src(getExternalCss().map(r => r.src)) + .pipe(isProd ? plugins.cached('process-external-css') : plugins.util.noop()) + .pipe(plugins.postcss(processors)) + .pipe(isProd ? plugins.concat(CSS_PROD_BUNDLE) : plugins.util.noop()) + .pipe(gulp.dest(CSS_DEST)); +} + +function getExternalCss() { + return APP_ASSETS.filter(d => /\.css$/.test(d.src)); +} + + +export = () => merge(processComponentCss(), prepareTemplates(), processExternalCss()); diff --git a/tools/tasks/seed/build.js.prod.ts b/tools/tasks/seed/build.js.prod.ts index e2a7f332a..a7cdefb00 100644 --- a/tools/tasks/seed/build.js.prod.ts +++ b/tools/tasks/seed/build.js.prod.ts @@ -1,7 +1,7 @@ import * as gulp from 'gulp'; import * as gulpLoadPlugins from 'gulp-load-plugins'; import {join} from 'path'; -import {APP_SRC, TMP_DIR, TOOLS_DIR} from '../../config'; +import {TMP_DIR, TOOLS_DIR} from '../../config'; import {templateLocals, makeTsProject} from '../../utils'; const plugins = gulpLoadPlugins(); @@ -16,9 +16,7 @@ export = () => { let src = [ 'typings/browser.d.ts', TOOLS_DIR + '/manual_typings/**/*.d.ts', - join(APP_SRC, '**/*.ts'), - '!' + join(APP_SRC, '**/*.spec.ts'), - '!' + join(APP_SRC, '**/*.e2e.ts') + join(TMP_DIR, '**/*.ts') ]; let result = gulp.src(src) .pipe(plugins.plumber()) diff --git a/tools/tasks/seed/copy.js.prod.ts b/tools/tasks/seed/copy.js.prod.ts new file mode 100644 index 000000000..0d1cf3e28 --- /dev/null +++ b/tools/tasks/seed/copy.js.prod.ts @@ -0,0 +1,12 @@ +import * as gulp from 'gulp'; +import {join} from 'path'; +import {APP_SRC, TMP_DIR} from '../../config'; + +export = () => { + return gulp.src([ + join(APP_SRC, '**/*.ts'), + '!' + join(APP_SRC, '**/*.spec.ts'), + '!' + join(APP_SRC, '**/*.e2e.ts') + ]) + .pipe(gulp.dest(TMP_DIR)); +}; diff --git a/tools/tasks/seed/css-lint.ts b/tools/tasks/seed/css-lint.ts new file mode 100644 index 000000000..a759da3c2 --- /dev/null +++ b/tools/tasks/seed/css-lint.ts @@ -0,0 +1,43 @@ +import * as gulp from 'gulp'; +import * as gulpLoadPlugins from 'gulp-load-plugins'; +import * as merge from 'merge-stream'; +import * as reporter from 'postcss-reporter'; +import * as stylelint from 'stylelint'; +import * as doiuse from 'doiuse'; +import * as colorguard from 'colorguard'; +import {join} from 'path'; +import {APP_SRC, APP_ASSETS, BROWSER_LIST, ENV} from '../../config'; +const plugins = gulpLoadPlugins(); + +const isProd = ENV === 'prod'; + +const processors = [ + doiuse({ + browsers: BROWSER_LIST, + }), + colorguard(), + stylelint(), + reporter({clearMessages: true}) +]; + +function lintComponentCss() { + return gulp.src([ + join(APP_SRC, '**', '*.css'), + '!' + join(APP_SRC, 'assets', '**', '*.css') + ]) + .pipe(isProd ? plugins.cached('css-lint') : plugins.util.noop()) + .pipe(plugins.postcss(processors)); +} + +function lintExternalCss() { + return gulp.src(getExternalCss().map(r => r.src)) + .pipe(isProd ? plugins.cached('css-lint') : plugins.util.noop()) + .pipe(plugins.postcss(processors)); +} + +function getExternalCss() { + return APP_ASSETS.filter(d => /\.css$/.test(d.src) && !d.vendor); +} + + +export = () => merge(lintComponentCss(), lintExternalCss());