Opinionated Suite of Gulp Tasks for JavaScript and TypeScript
wGulp aims to lessen the start-up time of a new JavaScript project by providing everything you need out of the box, while making it easy to customize defaults when needed.
Why follow conventions?
It's easy, it's sensible, it gets you cool features for free, it frees up your mind to actually think about interesting problems like your business logic.
Upgrading wGulp?
Take a look at the release notes. We will publish any breaking changes there.
./test - TypeScript or JavaScript test specs, written with Jasmine
See what's included out of the box
gulp help
Out of the box wGulp provides a lot of functionality. It is a collection of best-of-breed tools, patterns, and practices that should answer any need you have for developing a JavaScript/TypeScript/CoffeeScript/FutureScript library or application.
Main Tasks
analyze - Generate code complexity report
applyLicense - Prepends an Apache 2.0 license header to source files in src and sass. Does *not* do CoffeeScript yet
build - Build. Execute the tasks specified in build
bundle - Generate all bundles specified in "bundles"
bundle:<bundleName> - Create a bundle using the <bundleName> options specified in "bundles"
clean - Clean up the build and dist directories
coffee - Transpile CoffeeScript to JavaScript
compass - compile sass css (using compass)
copy:html - Copy html templates in ./src/ to ./build/src/
copy:htmltest - Copy html templates in ./test/ to ./build/test/
copy:js - Copy JavaScript files in ./src/ to ./build/src/
copy:jstest - Copy JavaScript files in ./test/ to ./build/test/
copy:ts - Copy TypeScript files in ./src/ to ./build/src/
cover - Generate and view code coverage report
customize - Copy the standard config files to your project for customization
default - Execute the tasks specified in default
dist - Create a distribution, execute the tasks specified in dist
help - List the available tasks
jsdoc - Generate JSdoc
jshint - Validate JS files with jshint
jsx - Compile React JSX
lint - Validate source with jshint and tslint
livescript - Transpile LiveScript to JavaScript
minify - Minifiy CSS and JS files in ./dist/
minify:css - Minify CSS files in ./dist/
minify:js - Minify JS files in ./dist/
qa - Run default tasks and start serve
sass - compile sass css
serve - Open the project website
test - Execute tasks specified in test and use Karma to run the tests
test:jasmine - Execute tasks specified in test and use node-jasmine to run the tests
tsc - Compile TypeScript
tsd - Discover and centralize TypeScript definition files
tslint - Validate TS files with tslint
tsc:test - Compile TS test files from ./test/ to ./build/test/
watch - Alias for watch:build
watch:analyze - Run analyze task and rerun when source changes
watch:build - Run build task and rerun when source changes
watch:cover - Run cover task and recalculate coverage when source or test files change
watch:jsdoc - Run jsdoc task and rerun when source changes
watch:lint - Run lint task and rerun when source or test files change
watch:test - Run test task and rerun when source or test files change
Language Configuration
wGulp comes with a languages option to help trim tasks you don't need from your build. By default, all wGulp languages will be included in your gulpconfig.js:
You should remove any language you aren't using to reduce build times.
Note: If you are using JavaScript for tests but CoffeeScript for source code, for example, you will still need both javascript and coffeescript listed in your languages configuration.
Task Dependency Tree
Tasks in gulp can wait until other tasks finish by designating those tasks as dependencies. We've extracted that functionality into a single configuration option called taskTree.
Here are some defaults for your reference:
You can override any portion of the tree to alter which tasks cause other tasks to run. Say you want to add a task called myCustomTask to build. You can simply copy the array from above and add it:
But that gets pretty long and verbose. Because of that annoyance we offer alternative syntax for including and excluding tasks from the default dependency arrays.
The object form of this configuration accepts the include and exclude keys as arrays.
Here are some more examples of this kind of configuration:
Not a library?
Then you may want to exclude the libraryDist task from dist:
taskTree: {dist: {exclude: ['libraryDist']},// Only need the following if you are using the minify tasks"minify:css": {exclude: ['libraryDist']},"minify:js": {exclude: ['libraryDist']},
...
}
Exclude from every task
The above configuration is useful, however it may become annoying if you are attempting to exclude a task from everything that depends on it.
wGulp now supports an excludeFromAll feature that will simplify the above like so:
taskTree: {excludeFromAll: ['libraryDist']}
The excludeFromAll option takes least precedence when determining dependencies. So if you explicitly include something for a specific task, that will override the excludeFromAll for that particular case.
Using the Dependency Tree in a Custom Task
Perhaps you want to utilize the dependency tree when defining your own task. wGulp exposes the getDeps function for this purpose.
varcustomizedOptions={taskTree: {'copy:dist': ['clean','build']}}varwGulp=require('wGulp')(gulp,customizedOptions);// Add your own tasks heregulp.task('copy:dist',wGulp.getDeps('copy:dist'),wGulp.copy({src: ['path/to/custom/file'],dest: wGulp.config.paths.dist}));
The getDeps function on the wGulp object simply takes the key of the task to lookup as an argument.
Bundling
wGulp provides a bundling framework to create JavaScript bundles with either
jspm(recommended) or browserify(for legacy support only).
Adding bundle configurations is easy! Here is a simple example using jspm:
bundler - [required] "browserify" or "jspm"
entry - [required] The single entry point of the application/module to bundle
output - [required] Name of the output file
debug - [optional - applies to browserify only] true or false
include - [optional] Array of module names to additionally include
exclude - [optional] Array of module names to exclude from the bundle
external - [optional - applies to browserify only] Array of module names to externalize
addToConfig - [optional - applies to jspm only] true or false. Writes bundle information to config.js, defaults to true
sfx - [optional - applies to jspm only] If true, create a [self-executing bundle](https://github.com/jspm/jspm-cli#4-creating-a-self-executing-bundle)
minify - [optional - applies to jspm only] If true, minifies the bundle contents. Defaults to false
sourceMaps - [optional - applies to jspm only] If true, generates source maps for the bundle. Defaults to true
Centralized APIs with TypeScript Definition Files
Maintaining statically typed APIs speeds up development, enables static code analysis, and builds an inherent contract between modules.
TypeScript definition files are the recommended way to establish these APIs, but using them becomes tricky when consuming from multiple sources.
There are two different sources for TS definitions:
Internal - project specific TS definitions that should be source controlled
External - TS definitions installed with third-party packages (found in node_modules/ or jspm_packages/ for example), or from DefinitelyTyped via the tsd package manager
To make usage consistent, all TS definitions should be centralized in an API directory (./api/), but only the internal definitions should be source controlled.
This can be accomplished like so:
Internal Definitions
Internal TS definitions simply live in ./api/ and should be committed.
External Definitions
DefinitelyTyped definitions should be installed to ./api/ by configuring the path option in tsd.json.
TS definitions distributed with your project's dependencies will be automatically discovered and moved to ./api/ during the tsd task.
This should rarely be needed (convention over configuration), but the dependency directories that are searched during the tsd task can be configured by changing the dependencies path option (see Overriding Default Project Structure).
The tsd Task
The tsd task will take care of the following:
Discovering all third-party TS definitions and moving them to ./api/
Generating an ./api/.gitignore file to ignore all external definitions (including those from DefinitelyTyped)
Notes:
If two or more external definitions collide (have the same filename), the task will warn you.
If any external definition collides with an internal definition, the task will warn you and terminate early to prevent overwriting the internal definition.
Example
To illustrate why this centralized API pattern is effective, consider the following example.
File Structure
|- api
| \- application.d.ts
|- jspm_packages
| |- dependency (provides its own tsd)
| | |- lib.d.ts
| | \- lib.js
| \- lodash (does not provide a tsd)
| \- lodash.js
|- src
| \- application.ts
\- tsd.json (sets `path` to ./api/)
Assume the application wants to get type info for its own code, the dependency that provides its own tsd, and lodash.
The application's tsd and the DefinitelyTyped definitions (once installed) are in ./api/, but the dependency's tsd is buried in ./jspm_packages/.
Additionally, the DefinitelyTyped definitions are not gitignored by default because they're installed to ./api/.
The tsd task will centralize all tsds to ./api/ and gitignore all external definitions, giving you an api directory structure like so:
This means that without any additional configuration, you can easily reference external definitions in your source code and in your internal definitions, like so:
The default testing configuration relies on the jspm module loader to dynamically load test and source files. In order to utilize this functionality, you will need to install any 3rd-party library dependencies via jspm.
If you don't already have jspm installed globally, go ahead and do so:
npm install jspm -g
If you do not want to use jspm to load your tests and instead would like to rely on browserify to bundle before testing, take a look at the browserify-with-karma example. Use a karma.conf.js that looks like the one in that example project.
Override Karma browser option via CLI
wGulp supports supplying the karma browser via a command line argument. Supported arguments are as follows. Note that these work for both gulp test and gulp watch:test. This is a simple alternative to specifying your own karma.conf.js when you only want to change the browser that the tests are running in.
Run in Chrome
gulp test -c
gulp test --chrome
gulp test --browsers Chrome
Run in Firefox
gulp test -f
gulp test --firefox
gulp test --browsers Firefox
Run in PhantomJS
gulp test -p
gulp test --phantom
gulp test --browsers PhantomJS
Run in multiple browsers
gulp test -cf
gulp test --chrome --firefox
gulp test --browsers Chrome,Firefox
Note: The --browsers option hands the string directly to karma's browsers: [] configuration, so it is not limited to the three browsers listed above.
Testing with SauceLabs
wGulp comes with SauceLabs support so that you can easily run your unit tests in a large number of browsers.
In order to utilize this feature you will need a SauceLabs account. Configure wGulp with your username and accessKey via customizedOptions:
varcustomizedOptions={sauceLabs: {testName: "Some identifier of your project/test run",username: "your-sauce-username",accessKey: "your-sauce-accessKey"}};
After that is configured, it is easy to use it.
gulp test --sauce
If you would like to customize the browsers that your tests get run in, add a browsers key to the sauceLabs config. Its value should be an object containing karma-sauce-launcher configs. See the karma-sauce-launcher documentation for more information.
Functional testing with theIntern
wGulp allows you to run your functional intern tests with one command and not be concerned with maintaining your
selenium server. You are also able to pass in an argument to determine if your tests will be run locally or on
SauceLabs based on your intern config files (found in tests/functional/CONFIGFILE).
gulp test:intern --local OR gulp test:intern --sauce
Note: This requires that port 4444 is open because that is what the selenium sever will be utilizing to run as well
as what this task will be expecting to close as part of its clean up.
Note: This task does not include the standing up of the server that is utilized to run your application.
Extending wGulp
wGulp provides a simple interface to extend its functionality via subtasks and customized configuration options.
Subtasks
wGulp provides a set of functions to perform common tasks each with some happy defaults.
These functions are exported on the wGulp module so that you can use them to create customized gulp tasks.
Here is an example of using one of these subtasks:
Each subtasks has some common arguments that allow you to override the defaults.
src - if this is specified, it will be passed into gulp.src()
glob - used if src is not specified, specifies a glob to match
cwd - used if src is not specified, specifies the directory to match the glob in
dest - directory the resulting files should be put in
wGulp currently has these subtasks:
analyze
Analyze code complexity with plato.
applyLicense
Given a list of glob patterns, and a license string this will prepend all matching files with said license. Does nothing
else intelligent at this point. Default behavior is to apply Apache 2.0 header to sources specified in defaults.glob.sources
Remove directories and/or files. This subtask does not take the standard/common arguments. Simply pass an array of paths to remove. Removes build and dist by default.
Transpile code from CoffeeScript to JavaScript.
Takes additional argument bare which defaults to true and is passed into gulp-coffee.
compass
Compile SASS using compass. Takes a few custom args:
configFile - Path to compass config file. Defaults to config's "compassConfig"
includePaths - Array of paths to include. Defaults to config's "path.styleIncludePaths"