├── .babelignore ├── .babelrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.MD ├── JSCS.intellij.formatter.xml ├── LICENSE.MD ├── README.md ├── UPGRADE.MD ├── gulp ├── config.js ├── tasks │ ├── check-js-quality.js │ ├── check-js-style.js │ ├── clean.js │ ├── scripts-javascript-dist.js │ ├── test-unit.js │ └── validate-package-json.js └── utils.js ├── gulpfile.babel.js ├── karma.conf.js ├── package.json └── src ├── gulp ├── abstractTaskLoader.js ├── config.js ├── tasks │ ├── check-js-quality.js │ ├── check-js-style.js │ ├── clean.js │ ├── copy.js │ ├── default.js │ ├── html.js │ ├── images.js │ ├── scripts-javascript-dist.js │ ├── scripts-javascript.js │ ├── scripts-typescript.js │ ├── serve-dist.js │ ├── serve.js │ ├── styles-dist.js │ ├── styles-vendor-dist.js │ ├── styles.js │ ├── test-unit.js │ ├── ts-lint.js │ └── validate-package-json.js ├── templates │ └── taskLoaderTemplate.js └── utils.js ├── index.js └── tests └── sanity_test.spec.js /.babelignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/modernWebDevBuild/6fb5ddb10bfd56d022d2d746da0d92cc174fb1c6/.babelignore -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-es2015-modules-commonjs"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://editorconfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Unix-style newlines with a newline ending every file. 8 | # Tab for indentation, whitespace trimming and UTF-8 encoding 9 | [*] 10 | end_of_line = lf 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 4 14 | trim_trailing_whitespace = false 15 | charset = utf-8 16 | 17 | [*.bat] 18 | end_of_line = crlf 19 | 20 | [{package.json,.travis.yml}] 21 | indent_style = space 22 | indent_size = 2 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific 2 | *.log 3 | dist/ 4 | 5 | # NPM 6 | node_modules/ 7 | 8 | # JSPM 9 | jspm_packages/ 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov/ 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage/ 16 | 17 | # Windows 18 | Desktop.ini 19 | 20 | # JetBrains IDEs 21 | *.iml 22 | .idea/ 23 | .webstorm/ 24 | 25 | # OSX 26 | .DS_Store 27 | .AppleDouble 28 | .LSOverride 29 | # Icon must end with two \r 30 | Icon 31 | 32 | # Sublime text 33 | .sublime-gulp.cache 34 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "validateAlignedFunctionParameters": { 4 | "lineBreakAfterOpeningBraces": false, 5 | "lineBreakBeforeClosingBraces": false 6 | }, 7 | "validateIndentation": 4, 8 | "validateNewlineAfterArrayElements": { 9 | "maximum": 2 10 | }, 11 | "validateParameterSeparator": ", ", 12 | "validateQuoteMarks": { 13 | "mark": "\"", 14 | "escape": true 15 | }, 16 | "excludeFiles": [ 17 | "node_modules/**", 18 | "jspm_packages/**" 19 | ], 20 | "fileExtensions": [ 21 | ".js" 22 | ], 23 | "requireSemicolons": true, 24 | "maximumLineLength": null, 25 | "disallowTrailingComma": true, 26 | "disallowTrailingWhitespace": "ignoreEmptyLines", 27 | "disallowDanglingUnderscores": { 28 | "allExcept": [ 29 | "_exception" 30 | ] 31 | }, 32 | "disallowEmptyBlocks": null, 33 | "disallowImplicitTypeConversion": null, 34 | "disallowKeywordsOnNewLine": [ 35 | "else" 36 | ], 37 | "disallowKeywords": [ 38 | "with" 39 | ], 40 | "disallowMixedSpacesAndTabs": true, 41 | "disallowMultipleLineBreaks": true, 42 | "disallowMultipleSpaces": null, 43 | "disallowNamedUnassignedFunctions": true, 44 | "disallowNewlineBeforeBlockStatements": true, 45 | "requirePaddingNewLinesAfterBlocks": { 46 | "allExcept": [ 47 | "inCallExpressions", 48 | "inArrayExpressions", 49 | "inProperties" 50 | ] 51 | }, 52 | "disallowPaddingNewlinesInBlocks": true, 53 | "requireSpaceAfterBinaryOperators": [ 54 | "=", 55 | ",", 56 | "+", 57 | "-", 58 | "/", 59 | "*", 60 | "==", 61 | "===", 62 | "!=", 63 | "!==" 64 | ], 65 | "requireSpaceBeforeBinaryOperators": [ 66 | "=", 67 | "+", 68 | "-", 69 | "/", 70 | "*", 71 | "==", 72 | "===", 73 | "!=", 74 | "!==" 75 | ], 76 | "disallowSpaceAfterKeywords": [ 77 | "if", 78 | "else", 79 | "for", 80 | "while", 81 | "do", 82 | "switch", 83 | "try", 84 | "catch", 85 | "function" 86 | ], 87 | "disallowSpaceAfterPrefixUnaryOperators": [ 88 | "++", 89 | "--", 90 | "+", 91 | "-", 92 | "~", 93 | "!" 94 | ], 95 | "disallowSpaceBeforeBlockStatements": true, 96 | "requireSpaceBeforeKeywords": [ 97 | "if", 98 | "else", 99 | "try", 100 | "catch" 101 | ], 102 | "disallowSpaceBeforePostfixUnaryOperators": [ 103 | "++", 104 | "--" 105 | ], 106 | "requireSpaceBetweenArguments": true, 107 | "disallowSpacesInAnonymousFunctionExpression": { 108 | "beforeOpeningRoundBrace": true, 109 | "beforeOpeningCurlyBrace": true 110 | }, 111 | "disallowSpacesInCallExpression": true, 112 | "disallowSpacesInFunctionDeclaration": { 113 | "beforeOpeningRoundBrace": true, 114 | "beforeOpeningCurlyBrace": true 115 | }, 116 | "disallowSpacesInFunctionExpression": { 117 | "beforeOpeningRoundBrace": true, 118 | "beforeOpeningCurlyBrace": true 119 | }, 120 | "disallowSpacesInsideParentheses": true, 121 | "disallowSpacesInFunction": { 122 | "beforeOpeningRoundBrace": true 123 | }, 124 | "disallowSpacesInNamedFunctionExpression": { 125 | "beforeOpeningRoundBrace": true, 126 | "beforeOpeningCurlyBrace": true 127 | }, 128 | "requireSpacesInsideArrayBrackets": "all", 129 | "requireSpacesInsideObjectBrackets": { 130 | "allExcept": [ 131 | "}", 132 | ")" 133 | ] 134 | }, 135 | "requireSpacesInsideParentheses": null, 136 | "requireYodaConditions": null, 137 | "requireAnonymousFunctions": true, 138 | "requireBlocksOnNewline": true, 139 | "requireCamelCaseOrUpperCaseIdentifiers": true, 140 | "requireCapitalizedConstructors": true, 141 | "requireCommaBeforeLineBreak": true, 142 | "requireCurlyBraces": true, 143 | "requireDotNotation": true, 144 | "requireFunctionDeclarations": null, 145 | "requireLineBreakAfterVariableAssignment": true, 146 | "requireLineFeedAtFileEnd": true, 147 | "disallowMultipleVarDecl": true, 148 | "requireOperatorBeforeLineBreak": [ 149 | "?", 150 | "=", 151 | "+", 152 | "-", 153 | "/", 154 | "*", 155 | "==", 156 | "===", 157 | "!=", 158 | "!==", 159 | ">", 160 | ">=", 161 | "<", 162 | "<=" 163 | ], 164 | "requirePaddingNewLineAfterVariableDeclaration": true, 165 | "requirePaddingNewLinesAfterUseStrict": true, 166 | "requirePaddingNewLinesBeforeExport": true, 167 | "requirePaddingNewlinesBeforeKeywords": [ 168 | "do", 169 | "for", 170 | "if", 171 | "switch", 172 | "case", 173 | "try", 174 | "void", 175 | "while", 176 | "with", 177 | "return", 178 | "typeof", 179 | "function" 180 | ], 181 | "requirePaddingNewLinesBeforeLineComments": null, 182 | "requireParenthesesAroundIIFE": null, 183 | "requireSpaceAfterLineComment": null, 184 | "requireSpacesInConditionalExpression": { 185 | "afterTest": true, 186 | "beforeConsequent": true, 187 | "afterConsequent": true, 188 | "beforeAlternate": true 189 | }, 190 | "requireSpacesInForStatement": true, 191 | "requirePaddingNewLinesInObjects": true, 192 | "disallowQuotedKeysInObjects": true, 193 | "requireAlignedObjectValues": null, 194 | "disallowSpaceAfterObjectKeys": true, 195 | "requireSpaceBeforeObjectValues": true, 196 | "disallowSpaceBeforeComma": true, 197 | "disallowSpaceBeforeSemicolon": true, 198 | "requireVarDeclFirst": null, 199 | "requireMatchingFunctionName": true, 200 | "requireObjectKeysOnNewLine": true, 201 | "disallowNodeTypes": [ 202 | "LabeledStatement" 203 | ], 204 | "requireArrowFunctions": true, 205 | "requireNumericLiterals": true, 206 | "requireTemplateStrings": null 207 | } 208 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | jspm_packages/**/* 3 | jspm.conf.js 4 | dist/**/* 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "strict": "global", 4 | "devel": true, 5 | "browser": true, 6 | "forin": true, 7 | "curly": true, 8 | "bitwise": true, 9 | "eqeqeq": true, 10 | "freeze": true, 11 | "futurehostile": true, 12 | "latedef": "nofunc", 13 | "maxdepth": 5, 14 | "maxparams": 10, 15 | "maxstatements": 100, 16 | "noarg": true, 17 | "nocomma": true, 18 | "nonbsp": true, 19 | "nonew": true, 20 | "singleGroups": true, 21 | "undef": true, 22 | "unused": true, 23 | "jquery": true, 24 | "mocha": true, 25 | "jasmine": true, 26 | "module": true, 27 | "varstmt": true, 28 | "eqnull": true, 29 | "globals": { 30 | "protractor": false, 31 | "module": false, 32 | "require": false, 33 | "process": false, 34 | "someWordYouShouldNotCheck": false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Reference: https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package 2 | 3 | # Project specific 4 | src/ 5 | gulp/ 6 | test/ 7 | coverage/ 8 | .coveralls.yml 9 | .editorconfig 10 | .jscsrc 11 | .jshintignore 12 | .jshintrc 13 | .travis.yml 14 | .nvmrc 15 | gulpfile.babel.js 16 | gulpfile.js 17 | 18 | # Misc 19 | *.log 20 | 21 | # NPM 22 | node_modules/ 23 | npm-debug.log 24 | ./.npmignore 25 | 26 | # Windows 27 | Desktop.ini 28 | 29 | # JetBrains IDEs 30 | *.iml 31 | .idea/ 32 | .webstorm/ 33 | 34 | # Git 35 | .git 36 | .gitattributes 37 | .gitignore 38 | .gitmodules 39 | 40 | # OSX 41 | .DS_Store 42 | .AppleDouble 43 | .LSOverride 44 | # Icon must end with two \r 45 | Icon 46 | 47 | # Sublime text 48 | .sublime-gulp.cache 49 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 4.3.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | - jspm_packages 7 | branches: 8 | only: 9 | - master 10 | notifications: 11 | email: true 12 | node_js: 13 | - '4.2' 14 | before_script: 15 | - npm prune 16 | before_install: 17 | - npm run setup 18 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | * 0.5.4 2 | * fixed #130 (undefined.emit) 3 | * 0.5.3 4 | * now less strict with dependencies (#118) 5 | * updated dependencies 6 | * 0.5.2 7 | * added tsx support for watches (#110) 8 | * replaced gulp-minify-css by gulp-clean-css 9 | * 0.5.1 10 | * fixed issue with the production build (404 at first render) 11 | * 0.5.0 12 | * upgraded dependencies to support TypeScript 1.8.x 13 | * 0.4.1 14 | * added the "browserSync" option, which lets you customize all BrowserSync settings (thanks @aaronroberson) 15 | * 0.4.0 16 | * fixed an issue with production styles bundle (fixes #96) 17 | * upgraded babel dependencies 18 | * check the [upgrade](UPGRADE.MD) notes to know what you need to change 19 | * 0.3.2 20 | * Fixed production bundles paths (css, js) (fixes #91) 21 | * 0.3.1 22 | * made JSPM optional (closes #77) 23 | * JSPM remains used by default but you can now customize the behavior by setting the useJSPM option to false 24 | * if you disable JSPM then you can provide a custom name for the configuration file (the build defaults to jspm.conf.js) 25 | * fixed an issue with the tasks chaining (callbacks were used needlessly) (closes #89) 26 | * upgraded the version of SystemJS-builder (closes #87) 27 | * 0.3.0 28 | * Breaking change: improved integration with tsconfig.json (see #72) 29 | * this might impact existing projects 30 | * before, the list of folders to include in TS compilation were hardcoded. Now, the tsconfig.json file is used, thus you have more control (but also more responsibility) 31 | * removed mentions of tsd. My current recommendation is to use `typings`, although there's nothing forcing you to with the build 32 | * https://github.com/typings/typings 33 | * https://angularclass.com/the-state-of-typescript-packages/ 34 | * upgraded dependencies 35 | * Babel dependencies 6.3.x to 6.4.x 36 | * autoprefixer 6.1.x to 6.2.x 37 | * Known issues 38 | * #76: there is an issue with the way that gulp-typescript (the plugin used within the scripts-typescript gulp task) handles the 'rootDir' property, which is inconsistent with tsc. 39 | * to solve this, for now you need to add a reference to the main typings file of your project; Example: 40 | ``` 41 | /// 42 | ``` 43 | * the final solution might come via the following issues and might require a new release of this project 44 | * https://github.com/ivogabe/gulp-typescript/issues/190 45 | * https://github.com/typings/typings/issues/69 46 | * 0.2.3 47 | * fixed sourcemap generation issues (see #69) 48 | * sourcemaps sometime require a browser refresh before actually working: this might be due to a related issue (timing issue?) 49 | * removed legacy leftovers 50 | * 0.2.2 51 | * added an option to define whether the production HTML should be minified or not: minifyProductionHTML (true by default) 52 | * Angular 2 does not support HTML minification anymore: https://github.com/dsebastien/modernWebDevBuild/issues/67 53 | * 0.2.1 54 | * minify the production bundle by default 55 | * added the gulp-inline-source plugin (https://www.npmjs.com/package/gulp-inline-source) 56 | * allows to inline resources (scripts, stylesheets, images) within HTML files 57 | * for example very useful for cases where some script can't/musn't be loaded through SystemJS (e.g., zone.js, reflect-metadata, Angular 2 polyfills, ...) 58 | * added an option to define whether the production JS bundle must be minified or not: minifyProductionJSBundle (true by default) 59 | * added an option to define whether the production JS bundle must be mangled or not: mangleProductionJSBundle (true by default) 60 | * 0.2.0 61 | * BREAKING CHANGE: scripts-javascript-dist now uses core/boot.js as entry point (vs core/core.bootstrap.js in earlier versions) (fixes # 62 | * this new default behavior can be customized by specifying the `distEntryPoint` option (see README or UPGRADE guide) 63 | * 0.1.1 64 | * added unit testing support with karma & jasmine 65 | * removed gulp-tsd dependency 66 | * removed tsd dependency 67 | * fix for #33 avoid the need for installing global dependencies 68 | * added support for passing options to the build (for now only used internally) 69 | * tagged dependencies that should be in client projects as peerDependencies 70 | * removed babel 6 plugins/presets from the peerDependencies 71 | * client projects are free to choose what they want to use 72 | * 0.1.0 73 | * Upgraded to Babel 6 74 | * now requires a .babelrc configuration file to be present in the project's main folder 75 | * now requires additional Babel 6 dependencies: 76 | * "babel-core": "6.1.x", 77 | * "babel-plugin-transform-es2015-modules-commonjs": "6.1.x", 78 | * "babel-preset-es2015": "6.1.x", 79 | * "babel-runtime": "6.1.x", 80 | * Updated dependencies 81 | * 0.0.9 82 | * Fixed watch/reload issues with BrowserSync 83 | * Removed the 'gen-ts-refs' task. No longer needed now that TS can get typings from node modules 84 | * Updated docs 85 | * 0.0.8 86 | * Added support for both TypeScript and ES2015 87 | * Changed back the way the build is organized 88 | * TS > ES5 89 | * ES2015 > ES5 90 | * Removed unnecessary package.json validation 91 | * 0.0.7 92 | * Fixed an npm publish issue (configured files and folders to include and exclude from the page) 93 | * 0.0.6 94 | * Updated dependencies 95 | * Fixed an npm publish issue 96 | * 0.0.5 97 | * First functional version ;-) 98 | * 0.0.4 99 | * Fixed an issue with Gulp and Babel 100 | * 0.0.3 101 | * Fixed an issue with Gulp and Babel 102 | * 0.0.2 103 | * Fixed an issue with node-sass 104 | * 0.0.1 105 | * Initial version 106 | -------------------------------------------------------------------------------- /JSCS.intellij.formatter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 28 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) dSebastien (https://www.dsebastien.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern Web Dev Build 2 | 3 | [![NPM version](https://img.shields.io/npm/v/modern-web-dev-build.svg)](https://www.npmjs.com/package/modern-web-dev-build) 4 | [![Downloads](https://img.shields.io/npm/dm/modern-web-dev-build.svg)](https://www.npmjs.com/package/modern-web-dev-build) 5 | [![Build Status](https://secure.travis-ci.org/dsebastien/modernWebDevBuild.png?branch=master)](https://travis-ci.org/dsebastien/modernWebDevBuild) 6 | [![Coverage Status]( 7 | https://coveralls.io/repos/dsebastien/modernWebDevBuild/badge.svg?branch=master&service=github 8 | )]( 9 | https://coveralls.io/github/dsebastien/modernWebDevBuild?branch=master 10 | ) 11 | [![Dependency Status](https://david-dm.org/dsebastien/modernWebDevBuild.svg?theme=shields.io&style=flat)](https://david-dm.org/dsebastien/modernWebDevBuild) 12 | [![devDependency Status](https://david-dm.org/dsebastien/modernWebDevBuild/dev-status.svg?theme=shields.io&style=flat)](https://david-dm.org/dsebastien/modernWebDevBuild#info=devDependencies) 13 | [![Gitter](https://img.shields.io/badge/gitter-join%20chat-green.svg?style=flat)](https://gitter.im/dsebastien/modernWebDevBuild?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 14 | [![License](https://img.shields.io/cocoapods/l/AFNetworking.svg)](LICENSE.MD) 15 | 16 | ## About 17 | A modern build for Web development. 18 | 19 | Get started and use ES2015, TypeScript, SASS, code quality & style checking, testing, minification, bundling and whatnot TODAY! :) 20 | 21 | ModernWebDevBuild abstracts away all the build pipeline boilerplate. Use it if you're not willing to dive too deep in the boring details of how to setup a proper build chain that takes care of transpiling, minifying, optimizing images and whatnot for production. 22 | 23 | This project is very opinionated and technology choices are embedded. Although, the build is pretty flexible about code/assets organization (to some extent). Over time, it'll be interesting to see how customizable we can make this thing. 24 | 25 | The provided build tasks are based on [Gulp](http://gulpjs.com/). Instructions are available below to get you started. 26 | 27 | This project is available as an npm package: https://www.npmjs.com/package/modern-web-dev-build 28 | 29 | ## Demo 30 | ModernWebDev Build and Generator Demo 33 | 34 | ## Background 35 | 36 | The idea for this project emerged as I was rediscovering the state of the art for Web development (early 2015) and from my frustration of not finding everything I needed in a ready-to-use form. 37 | 38 | What surprised me at first was that tooling had become so much more complex than it was in the past. I would argue that it is way too complex nowadays and that isn't good for the accessibility of the Web platform. Unfortunately for now, there aren't many alternatives and the benefits of a good build chain are too important to keep aside (who wouldn't want to use all the good stuff ES2015 has brought us?). 39 | 40 | Note that this project is heavily inspired from: 41 | * Google's [Web Starter Kit](https://github.com/google/web-starter-kit) 42 | * Countless blog articles about Gulp, TypeScript, ... 43 | * Many others I'm forgetting :( 44 | 45 | ## Features 46 | * ES2015 and TypeScript support 47 | * built-in HTTP server with live reloading & cross-device synchronization (BrowserSync) 48 | * configured to support CORS 49 | * awesome developer experience with a change detection mechanism that automagically: 50 | * transpiles TypeScript > ESx w/ sourcemaps (you choose the target version) 51 | * transpiles ES2015 > ESx w/ sourcemaps (you choose the target version) 52 | * transpiles SASS > CSS w/ sourcemaps 53 | * checks JavaScript/TypeScript code quality/style and report on the console (without breaking the build) 54 | * ... 55 | * production bundle creation support with: 56 | * CSS bundle creation 57 | * CSS optimization & minification 58 | * JS bundle creation 59 | * JS minification 60 | * HTML minification 61 | * images optimization 62 | * ... 63 | 64 | Check out the [change log](CHANGELOG.MD) 65 | 66 | ## Status & roadmap 67 | Check out the issues/labels & milestones to get an idea of what's next. 68 | For existing features, refer to the previous section. 69 | 70 | ## Embedded choices 71 | As state above, some important technology choices are clearly embedded with this project. Here's a rundown of those choices: 72 | * [TypeScript](http://www.typescriptlang.org/) and ES2015 (although the final output is ES5 for wider compatibility) 73 | * [SystemJS](https://github.com/systemjs/systemjs): module loader 74 | * [JSPM](http://jspm.io/) to manage your application dependencies (through jspm.conf.js) 75 | * JSPM support can be disabled if you wish 76 | * [Karma](http://karma-runner.github.io/) to run tests 77 | * [SASS](http://sass-lang.com/): who doesn't want variables and mixins? 78 | * component based code & assets organization (Angular friendly) 79 | * [JSCS](http://jscs.info/) and included code style rules 80 | * [JSHint](http://jshint.com/) and included code quality rules 81 | * [TSLint](https://github.com/palantir/tslint) and included code quality/style rules 82 | * [BrowserSync](http://www.browsersync.io/) development Web Server 83 | 84 | ## Upgrade 85 | Check out the [upgrade](UPGRADE.md) page 86 | 87 | ## Installation 88 | 89 | ### General prereqs 90 | Before you install the build (whether manually or through the project generator), you need to install some dependencies globally: 91 | * `npm install --global gulp` 92 | 93 | ### New projects 94 | The easiest approach to integrate this build is to use our Yeoman Generator available over at https://github.com/dsebastien/modernWebDevGenerator and on npm: https://www.npmjs.com/package/generator-modern-web-dev. 95 | The generator will set up (almost) everything for you. 96 | 97 | ### Existing projects 98 | First configure the required dependencies in your package.json file: 99 | * add Modern Web Dev Build to your devDependencies: `npm install modern-web-dev-build --save-dev` 100 | * execute `npm install --no-optional` 101 | 102 | You should get warnings about missing peer dependencies. Those are dependencies that are required by the build but that you should add to your own project. 103 | Install these one by one. 104 | 105 | For now the required peer dependencies are as follows: 106 | * babel-core 107 | * gulp 108 | * jspm 109 | * nodemon (required by recommended npm scripts) 110 | * typescript 111 | 112 | Next, check the minimal require file contents below! 113 | 114 | ## Required folder structure and files 115 | The build tries to provide a flexible structure, but given the technical choices that are embedded, some rules must be respected and the build expects certain folders and files to be present. In the future we'll see if we can make this more configurable. 116 | 117 | ### Mandatory folder structure & files 118 | Here's an overview of the structure imposed by ModernWebDevBuild. 119 | Note that if you've generated your project using the Yeoman generator, README files will be there to guide you. 120 | 121 | Please make sure to check the file organization section for more background about the organization and usage guidelines. 122 | 123 | * project root 124 | * app: folder containing all the files of the application 125 | * components: folder containing components of your application (e.g., login, menu, ...); basically reusable pieces 126 | * core: folder containing at least the entrypoint of your application 127 | * commons: folder containing common reusable code (e.g., base utilities) 128 | * services: folder containing generic services (e.g., for local storage) 129 | * boot.ts: the entrypoint of your application 130 | * app.ts: the application root 131 | * fonts: folder containing fonts of your application (if any) 132 | * images: folder for image assets 133 | * pages: folder for full-blown pages of your application 134 | * scripts: folder for scripts 135 | * styles: folder for the main stylesheets 136 | * main.scss: file used to import all application-specific stylesheets 137 | * vendor.scss: file used to import all third-party stylesheets 138 | * note that the goal isn't to put ALL your stylesheets in there, basically just the entrypoints and the generic parts (e.g., variables, mixins, responsive styles, ...) 139 | * index.html: the entrypoint of your application 140 | * .babelrc: Babel configuration file 141 | * .jscsrc: JSCS rule set to use while checking JavaScript code style 142 | * reference: http://jscs.info/overview 143 | * .jshintrc: JSHint rule set to use while checking JavaScript code quality 144 | * reference: http://jshint.com/docs/ 145 | * note that the file is actually optional but indeed recommended! 146 | * .jshintignore: files and folders to ignore while checking JavaScript code quality 147 | * gulpfile.babel.js: gulp configuration file 148 | * jspm.conf.js: SystemJS/JSPM configuration file 149 | * can have another name if you do not use JSPM (see options) 150 | * karma.conf.js: Karma configuration file (configuration of the test runner) 151 | * package.json: NPM configuration file (also used by JSPM) 152 | * tsconfig.json: TypeScript compiler configuration 153 | * tslint.json: TypeScript code quality/style rules 154 | 155 | ### Minimal (build-related) required file contents 156 | Although we want to limit this list as much as possible, for everything to build successfully, some files need specific contents: 157 | 158 | #### .babelrc 159 | ``` 160 | { 161 | "presets": ["es2015"], 162 | "plugins": ["transform-es2015-modules-commonjs"], 163 | "comments": false 164 | } 165 | ``` 166 | 167 | With the configuration above, Babel will transpile ES2015 code to ES5 commonjs. 168 | For that configuration to work, the following devDependencies must also be added to your project: 169 | 170 | ``` 171 | "babel-plugin-transform-es2015-modules-commonjs": "6.3.x", 172 | "babel-preset-es2015": "6.3.x", 173 | ``` 174 | 175 | #### gulpfile.babel.js 176 | In order to use ModernWebDevBuild, your gulpfile must at least contain the following. 177 | The code below uses ES2015 (via gulpfile.babel.js), but if you're old school you can also simply use a gulpfile.js with ES5. 178 | Note that the build tasks provided by ModernWebDevBuild are transpiled to ES5 before being published 179 | 180 | ``` 181 | "use strict"; 182 | 183 | import gulp from "gulp"; 184 | 185 | import modernWebDevBuild from "modern-web-dev-build"; 186 | let options = undefined; // no options are supported yet 187 | 188 | //options.minifyHTML = false; 189 | //... 190 | 191 | modernWebDevBuild.registerTasks(gulp, options); 192 | ``` 193 | 194 | With the above, all the gulp tasks provided by ModernWebDevBuild will be available to you. 195 | 196 | #### .jscsrc 197 | Valid configuration 198 | 199 | #### .jshintrc 200 | At least the following: 201 | 202 | ``` 203 | node_modules/**/* 204 | jspm_packages/**/* 205 | jspm.conf.js 206 | dist/**/* 207 | .tmp/**/* 208 | ``` 209 | 210 | #### jspm.conf.js 211 | The SystemJS/JSPM configuration file plays a very important role; 212 | * it is where all your actual application dependencies are to be defined 213 | * it is where you can define your own 'paths', allowing you to load modules of your application easily without having to specify relative paths (i.e., create aliases for paths). 214 | 215 | If you choose to use the default JSPM support, then you can add dependencies to your project using `jspm install`; check the [official JSPM documentation](http://jspm.io/) to know more about how to install packages. 216 | 217 | With the help of this configuration file, SystemJS will be able to load your own application modules and well as third party dependencies. 218 | In your code, you'll be able to use ES2015 style (e.g., `import {x} from "y"`). In order for this to work, you'll also need to load SystemJS and the SystemJS/JSPM configuration file in your index.html (more on this afterwards). 219 | 220 | If you have disabled the use of JSPM by the build then you can rename that file if you wish and inform the build (see the options), BUT make sure that any reference in the various configuration files is updated (e.g., karma configuration, jshint configuration, etc). Only rename it if it is really really useful to you :) 221 | 222 | Here's an example: 223 | ``` 224 | System.config({ 225 | defaultJSExtensions: true, 226 | transpiler: false, 227 | paths: { 228 | "github:*": "jspm_packages/github/*", 229 | "npm:*": "jspm_packages/npm/*", 230 | "core/*": "./.tmp/core/*", 231 | "pages/*": "./.tmp/pages/*" 232 | } 233 | }); 234 | ``` 235 | 236 | In the above: 237 | * defaultJSExtensions: is mandatory so that extensions don't have to be specified when importing modules 238 | * transpiler: is set to false because we don't use in-browser transpilation 239 | * paths 240 | * core/*, pages/* allow you to import modules from different parts of your codebase without having to specify relative or absolute paths. This is covered in the folder structure section above. 241 | * you can rename those if you wish 242 | 243 | #### package.json 244 | In addition to the dependencies listed previously, you also need to have the following in your package.json file, assuming that you want to use JSPM: 245 | 246 | ``` 247 | "jspm": { 248 | "directories": {}, 249 | "configFile": "jspm.conf.js", 250 | "dependencies": { 251 | }, 252 | "devDependencies: { 253 | } 254 | } 255 | ``` 256 | 257 | This is where you let JSPM know where to save the information about dependencies you install. This is also where you can easily add new dependencies; for example: `"angular2": "npm:angular2@^2.0.0-beta.1",`. 258 | Once a dependency is added there, you can invoke `jspm install` to get the files and transitive dependencies installed and get an updated jspm.conf.js file. 259 | 260 | #### tsconfig.json 261 | Given that TypeScript is one of the (currently) embedded choices of this project, the TypeScript configuration file is mandatory. 262 | 263 | The tsconfig.json file contains: 264 | * the configuration of the TypeScript compiler 265 | * TypeScript code style rules 266 | * the list of files/folders to include/exclude 267 | 268 | Here's is the minimal required contents for ModernWebDevBuild. Note that the outDir value is important as it tells the compiler where to write the generated code! Make sure that you also DO have the rootDir property defined and pointing to "./app", otherwise the build will fail (more precisely, `npm run serve` will fail). 269 | 270 | The build depends on the presence of those settings. 271 | 272 | ``` 273 | { 274 | "version": "1.7.3", 275 | "compilerOptions": { 276 | "target": "es5", 277 | "module": "commonjs", 278 | "declaration": false, 279 | "noImplicitAny": true, 280 | "suppressImplicitAnyIndexErrors": true, 281 | "removeComments": false, 282 | "emitDecoratorMetadata": true, 283 | "experimentalDecorators": true, 284 | "noEmitOnError": false, 285 | "preserveConstEnums": true, 286 | "inlineSources": false, 287 | "sourceMap": false, 288 | "outDir": "./.tmp", 289 | "rootDir": "./app", 290 | "moduleResolution": "node", 291 | "listFiles": false 292 | }, 293 | "exclude": [ 294 | "node_modules", 295 | "jspm_packages", 296 | "typings/browser", 297 | "typings/browser.d.ts" 298 | ] 299 | } 300 | ``` 301 | 302 | Here's a more complete example including code style rules: 303 | 304 | ``` 305 | { 306 | "version": "1.7.3", 307 | "compilerOptions": { 308 | "target": "es5", 309 | "module": "commonjs", 310 | "declaration": false, 311 | "noImplicitAny": true, 312 | "suppressImplicitAnyIndexErrors": true, 313 | "removeComments": false, 314 | "emitDecoratorMetadata": true, 315 | "experimentalDecorators": true, 316 | "noEmitOnError": false, 317 | "preserveConstEnums": true, 318 | "inlineSources": false, 319 | "sourceMap": false, 320 | "outDir": "./.tmp", 321 | "rootDir": "./app", 322 | "moduleResolution": "node", 323 | "listFiles": false 324 | }, 325 | "formatCodeOptions": { 326 | "indentSize": 2, 327 | "tabSize": 4, 328 | "newLineCharacter": "\r\n", 329 | "convertTabsToSpaces": false, 330 | "insertSpaceAfterCommaDelimiter": true, 331 | "insertSpaceAfterSemicolonInForStatements": true, 332 | "insertSpaceBeforeAndAfterBinaryOperators": true, 333 | "insertSpaceAfterKeywordsInControlFlowStatements": true, 334 | "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, 335 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, 336 | "placeOpenBraceOnNewLineForFunctions": false, 337 | "placeOpenBraceOnNewLineForControlBlocks": false 338 | }, 339 | "exclude": [ 340 | "node_modules", 341 | "jspm_packages", 342 | "typings/browser", 343 | "typings/browser.d.ts" 344 | ] 345 | } 346 | ``` 347 | 348 | Note the exclusion that we have made, all of which are relevant and there to avoid known issues (e.g., https://github.com/typings/discussions/issues/6 if you are using typings). 349 | 350 | #### tslint.json 351 | tslint.json is the configuration file for [TSLint](https://github.com/palantir/tslint). 352 | 353 | Although it is not strictly mandatory (the build will work without this file), we heavily recommend you to use it as it is very useful to ensure a minimal code quality level and can help you avoid common mistakes and unnecessary complicated code: 354 | 355 | Here's an example: 356 | ``` 357 | { 358 | "rules": { 359 | "class-name": true, 360 | "curly": true, 361 | "eofline": true, 362 | "forin": true, 363 | "indent": [false, "tabs"], 364 | "interface-name": false, 365 | "label-position": true, 366 | "label-undefined": true, 367 | "max-line-length": false, 368 | "no-any": false, 369 | "no-arg": true, 370 | "no-bitwise": true, 371 | "no-console": [false, 372 | "debug", 373 | "info", 374 | "time", 375 | "timeEnd", 376 | "trace" 377 | ], 378 | "no-construct": true, 379 | "no-debugger": true, 380 | "no-duplicate-key": true, 381 | "no-duplicate-variable": true, 382 | "no-empty": true, 383 | "no-eval": true, 384 | "no-imports": true, 385 | "no-string-literal": false, 386 | "no-trailing-comma": true, 387 | "no-unused-variable": false, 388 | "no-unreachable": true, 389 | "no-use-before-declare": null, 390 | "one-line": [true, 391 | "check-open-brace", 392 | "check-catch", 393 | "check-else", 394 | "check-whitespace" 395 | ], 396 | "quotemark": [true, "double"], 397 | "radix": true, 398 | "semicolon": true, 399 | "triple-equals": [true, "allow-null-check"], 400 | "variable-name": false, 401 | "no-trailing-whitespace": true, 402 | "whitespace": [false, 403 | "check-branch", 404 | "check-decl", 405 | "check-operator", 406 | "check-separator", 407 | "check-type", 408 | "check-typecast" 409 | ] 410 | } 411 | } 412 | ``` 413 | 414 | #### karma.conf.js 415 | Karma loads his configuration from karma.conf.js. That file contains everything that Karma needs to know to execute your unit tests. 416 | 417 | Here's an example configuration file that uses Jasmine. Note that the main Karma dependencies including PhantomJS are included in the build. 418 | You only need to add a dependency to jasmine, karma-jasmine and karma-jspm for the following to work. 419 | 420 | If you choose not to use JSPM, then you can use karma-systemjs instead: https://www.npmjs.com/package/karma-systemjs 421 | 422 | Example: 423 | ``` 424 | // Karma configuration 425 | // reference: http://karma-runner.github.io/0.13/config/configuration-file.html 426 | 427 | module.exports = function (config) { 428 | config.set({ 429 | 430 | // base path that will be used to resolve all patterns (eg. files, exclude) 431 | //basePath: ".tmp/", 432 | 433 | plugins: [ 434 | "karma-jspm", 435 | "karma-jasmine", 436 | "karma-phantomjs-launcher", 437 | "karma-chrome-launcher", 438 | "karma-firefox-launcher", 439 | "karma-ie-launcher", 440 | "karma-junit-reporter", 441 | "karma-spec-reporter" 442 | ], 443 | 444 | // frameworks to use 445 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 446 | frameworks: [ 447 | "jspm", 448 | "jasmine" 449 | ], 450 | 451 | // list of files / patterns to load in the browser (loaded before SystemJS) 452 | files: [], 453 | 454 | // list of files to exclude 455 | exclude: [], 456 | 457 | // list of paths mappings 458 | // can be used to map paths served by the Karma web server to /base/ content 459 | // knowing that /base corresponds to the project root folder (i.e., where this config file is located) 460 | proxies: { 461 | "/.tmp": "/base/.tmp" // without this, karma-jspm can't load the files 462 | }, 463 | 464 | // preprocess matching files before serving them to the browser 465 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 466 | preprocessors: {}, 467 | 468 | // test results reporter to use 469 | // possible values: 'dots', 'progress', 'spec', 'junit' 470 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 471 | // https://www.npmjs.com/package/karma-junit-reporter 472 | // https://www.npmjs.com/package/karma-spec-reporter 473 | reporters: ["spec"], 474 | 475 | // web server port 476 | port: 9876, 477 | 478 | // enable / disable colors in the output (reporters and logs) 479 | colors: true, 480 | 481 | // level of logging 482 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 483 | logLevel: config.LOG_INFO, 484 | 485 | // enable / disable watching file and executing tests whenever any file changes 486 | autoWatch: true, 487 | 488 | // start these browsers 489 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 490 | browsers: [ 491 | "PhantomJS" 492 | //"Chrome", 493 | //"Firefox", 494 | //"PhantomJS", 495 | //"IE" 496 | ], 497 | 498 | // Continuous Integration mode 499 | // if true, Karma captures browsers, runs the tests and exits 500 | singleRun: false, 501 | 502 | junitReporter: { 503 | outputFile: "target/reports/tests-unit/unit.xml", 504 | suite: "unit" 505 | }, 506 | 507 | // doc: https://www.npmjs.com/package/karma-jspm 508 | // reference config: https://github.com/gunnarlium/babel-jspm-karma-jasmine-istanbul 509 | jspm: { 510 | // Path to your SystemJS/JSPM configuration file 511 | config: "jspm.conf.js", 512 | 513 | // Where to find jspm packages 514 | //packages: "jspm_packages", 515 | 516 | // One use case for this is to only put test specs in loadFiles, and jspm will only load the src files when and if the test files require them. 517 | loadFiles: [ 518 | // load all tests 519 | ".tmp/*.spec.js", // in case there are tests in the root folder 520 | ".tmp/**/*.spec.js" 521 | ], 522 | 523 | // Make additional files/a file pattern available for jspm to load, but not load it right away. 524 | serveFiles: [ 525 | ".tmp/**/!(*.spec).js" // make sure that all files are available 526 | ], 527 | 528 | // SystemJS configuration specifically for tests, added after your config file. 529 | // Good for adding test libraries and mock modules 530 | paths: {} 531 | } 532 | }); 533 | }; 534 | ``` 535 | 536 | Dev dependencies to add for the above Karma configuration: 537 | ``` 538 | "jasmine": "...", 539 | "karma-jasmine": "...", 540 | "karma-jspm": "..." 541 | ``` 542 | 543 | ### Minimal (application-specific) required file contents 544 | Although we want to limit this list as much as possible, for everything to build successfully, some files need specific contents: 545 | 546 | #### core/app.ts 547 | This should be the top element of your application. This should be loaded by core/boot.ts (see below). 548 | 549 | ``` 550 | "use strict"; 551 | 552 | export class App { 553 | ... 554 | constructor(){ 555 | console.log("Hello world!"); 556 | } 557 | } 558 | ``` 559 | 560 | #### core/boot.ts 561 | The boot.ts file is the entrypoint of your application. Currently, it is mandatory for this file to exist (with that specific name), although that could change or be customizable later. 562 | 563 | The contents are actually not important but here's a starting point: 564 | 565 | ``` 566 | "use strict"; 567 | 568 | import {App} from "core/app"; 569 | // bootstrap your app 570 | ``` 571 | 572 | #### styles/main.scss 573 | The main.scss file is where you should load all the stylesheets scattered around in your application. 574 | 575 | Here's an example of a main.scss: 576 | ``` 577 | // 578 | // Main stylesheet. 579 | // Should import all the other stylesheets 580 | // 581 | 582 | // Variables, functions, mixins and utils 583 | @import "base/variables"; 584 | @import "base/functions"; 585 | @import "base/mixins"; 586 | @import "base/utils"; 587 | 588 | // Base/generic style rules 589 | @import "base/reset"; 590 | @import "base/responsive"; 591 | @import "base/fonts"; 592 | @import "base/typography"; 593 | @import "base/base"; 594 | 595 | // Layout 596 | @import "layout/layout"; 597 | @import "layout/theme"; 598 | @import "layout/print"; 599 | 600 | // Components 601 | @import "../components/posts/posts"; 602 | 603 | // Pages 604 | @import "../pages/home/home"; 605 | ``` 606 | 607 | In the example above, you can see that in the main.scss file, we import many other stylesheets (sass partials). 608 | 609 | Here's another example, this time for vendor.scss: 610 | ``` 611 | // 612 | // Includes/imports all third-party stylesheets used throughout the application. 613 | // Should be loaded before the application stylesheets 614 | // 615 | 616 | // Nicolas Gallagher's Normalize.css 617 | @import '../../jspm_packages/github/necolas/normalize.css@3.0.3/normalize.css'; // the path refers to the file at BUILD time 618 | ``` 619 | 620 | As you can see above, a third-party stylesheet is imported. 621 | 622 | 623 | ### index.html 624 | The index.html file is the entrypoint of your application. It is not mandatory per se for ModernWebDevBuild, but when you run `npm run serve`, it'll be opened. Also, the `html` build task will try and replace/inject content in it. 625 | 626 | Here's the minimal required contents for index.html (required for production builds with minification and bundling): 627 | 628 | ``` 629 | 630 | 631 | 632 | 633 | ... 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 651 | 652 | 653 | 654 | ``` 655 | 656 | In the above, the most important parts are: 657 | * for production, the contents of ` ... ` will be replaced by the vendor bundle created by the build 658 | * for production, the contents of ` ... ` will be replaced by the application's CSS bundle created by the build 659 | * for production, the contents of ` ... ` will be replaced by the application's JS bundle created by the build 660 | 661 | Also, note that during development, SystemJS is loaded (system.src.js), the JSPM configuration is loaded (jspm.conf.js) and SystemJS is used to load the entrypoint of the application (core/core.bootstrap). 662 | 663 | ## Commands 664 | Once you have added ModernWebDevBuild to your project, you can list all the available commands using `gulp help`. 665 | The command will give you a description of each task. The most important to start discovering are: 666 | * `gulp serve`: start a Web server with live reload, watching files, transpiling, generating sourcemaps, etc 667 | * `gulp serve-dist`: same with the production version 668 | * `gulp clean` 669 | * `gulp ts-lint`: check TypeScript code quality/style 670 | * `gulp check-js-quality`: check JavaScript code quality 671 | * `gulp check-js-style`: check JavaScript code style 672 | * `gulp prepare-test-unit`: clean, compile and check quality/style 673 | * `gulp test-unit`: run unit tests using Karma (prereq: `gulp prepare-test-unit` 674 | 675 | You can run the `gulp -T` command get an visual idea of the links between the different tasks. 676 | 677 | ## Scripts 678 | To make your life easier, you can add the following scripts to your package.json file. Note that if you have used the generator to create your project, you normally have these already: 679 | 680 | ``` 681 | 682 | ``` 683 | 684 | ## Options 685 | The build can be customized by passing options. 686 | Defining options is done as in the following example gulpfile.babel.js: 687 | 688 | ``` 689 | "use strict"; 690 | 691 | import gulp from "gulp"; 692 | 693 | import modernWebDevBuild from "modern-web-dev-build"; 694 | 695 | let options = {}; 696 | 697 | options.distEntryPoint = "core/core.bootstrap"; 698 | ``` 699 | 700 | Available options: 701 | * distEntryPoint 702 | * must be a relative path from .tmp/ to the file to use as entry point for creating the production JS bundle. The extension does not need to be specified (SystemJS is used to load the file) 703 | * by default, the following file is used: `core/boot.js` 704 | * minifyProductionJSBundle (default: true) 705 | * by default, the production JS bundle is minified 706 | * you can disable it by setting this option to false 707 | * mangleProductionJSBundle (default: true) 708 | * by default, the production JS bundle is mangled 709 | * you can disable it by setting this option to false 710 | * useJSPM (default: true) 711 | * by default, the production JS bundle is created using the JSPM API, which requires jspm configuration in your package.json 712 | * you can disable JSPM by setting this option to false 713 | * if you disable the usage of JSPM, then the SystemJS builder API will be used to create the production JS bundle: https://www.npmjs.com/package/systemjs-builder 714 | * systemjsConfigurationFile (default: jspm.conf.js) 715 | * by default, if you disable JSPM usage by the build, it will expect to find 'jspm.conf.js' as your SystemJS configuration file 716 | * you can define this option to customize the file name 717 | * minifyProductionHTML (default: true) 718 | * by default, the production HTML is minified 719 | * you can disable it by setting this option to false 720 | * browserSync 721 | * Pass in browserSync options. See http://www.browsersync.io/docs/options/ 722 | * this should be defined like this: "browserSync": { // BrowserSync options } 723 | 724 | ## FAQ 725 | 726 | ### How can I inline some script in the production version of some HTML page? 727 | * add `inline ` right above the script tag that you want to have inlined; this tells the gulp-inline-source plugin where to find the resource that should be inlined 728 | * add the `inline` attribute to the script tag itself: `````` 729 | 730 | Example: 731 | ``` 732 | 733 | 734 | ``` 735 | 736 | Note that the path specified in the ` directory path used for resolving inlineable paths 61 | })) 62 | 63 | // Minify HTML 64 | .pipe(iff(minifyProductionHTML && config.files.any + config.extensions.html, minifyHtml({ 65 | quotes: true // do not remove quotes (Angular 2 does not like that) 66 | }))) 67 | 68 | // Output files 69 | .pipe(gulp.dest(config.html.dest)) 70 | 71 | // Task result 72 | .pipe(size({ 73 | title: "html" 74 | })); 75 | }); 76 | } 77 | } 78 | 79 | module.exports = new HtmlTaskLoader(); 80 | -------------------------------------------------------------------------------- /src/gulp/tasks/images.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | import utils from "../utils"; 6 | 7 | import eventStream from "event-stream"; 8 | import cache from "gulp-cache"; 9 | import imageMin from "gulp-imagemin"; 10 | import size from "gulp-size"; 11 | //import debug from "gulp-debug"; 12 | 13 | class ImagesTaskLoader extends AbstractTaskLoader { 14 | registerTask(gulp){ 15 | super.registerTask(gulp); 16 | 17 | gulp.task("images", "Optimize images", () =>{ 18 | return gulp.plumbedSrc( 19 | config.images.src 20 | ) 21 | // Filter out the empty directories 22 | .pipe(utils.filterEmptyDirectories(eventStream)) 23 | 24 | // Display the files in the stream 25 | //.pipe(debug({title: "Stream contents:", minimal: true})) 26 | 27 | // Minify and cache 28 | .pipe(cache(imageMin({ 29 | progressive: true, 30 | interlaced: true 31 | }))) 32 | 33 | // Output files 34 | .pipe(gulp.dest(config.images.dest)) 35 | 36 | // Task result 37 | .pipe(size({ 38 | title: "images" 39 | })); 40 | }); 41 | } 42 | } 43 | 44 | module.exports = new ImagesTaskLoader(); 45 | -------------------------------------------------------------------------------- /src/gulp/tasks/scripts-javascript-dist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | //import size from "gulp-size"; 8 | 9 | import path from "path"; 10 | import gutil from "gulp-util"; 11 | 12 | class ScriptsJavaScriptDistTaskLoader extends AbstractTaskLoader { 13 | registerTask(gulp){ 14 | super.registerTask(gulp); 15 | 16 | gulp.task("scripts-javascript-dist", "Package all JavaScript code for production", () =>{ 17 | // Assuming that all TS and ES2015 code has already been converted to ES5 using the System module type 18 | // Assuming that there is a single entrypoint for the application 19 | // We only need to create the final bundle 20 | 21 | // Determine if the bundle should be minified or not 22 | let minifyProductionJSBundle = true; 23 | 24 | if(typeof gulp.options.minifyProductionJSBundle !== "undefined"){ 25 | minifyProductionJSBundle = gulp.options.minifyProductionJSBundle; 26 | 27 | if(minifyProductionJSBundle === false){ 28 | gutil.log("The production JS bundle will NOT be minified!"); 29 | } 30 | } 31 | 32 | // Determine if the bundle should be mangled or not 33 | let mangleProductionJSBundle = true; 34 | 35 | if(typeof gulp.options.mangleProductionJSBundle !== "undefined"){ 36 | mangleProductionJSBundle = gulp.options.mangleProductionJSBundle; 37 | 38 | if(mangleProductionJSBundle === false){ 39 | gutil.log("The production JS bundle will NOT be mangled!"); 40 | } 41 | } 42 | 43 | // Determine if JSPM should be used or not 44 | let useJSPM = true; 45 | 46 | if(typeof gulp.options.useJSPM !== "undefined"){ 47 | useJSPM = gulp.options.useJSPM; 48 | 49 | if(useJSPM === false){ 50 | gutil.log("The production JS bundle will be built using SystemJS-builder rather than with JSPM!"); 51 | } 52 | } 53 | 54 | // Determine the entry point for the bundle creation (i.e., where to start from) 55 | let distEntryPoint = config.javascript.srcDist; 56 | 57 | if(typeof gulp.options.distEntryPoint !== "undefined"){ 58 | distEntryPoint = path.join(config.folders.temp, gulp.options.distEntryPoint); 59 | gutil.log("The production JS bundle entry point has been customized: ", distEntryPoint); 60 | } 61 | 62 | // Determine the SystemJS configuration file name (only used if JSPM is disabled) 63 | let systemjsConfigurationFile = config.files.systemjsConfigDefault; 64 | 65 | if(typeof gulp.options.systemjsConfigurationFile !== "undefined"){ 66 | systemjsConfigurationFile = gulp.options.systemjsConfigurationFile; 67 | gutil.log("The SystemJS configuration file has been customized: ", systemjsConfigurationFile); 68 | } 69 | 70 | // Create the bundle 71 | 72 | let bundleConfiguration = { 73 | sourceMaps: false, 74 | minify: minifyProductionJSBundle, 75 | mangle: mangleProductionJSBundle, 76 | //format: 'cjs', // to output the SFX bundle in the AMD module format 77 | // runtime: false, // to exclude the Traceur or Babel runtime 78 | globalDefs: { 79 | DEBUG: false 80 | } 81 | }; 82 | 83 | // default: using JSPM 84 | if(useJSPM === true){ 85 | // Reference: https://github.com/systemjs/builder/issues/203 86 | let jspm = require("jspm"); 87 | 88 | jspm.setPackagePath("."); 89 | 90 | return jspm.bundleSFX( 91 | distEntryPoint, // where to start creating the bundle from 92 | config.javascript.destDist, 93 | bundleConfiguration 94 | ); 95 | } else{ 96 | let Builder = require("systemjs-builder"); 97 | let systemjsBuilder = new Builder(".", systemjsConfigurationFile); 98 | 99 | return systemjsBuilder.buildStatic( 100 | distEntryPoint, // where to start creating the bundle from 101 | config.javascript.destDist, 102 | bundleConfiguration 103 | ); 104 | } 105 | }); 106 | } 107 | } 108 | 109 | module.exports = new ScriptsJavaScriptDistTaskLoader(); 110 | -------------------------------------------------------------------------------- /src/gulp/tasks/scripts-javascript.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import changed from "gulp-changed"; 8 | import sourcemaps from "gulp-sourcemaps"; 9 | import babel from "gulp-babel"; 10 | import size from "gulp-size"; 11 | //import debug from "gulp-debug"; 12 | 13 | class ScriptsJavaScriptTaskLoader extends AbstractTaskLoader { 14 | registerTask(gulp){ 15 | super.registerTask(gulp); 16 | 17 | gulp.task("scripts-javascript", "Transpile JavaScript (ES2015 to ES5 using Babel) and generate sourcemaps", () =>{ 18 | return gulp.plumbedSrc(// handle errors nicely (i.e., without breaking watch) 19 | config.javascript.src 20 | ) 21 | 22 | // Display the files in the stream 23 | //.pipe(debug({title: "Stream contents:", minimal: true})) 24 | 25 | // speed things up by ignoring unchanged resources 26 | .pipe(changed(config.javascript.dest)) 27 | 28 | // Initialize sourcemap generation 29 | .pipe(sourcemaps.init({ 30 | loadMaps: true 31 | //debug: true 32 | })) 33 | 34 | // Transpile ES2015 to ES5 35 | // options: see .babelrc file 36 | .pipe(babel()) 37 | 38 | // Write sourcemaps: https://www.npmjs.com/package/gulp-sourcemaps 39 | //.pipe($.sourcemaps.write()) // use "." to write the sourcemap to a separate file in the same dir 40 | .pipe(sourcemaps.write(".", { // use "." to write the sourcemap to a separate file in the same dir 41 | includeContent: false, // alternative: include the contents and remove sourceRoot. Avoids issues but prevents from editing the sources directly in the browser 42 | sourceRoot: "/" // use an absolute path because we have scripts in different subpaths 43 | })) 44 | 45 | // Copy files 46 | .pipe(gulp.dest(config.javascript.dest)) 47 | 48 | // Display the files in the stream 49 | //.pipe(debug({title: "Stream contents:", minimal: true})) 50 | 51 | // Task result 52 | .pipe(size({ 53 | title: "scripts-javascript" 54 | })); 55 | }); 56 | } 57 | } 58 | 59 | module.exports = new ScriptsJavaScriptTaskLoader(); 60 | -------------------------------------------------------------------------------- /src/gulp/tasks/scripts-typescript.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | //import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import sourcemaps from "gulp-sourcemaps"; 8 | import ts from "gulp-typescript"; 9 | import size from "gulp-size"; 10 | //import debug from "gulp-debug"; 11 | 12 | class ScriptsTypeScriptTaskLoader extends AbstractTaskLoader { 13 | registerTask(gulp){ 14 | super.registerTask(gulp); 15 | 16 | gulp.task("scripts-typescript", "Transpile TypeScript to ES, include references to library and app .d.ts files and generate sourcemaps", () =>{ 17 | // references: 18 | // https://www.npmjs.com/package/gulp-typescript 19 | const tsProject = ts.createProject("tsconfig.json", { 20 | typescript: require("typescript") // override the typescript version by that defined in package.json 21 | 22 | // configuration defined in tsconfig.json 23 | // other overrides here if needed 24 | // http://json.schemastore.org/tsconfig 25 | // https://github.com/Microsoft/TypeScript/wiki/Compiler%20Options 26 | }); 27 | //console.log(tsProject.config.compilerOptions); 28 | const tsConfigOutDir = tsProject.config.compilerOptions.outDir; 29 | 30 | const tsResult = tsProject.src() 31 | // Display the files in the stream 32 | //.pipe(debug({title: "Stream contents:", minimal: true})) 33 | .pipe(sourcemaps.init()) 34 | .pipe(ts( 35 | tsProject 36 | )); 37 | 38 | // Output type definition files 39 | tsResult.dts.pipe(gulp.dest(tsConfigOutDir)); 40 | 41 | // Output js files 42 | return tsResult.js 43 | 44 | // Display the files in the stream 45 | //.pipe(debug({title: "Stream contents:", minimal: true})) 46 | 47 | .pipe(sourcemaps.write(".", { // use "." to write the sourcemap to a separate file in the same dir 48 | includeContent: false, // alternative: include the contents and remove sourceRoot. Avoids issues but prevents from editing the sources directly in the browser 49 | sourceRoot: "/" // use an absolute path because we have scripts in different subpaths 50 | })) 51 | 52 | // Output files 53 | .pipe(gulp.dest(tsConfigOutDir)) 54 | 55 | // Task result 56 | .pipe(size({ 57 | title: "scripts-typescript" 58 | })); 59 | }); 60 | } 61 | } 62 | 63 | module.exports = new ScriptsTypeScriptTaskLoader(); 64 | 65 | -------------------------------------------------------------------------------- /src/gulp/tasks/serve-dist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | import wait from "gulp-wait"; 7 | 8 | const browserSync = require("browser-sync").create(config.webServerNames.dist); 9 | 10 | import historyApiFallback from "connect-history-api-fallback"; // fix for SPAs w/ BrowserSync & others: https://github.com/BrowserSync/browser-sync/issues/204 11 | 12 | class ServeDistTaskLoader extends AbstractTaskLoader { 13 | registerTask(gulp){ 14 | super.registerTask(gulp); 15 | 16 | let runSequence = require("run-sequence"); 17 | 18 | runSequence = runSequence.use(gulp); // needed to bind to the correct gulp object (alternative is to pass gulp to runSequence as first argument) 19 | 20 | const startBrowserSync = () =>{ 21 | browserSync.init({ 22 | notify: false, 23 | //port: 8000, 24 | 25 | // Customize the BrowserSync console logging prefix 26 | logPrefix: "MWD", // Modern Web Dev 27 | 28 | // Run w/ https by uncommenting "https: true" 29 | // Note: this uses an unsigned certificate which on first access 30 | // will present a certificate warning in the browser. 31 | // https: true, 32 | server: { 33 | baseDir: config.webServerFolders.dist, 34 | 35 | // fix for SPAs w/ BrowserSync & others: https://github.com/BrowserSync/browser-sync/issues/204 36 | // reference: https://github.com/BrowserSync/browser-sync/issues/204 37 | // todo extract common middleware config 38 | middleware: [ 39 | historyApiFallback(), // not necessary if the app uses hash based routing 40 | function(req, res, next){ 41 | res.setHeader("Access-Control-Allow-Origin", "*"); // add CORS to the response headers (for resources served by BrowserSync) 42 | next(); 43 | } 44 | ] 45 | }, 46 | reloadDelay: 1000, 47 | reloadDebounce: 1000 48 | }); 49 | }; 50 | 51 | gulp.task("wait-a-bit", "Wait a second...", () =>{ 52 | return gulp.src("./package.json"). 53 | pipe(wait(1500)); 54 | }); 55 | 56 | gulp.task("serve-dist", "Build and serve the production version (i.e., dist folder contents", () =>{ 57 | return runSequence([ "default" ], [ "wait-a-bit" ], startBrowserSync); // here we need to ensure that all the other tasks are done before we start BrowserSync 58 | }); 59 | } 60 | } 61 | 62 | module.exports = new ServeDistTaskLoader(); 63 | -------------------------------------------------------------------------------- /src/gulp/tasks/serve.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | import utils from "../utils"; 6 | 7 | let browserSync = require("browser-sync").create(config.webServerNames.dev); 8 | 9 | import historyApiFallback from "connect-history-api-fallback"; // fix for SPAs w/ BrowserSync & others: https://github.com/BrowserSync/browser-sync/issues/204 10 | //import debug from "gulp-debug"; 11 | 12 | class ServeTaskLoader extends AbstractTaskLoader { 13 | registerTask(gulp){ 14 | super.registerTask(gulp); 15 | 16 | let runSequence = require("run-sequence"); 17 | 18 | runSequence = runSequence.use(gulp); // needed to bind to the correct gulp object (alternative is to pass gulp to runSequence as first argument) 19 | 20 | // TypeScript 21 | gulp.task("serve-scripts-typescript", "Transpile TypeScript to ES5 and reload the browser (this task should only be called during serve)", [ "prepare-serve-scripts-typescript" ], () =>{ 22 | return browserSync.reload(); 23 | }); // reload BrowserSync once everything is ready 24 | 25 | gulp.task("prepare-serve-scripts-typescript", "Transpile TypeScript to ES5 and generate sourcemaps", [ 26 | "ts-lint" 27 | ], (callback) =>{ 28 | return runSequence( 29 | "scripts-typescript", 30 | callback); 31 | }); 32 | 33 | // JavaScript 34 | gulp.task("serve-scripts-javascript", "Transpile JavaScript to ES5 and reload the browser (this task should only be called during serve)", [ "prepare-serve-scripts-javascript" ], () =>{ 35 | return browserSync.reload(); 36 | }); // reload BrowserSync once everything is ready 37 | gulp.task("prepare-serve-scripts-javascript", "Transpile JavaScript to ES5 and generate sourcemaps", [ 38 | "check-js-style", 39 | "check-js-quality" 40 | ], (callback) =>{ 41 | return runSequence( 42 | "scripts-javascript", 43 | callback); 44 | }); 45 | 46 | let options = { // http://www.browsersync.io/docs/options/ 47 | notify: false, 48 | //port: 8000, 49 | 50 | // Customize the BrowserSync console logging prefix 51 | logPrefix: "MWD", // Modern Web Dev 52 | 53 | // Run w/ https by uncommenting "https: true" 54 | // Note: this uses an unsigned certificate which on first access 55 | // will present a certificate warning in the browser. 56 | // https: true, 57 | ghostMode: { // replicate actions in all clients 58 | clicks: false, 59 | forms: false, 60 | scroll: false 61 | }, 62 | server: { 63 | baseDir: config.webServerFolders.dev, 64 | //routes: alternative way to map content that is above the base dir 65 | // fix for SPAs w/ BrowserSync & others: https://github.com/BrowserSync/browser-sync/issues/204 66 | // reference: https://github.com/BrowserSync/browser-sync/issues/204 67 | middleware: [ 68 | historyApiFallback(), // not necessary if the app uses hash based routing 69 | function(req, res, next){ 70 | res.setHeader("Access-Control-Allow-Origin", "*"); // add CORS to the response headers (for resources served by BrowserSync) 71 | next(); 72 | } 73 | ] 74 | }//, 75 | //reloadDebounce: 500 // restrict the frequency in which browser reload events can be emitted to connected clients 76 | }; 77 | 78 | let startBrowserSync = () =>{ 79 | browserSync.init(utils.mergeOptions(options, gulp.options.browserSync)); 80 | 81 | gulp.watch(config.html.src).on("change", browserSync.reload); // force a reload when html changes 82 | gulp.watch(config.styles.src, [ "styles" ]); // stylesheet changes will be streamed if possible or will force a reload 83 | gulp.watch(config.typescript.srcAppOnly, [ 84 | "serve-scripts-typescript" 85 | ]); // TypeScript changes will force a reload 86 | gulp.watch(config.javascript.src, [ 87 | "serve-scripts-javascript" 88 | ]); // JavaScript changes will force a reload 89 | gulp.watch(config.images.src).on("change", browserSync.reload); // force a reload when images change 90 | }; 91 | 92 | gulp.task("serve", "Watch files for changes and rebuild/reload automagically", () =>{ 93 | runSequence("prepare-serve", startBrowserSync); // here we need to ensure that all the other tasks are done before we start BrowserSync 94 | }); 95 | 96 | gulp.task("prepare-serve", "Do all the necessary preparatory work for the serve task", () =>{ 97 | return runSequence([ 98 | "clean", 99 | "ts-lint", 100 | "check-js-style", 101 | "check-js-quality" 102 | ], [ 103 | "scripts-typescript", 104 | "scripts-javascript", 105 | "styles", 106 | "validate-package-json" 107 | ]); 108 | }); 109 | } 110 | } 111 | 112 | module.exports = new ServeTaskLoader(); 113 | -------------------------------------------------------------------------------- /src/gulp/tasks/styles-dist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import sass from "gulp-sass"; 8 | import cssimport from "gulp-cssimport"; 9 | import concat from "gulp-concat"; 10 | import csso from "gulp-csso"; 11 | import minifyCss from "gulp-clean-css"; 12 | import size from "gulp-size"; 13 | 14 | class StylesDistTaskLoader extends AbstractTaskLoader { 15 | registerTask(gulp){ 16 | super.registerTask(gulp); 17 | 18 | gulp.task("styles-dist", "Optimize and minimize stylesheets for production", () =>{ 19 | return gulp.plumbedSrc(// handle errors nicely (i.e., without breaking watch) 20 | config.styles.srcWithoutVendor 21 | ) 22 | 23 | // Display the files in the stream 24 | //.pipe(debug({title: "Stream contents:", minimal: true})) 25 | 26 | // Process Sass files 27 | .pipe(sass({ 28 | style: "compressed" 29 | //errLogToConsole: true 30 | })) 31 | 32 | // Replace CSS imports by actual contents 33 | .pipe(cssimport()) 34 | 35 | // Remove any unused CSS 36 | // Note that it breaks the sourcemaps (but we shouldn't care for dist since we don't need sourcemaps there) 37 | // Note that it also causes weird output during build in combination w/ Angular 38 | //.pipe($.uncss({ 39 | // html: [ 40 | // config.html.src 41 | // ], 42 | // // CSS Selectors for UnCSS to ignore 43 | // ignore: [ 44 | // ] 45 | //})) 46 | 47 | // Regroup all files together 48 | .pipe(concat(config.styles.finalCssBundleFilename)) 49 | 50 | // Optimize and minimize 51 | .pipe(csso()) // https://www.npmjs.com/package/gulp-csso 52 | .pipe(minifyCss( 53 | config.minifyCss 54 | )) 55 | 56 | // Output file 57 | .pipe(gulp.dest(config.styles.destDist)) 58 | 59 | // Task result 60 | .pipe(size({ 61 | title: "styles-dist" 62 | })); 63 | }); 64 | } 65 | } 66 | 67 | module.exports = new StylesDistTaskLoader(); 68 | -------------------------------------------------------------------------------- /src/gulp/tasks/styles-vendor-dist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import sass from "gulp-sass"; 8 | import cssimport from "gulp-cssimport"; 9 | import concat from "gulp-concat"; 10 | import csso from "gulp-csso"; 11 | import minifyCss from "gulp-clean-css"; 12 | import size from "gulp-size"; 13 | //import debug from "gulp-debug"; 14 | 15 | class StylesVendorDistTaskLoader extends AbstractTaskLoader { 16 | registerTask(gulp){ 17 | super.registerTask(gulp); 18 | 19 | gulp.task("styles-vendor-dist", "Optimize and minimize vendor stylesheets for production", () =>{ 20 | return gulp.plumbedSrc(// handle errors nicely (i.e., without breaking watch)([ 21 | config.styles.srcVendorOnly 22 | ) 23 | 24 | // Display the files in the stream 25 | //.pipe(debug({title: "Stream contents:", minimal: true})) 26 | 27 | // Process Sass files 28 | .pipe(sass({ 29 | style: "compressed" 30 | //errLogToConsole: true 31 | })) 32 | 33 | // Replace CSS imports by actual contents 34 | .pipe(cssimport()) 35 | 36 | // Remove any unused CSS 37 | // Note that it breaks the sourcemaps (but we shouldn't care for dist since we don't need sourcemaps there) 38 | // Note that it also causes weird output during build in combination w/ Angular 39 | //.pipe($.uncss({ 40 | // html: [ 41 | // config.html.src 42 | // ], 43 | // // CSS Selectors for UnCSS to ignore 44 | // ignore: [ 45 | // ] 46 | //})) 47 | 48 | // Regroup all files together 49 | .pipe(concat(config.styles.finalVendorCssBundleFilename)) 50 | 51 | // Optimize and minimize 52 | .pipe(csso()) // https://www.npmjs.com/package/gulp-csso 53 | .pipe(minifyCss( 54 | config.minifyCss 55 | )) 56 | 57 | // Output file 58 | .pipe(gulp.dest(config.styles.destDist)) 59 | 60 | // Task result 61 | .pipe(size({ 62 | title: "styles-vendor-dist" 63 | })); 64 | }); 65 | } 66 | } 67 | 68 | module.exports = new StylesVendorDistTaskLoader(); 69 | -------------------------------------------------------------------------------- /src/gulp/tasks/styles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import sass from "gulp-sass"; 8 | import sourcemaps from "gulp-sourcemaps"; 9 | import autoprefixer from "gulp-autoprefixer"; 10 | import iff from "gulp-if"; 11 | import size from "gulp-size"; 12 | //import debug from "gulp-debug"; 13 | 14 | let browserSync = require("browser-sync").get(config.webServerNames.dev); 15 | 16 | class StylesTaskLoader extends AbstractTaskLoader { 17 | registerTask(gulp){ 18 | super.registerTask(gulp); 19 | 20 | gulp.task("styles", "Compile, add vendor prefixes and generate sourcemaps", () =>{ 21 | return gulp.plumbedSrc(// handle errors nicely (i.e., without breaking watch) 22 | config.styles.src 23 | ) 24 | 25 | // Display the files in the stream 26 | //.pipe(debug({title: "Stream contents:", minimal: true})) 27 | 28 | // Initialize sourcemap generation 29 | .pipe(sourcemaps.init({ 30 | //debug: true 31 | })) 32 | 33 | // Process the sass files 34 | .pipe(sass({ 35 | style: "compressed" 36 | //errLogToConsole: true 37 | })) 38 | 39 | // Write sourcemaps: https://www.npmjs.com/package/gulp-sourcemaps 40 | .pipe(sourcemaps.write(".", { // use "." to write the sourcemap to a separate file in the same dir 41 | includeContent: false, // alternative: include the contents and remove sourceRoot. Avoids issues but prevents from editing the sources directly in the browser 42 | sourceRoot: "/" // use an absolute path because we have scripts in different subpaths 43 | })) 44 | 45 | // Include vendor prefixes 46 | // The if clause prevents autoprefixer from messing up the sourcemaps (necessary if the maps are put in separate files) 47 | // reference: https://github.com/sindresorhus/gulp-autoprefixer/issues/8#issuecomment-93817177 48 | .pipe(iff([ config.extensions.css, "!*.map" ], autoprefixer({ 49 | browsers: config.autoprefixerBrowsers // alternative: $.autoprefixer("last 2 version") 50 | }))) 51 | 52 | // Output files 53 | .pipe(gulp.dest(config.styles.dest)) 54 | 55 | // Reload Browser if needed 56 | // Stream if possible 57 | .pipe(iff(browserSync.active, browserSync.stream({ 58 | once: true, 59 | stream: true 60 | }))) 61 | 62 | // Task result 63 | .pipe(size({ 64 | title: "styles" 65 | })); 66 | }); 67 | } 68 | } 69 | 70 | module.exports = new StylesTaskLoader(); 71 | 72 | -------------------------------------------------------------------------------- /src/gulp/tasks/test-unit.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | //import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | //import debug from "gulp-debug"; 8 | let KarmaServer = require("karma").Server; // TODO replace by import {Server as KarmaServer} from "karma"; 9 | let path = require("path"); 10 | 11 | class TestUnitTaskLoader extends AbstractTaskLoader { 12 | registerTask(gulp){ 13 | super.registerTask(gulp); 14 | 15 | let runSequence = require("run-sequence"); 16 | 17 | runSequence = runSequence.use(gulp); // needed to bind to the correct gulp object (alternative is to pass gulp to runSequence as first argument) 18 | 19 | let karmaConfigFilePath = path.resolve("karma.conf.js"); 20 | 21 | gulp.task("test-unit", "Execute all unit tests", (callback) =>{ 22 | return new KarmaServer({ 23 | configFile: karmaConfigFilePath, // necessary otherwise the file is not resolved correctly by the karma runtime 24 | singleRun: true 25 | }, callback).start(); 26 | }); 27 | 28 | gulp.task("test-unit-dev", "Execute all unit tests continuously (watches files)", (callback) =>{ 29 | return new KarmaServer({ 30 | configFile: karmaConfigFilePath, // necessary otherwise the file is not resolved correctly by the karma runtime 31 | singleRun: false 32 | }, callback).start(); 33 | }); 34 | 35 | gulp.task("prepare-test-unit", "Do all the necessary preparatory work for the test-unit task", () =>{ 36 | return runSequence([ 37 | "clean", 38 | "ts-lint", 39 | "check-js-style", 40 | "check-js-quality" 41 | ], [ 42 | "scripts-typescript", 43 | "scripts-javascript" 44 | ]); 45 | }); 46 | } 47 | } 48 | 49 | module.exports = new TestUnitTaskLoader(); 50 | -------------------------------------------------------------------------------- /src/gulp/tasks/ts-lint.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import tslint from "gulp-tslint"; 8 | import iff from "gulp-if"; 9 | import size from "gulp-size"; 10 | //import debug from "gulp-debug"; 11 | 12 | let browserSync = require("browser-sync").get(config.webServerNames.dev); 13 | 14 | class TsLintTaskLoader extends AbstractTaskLoader { 15 | registerTask(gulp){ 16 | super.registerTask(gulp); 17 | 18 | gulp.task("ts-lint", "Lint TypeScript code", () =>{ 19 | return gulp.plumbedSrc(// handle errors nicely (i.e., without breaking watch) 20 | config.typescript.srcAppOnly // only the application's code needs to be checked 21 | ) 22 | 23 | // Display the files in the stream 24 | //.pipe(debug({title: "Stream contents:", minimal: true})) 25 | 26 | // Check the code quality 27 | .pipe(tslint()) 28 | 29 | // Fail the build only if BrowserSync is not active 30 | .pipe(iff(!browserSync.active, tslint.report("prose"))) 31 | .pipe(iff(browserSync.active, tslint.report("prose", { 32 | emitError: false 33 | }))) 34 | 35 | // Task result 36 | .pipe(size({ 37 | title: "ts-lint" 38 | })); 39 | }); 40 | } 41 | } 42 | 43 | module.exports = new TsLintTaskLoader(); 44 | -------------------------------------------------------------------------------- /src/gulp/tasks/validate-package-json.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | import packageJsonValidator from "gulp-nice-package"; 8 | 9 | class PackageJSONTaskLoader extends AbstractTaskLoader { 10 | registerTask(gulp){ 11 | super.registerTask(gulp); 12 | 13 | gulp.task("validate-package-json", "Validate the package.json file", () =>{ 14 | return gulp.plumbedSrc(config.files.packageJSON) 15 | .pipe(packageJsonValidator()); 16 | }); 17 | } 18 | } 19 | 20 | module.exports = new PackageJSONTaskLoader(); 21 | -------------------------------------------------------------------------------- /src/gulp/templates/taskLoaderTemplate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import AbstractTaskLoader from "../abstractTaskLoader"; 4 | //import config from "../config"; 5 | //import utils from "../utils"; 6 | 7 | class TemplateTaskLoader extends AbstractTaskLoader { 8 | registerTask(gulp){ 9 | super.registerTask(gulp); 10 | 11 | console.log("Hello world!"); 12 | } 13 | } 14 | 15 | module.exports = new TemplateTaskLoader(); 16 | -------------------------------------------------------------------------------- /src/gulp/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import notify from "gulp-notify"; 4 | import gutil from "gulp-util"; 5 | import plumber from "gulp-plumber"; 6 | 7 | /** 8 | * Whether we should make the house explode whenever errors occur (e.g., stop gulp serve) 9 | * @type {boolean} 10 | */ 11 | let exitOnError = false; 12 | 13 | /** 14 | * Display errors nicely and avoid having errors breaking tasks/watch 15 | * Reference: https://github.com/mikaelbr/gulp-notify/issues/81 16 | * @param error the error to report 17 | */ 18 | let reportError = function(error){ // note that we do not use an arrow function to get the expected value of this when this function is called (i.e., context of the caller) 19 | let lineNumber = error.lineNumber ? `LINE ${error.lineNumber} -- ` : ""; 20 | let report = ""; 21 | let chalk = gutil.colors.white.bgRed; 22 | 23 | notify({ 24 | title: `Task Failed [${error.plugin}]`, 25 | message: `${lineNumber} See console.`, 26 | sound: true 27 | 28 | // the version below probably works on OSX 29 | //sound: 'Sosumi' // See: https://github.com/mikaelbr/node-notifier#all-notification-options-with-their-defaults 30 | }).write(error); 31 | 32 | // Inspect the error object 33 | //gutil.log(error); 34 | 35 | // Easy error reporting 36 | //console.log(error.toString()); 37 | 38 | // Pretty error reporting 39 | report += chalk("TASK:") + " [" + error.plugin + "]\n"; 40 | report += chalk("ISSUE:") + " " + error.message + "\n"; 41 | 42 | if(error.lineNumber){ 43 | report += chalk("LINE:") + " " + error.lineNumber + "\n"; 44 | } 45 | 46 | if(error.fileName){ 47 | report += chalk("FILE:") + " " + error.fileName + "\n"; 48 | } 49 | 50 | console.error(report); 51 | 52 | if(exitOnError){ 53 | process.exit(1); 54 | } else{ 55 | // Prevent the 'watch' task from stopping 56 | this.emit("end"); 57 | } 58 | }; 59 | 60 | /** 61 | * Exclude files from globs 62 | * @param providedPath the path that should be excluded 63 | * @returns {string} the exclusion string 64 | */ 65 | let exclude = providedPath => "!" + providedPath; 66 | 67 | /** 68 | * Filter out empty directories 69 | * reference: http://stackoverflow.com/questions/23719731/gulp-copying-empty-directories 70 | * @param es the list to filter 71 | * @returns {*} the filtered list 72 | */ 73 | let filterEmptyDirectories = es =>{ 74 | return es.map((file, cb) =>{ 75 | if(file.stat.isFile()){ 76 | return cb(null, file); 77 | } else{ 78 | return cb(); 79 | } 80 | }); 81 | }; 82 | 83 | /** 84 | * Validate that the passed argument is not null or undefined. 85 | * If validation fails, an error is thrown. 86 | * @param arg the argument to check 87 | * @param errorMessage the error message to use if validation fails 88 | * @throws Error if validation fails 89 | */ 90 | let validateArgument = (arg, errorMessage) =>{ 91 | errorMessage = errorMessage || "the provided argument cannot be null or undefined!"; 92 | 93 | if(arg === null || arg === undefined){ 94 | throw new TypeError(errorMessage); 95 | } 96 | }; 97 | 98 | /** 99 | * Validate that the passed argument is a valid gulp object 100 | * @param obj the object to validate 101 | * @throws Error if validation fails 102 | */ 103 | let validateGulpObject = obj =>{ 104 | validateArgument(obj); 105 | validateArgument(obj.tasks, "the provided argument must be a gulp instance!"); 106 | }; 107 | 108 | /** 109 | * Validate that the passed argument is a valid gulp object and is configured as expected. 110 | * It should have the help task defined 111 | * @param obj the object to validate 112 | * @throws Error if validation fails 113 | */ 114 | let validateGulpObjectIsConfigured = obj =>{ 115 | const notCorrectlyConfiguredErrorMessage = "the provided argument is a valid gulp object but it isn't configured properly. You need to invoke the configureGulpObject utility function before passing it to the tasks!"; 116 | 117 | validateGulpObject(obj); 118 | validateArgument(obj.tasks.help, notCorrectlyConfiguredErrorMessage); 119 | validateArgument(obj.plumbedSrc, notCorrectlyConfiguredErrorMessage); 120 | }; 121 | 122 | /** 123 | * Configure the passed in gulp object so that it is customized as we need: 124 | * - gulp help loaded and enabled 125 | * - gulp plumbedSrc function added (integrates plumber) 126 | * @param obj the object to validate 127 | * @param options the build options 128 | * @throws Error if validation fails 129 | */ 130 | let configureGulpObject = (obj, options) =>{ 131 | validateGulpObject(obj); 132 | const help = require("gulp-help"); 133 | 134 | let configuredGulpObject = help(obj); // provide help through 'gulp help' -- the help text is the second gulp task argument (https://www.npmjs.com/package/gulp-help/) 135 | 136 | configuredGulpObject.options = options; // keep the options on the gulp object (useful as it'll be passed around and can be manipulated) 137 | 138 | // Easily integrate plumber invocation 139 | // Reference: https://gist.github.com/floatdrop/8269868 140 | configuredGulpObject.plumbedSrc = function(){ // should be a function 141 | return configuredGulpObject.src.apply(configuredGulpObject, arguments) 142 | .pipe(plumber({ 143 | errorHandler: reportError 144 | })); 145 | }; 146 | 147 | return configuredGulpObject; 148 | }; 149 | 150 | /** 151 | * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 152 | * @param obj1 153 | * @param obj2 154 | * @returns obj3 a new object based on obj1 and obj2 155 | */ 156 | let mergeOptions = (obj1 = {}, obj2 = {}) =>{ 157 | let retVal = {}; 158 | 159 | for(let attrname in obj1){ 160 | if(obj1.hasOwnProperty(attrname)){ 161 | retVal[ attrname ] = obj1[ attrname ]; 162 | } 163 | } 164 | 165 | for(let attrname in obj2){ 166 | if(obj2.hasOwnProperty(attrname)){ 167 | retVal[ attrname ] = obj2[ attrname ]; 168 | } 169 | } 170 | 171 | return retVal; 172 | }; 173 | 174 | export default { 175 | exclude, 176 | reportError, 177 | filterEmptyDirectories, 178 | validateArgument, 179 | validateGulpObjectIsConfigured, 180 | configureGulpObject, 181 | mergeOptions 182 | }; 183 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* Rather than manage one giant configuration file responsible for creating multiple gulp tasks, each task has been broken out into 2 | its own file. Any files in that directory get automatically required below. 3 | 4 | To add a new task, simply add a new task file that directory. 5 | gulp/tasks/default.js specifies the default set of tasks to run 6 | when you run `gulp`. 7 | 8 | Principle taken from gulp-starter: https://github.com/greypants/gulp-starter 9 | */ 10 | 11 | "use strict"; 12 | 13 | import requireDir from "require-dir"; 14 | 15 | import utils from "./gulp/utils"; 16 | 17 | /** 18 | * This class takes care of loading gulp tasks. 19 | */ 20 | class TasksLoader { 21 | constructor(){ 22 | } 23 | 24 | /** 25 | * Looks for and registers all available tasks. 26 | * @param inputGulp the gulp object to use. If not provided, it'll be loaded 27 | * @param inputOptions the build options to use. If not provided, an empty object is used 28 | */ 29 | registerTasks(inputGulp, inputOptions){ 30 | let gulp = inputGulp || require("gulp"); // this module can be imported without a defined gulp instance 31 | let options = inputOptions || {}; 32 | 33 | gulp = utils.configureGulpObject(gulp, options); // we need to customize the gulp object a bit 34 | 35 | // Load all tasks in gulp/tasks 36 | const loadedModules = requireDir("./gulp/tasks", { 37 | recurse: false 38 | }); 39 | 40 | // request each module to register its tasks 41 | for(let key in loadedModules){ 42 | if(loadedModules.hasOwnProperty(key)){ 43 | let loadedModule = loadedModules[ key ]; 44 | 45 | if(loadedModule.registerTask){ 46 | //console.log(`Registering module: ${key}`); 47 | loadedModule.registerTask(gulp); 48 | } else{ 49 | throw new TypeError(`The following module does not expose the expected interface: ${key}`); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | export default new TasksLoader(); 57 | -------------------------------------------------------------------------------- /src/tests/sanity_test.spec.js: -------------------------------------------------------------------------------- 1 | describe("sanity checks", () =>{ 2 | it("should be able to test", () =>{ 3 | expect(true).toBe(true); 4 | }); 5 | 6 | xit("should skip this", () =>{ 7 | expect(4).toEqual(40); 8 | }); 9 | }); 10 | --------------------------------------------------------------------------------