generator-tom v1.3.1
Tom 
:a: Yeoman Generator for AngularJS App Scaffold in CoffeeScript
Why Tom ? Because Tom Waits was playing all along the implementation and was a true inspiration :saxophone:
Features
Source Linting
- CoffeeScript source code linting
Configuration
- Environment-based configuration, set
NODE_ENVenvironment variable todevelopmenton your machine for a successful build - Modify configuration attribtues at runtime
usage
Configuration is provider through a class called Config with two function for getting and setting configuration attribtues.
get
The example below shows a sample configuration for the api attribue in app/config/development.yml
api:
credentials:
key: '7968tuyfgjhavsbdfjk'
host: 'http://api.service.url'
redirect_url: 'http://my.place.url'to load this configuration when needed
api = Config.get('api') # Object { credentials: ..., host: ..., redirect_url: ...}you may also dig for a deeper path inside attribtues using dot notation
host = Config.get('api.host')
api_key = Conig.get('api.credentials.key')set
setting an attribtue is as easy as getting it
Config.set('api.host', 'http://some.other.url')Built-in Servers
- Static server for development
- WebSocket server using socket.io
- Tests server using Google Chrome's webdriver (Selenium)
Build Versioning
- Keep builds history
- Navigate and checkout build versions
- Latest build is copied to its own directory current
Assets
- Compiled Javascript with Browserify
- Minified Javascript with UglifyJS
- LESS CSS pre-processor
- Minified CSS with clean-css
- Delivery of static assets from a CDN
CDN
Deliver your build's static files from an external CDN.
In the desired environment's configuration file. i.e. app/config/production.yml
specify endpoints
WARNING endpoints will be whitelisted with Angular's ng.$sceDelegateProvider.whitelist
cdn:
url: 'http://cdn.service.url'You may also specify an endpoint per asset type:
supported:
html,css,js,img
cdn:
url: 'http://cdn.service.url'
html: 'http://cdn.assets.html.service.url'
css: 'http://cdn.assets.css.service.url'
js: 'http://cdn.assets.js.service.url'
img: 'http://cdn.images.service.url'And if one of the types does not exist it will fallback to cdn:url, however if the cdn configuration is not found the URIs will only be preceded by a / delivering from the same host (self).
serving assets
CDN.template('uri/here.html')forHTMLCDN.css('uri/style.css')forCSSCDN.js('my/script.js')forJavascriptCDN.img('pretty/penguin.jpg')for images
deploy to AWS S3
- deploy your assets directly to Amazon Simple Storage Service (using grunt-aws)
- configure aws in
aws.json - use
grunt deploy:awsto deploy
- configure aws in
tip: multiple buckets
// aws.json
{
"s3": {
"buckets": {
"default": "my-default-duck",
"html": "my-html-only"
},
"key": "",
"secret": ""
}
}# Gruntfile.coffee
...
s3: {
options:
bucket: '<%= aws.s3.buckets.default %>'
accessKeyId: '<%= aws.s3.key %>'
secretAccessKey: '<%= aws.s3.secret %>'
current:
cwd: 'current/'
src: '**'
views:
options:
bucket: '<%= aws.s3.buckets.html %>'
cwd: 'app/views/'
src: '**'
}
...Local Storage
- Ships with a local storage service that wraps the browser's local storage feature
- Supports storing mixed data types (arrays, objects, etc.) preserving data type
WebSockets
- Ships with a socket service that makes it easy to communicate using WebSockets
- It wraps the socket.io client library
- Supports namespaces (a.k.a channels)
Testing
Tests will always use the latest build existing in your current directory
- Unit tests unsing Karma
- E2E tests using Protractor
- All the code provided ships with tests, we :heart: tested code
Installation
- install yeoman
- via npm
npm install -g generator-tom - create your project's directory
mkdir ~/dev/my-app && cd $_ yo tom- in another terminal session run
grunt serve gruntand you're done
Directory Structure
- app
- assets : asset resources (styles and images)
- config : app configuration files
- src : app features source files
- views : app views
- builds : builds versions (created after the first build)
- current : the current build's code (created after the first build)
- lib : built-in libraries
- tests : all your tests go here
- e2e : end to end testing files (filnames should follow *Test.coffee)
- config : configuration for E2E tests
- unit : unit testing files (filenames should follow *Test.coffee)
- config : configuration for unit tests
- app.yml : list your app's componentsApp Foundation
separate listing of dependencies makes it seamless to replace the implementation of a feature or module as long as their interface (usage) match, almost what the Liskov principle is about. An easy way to load the app's foundational components is to list them in app.yml as such:
classmap:
- app/routes.coffee:routes
- app/filters.coffee:filters
- lib/CDN/CDN.coffee:CDN
- lib/Config/Config.coffee:Config
- lib/Socket/Socket.coffee:Socket
- lib/Storage/StorageService.coffee:Storage
- app/src/Main/controllers/MainController.coffee:MainController
autoload:
-
expand: yes
cwd: lib
src:
- Storage/**/*.coffee
- Socket/**/*.coffee
dest: lib/classmap
will be translated as a browserify alias, exposing the file/module as specified after : which makes it possible to require the module by alias.
example: app/routes.coffee:routes can be included as require('routes') in any class
autoload
will be translated to aliasMappings in browserify, loads the files according to the patterns in src and exposes them according to the dest
example:
-
expand: yes
cwd: lib
src:
- Storage/**/*.coffee
- Socket/**/*.coffee
dest: lib/now we can require('lib/Storage/StorageService') and anything under those directories.
Commands
grunt
lint, build, minify, and test. Ideal for compiling for production ahead of deployment.
grunt serve
run internal servers for development and testing.
- development server to serve static content (index.html)
http://localhost:9090 - socket server using socket.io
http://localhost:9091 - testing server to be used when running E2E tests
http://localhost:4444/wd/hub
grunt build-dev
lints & builds the project without minifying, sets the latest build as current. Ideal for development.
grunt test
run all tests.
grunt e2e-test
run E2E tests only.
grunt unit-test
run Unit tests only.
grunt list
list all the builds and show the current one.
grunt checkout:<build|step>
checkout a build (make it the current one).
- by target
grunt checkout:2601201422635 - by step
grunt checkout:next- supported steps: first, last, next, prev
grunt deploy:aws
deploy static assets to an AWS S3 bucket. Will upload everything inside current/ and app/views/
Running this project's tests
mocha --recursive
Generators
options
instructions between * are optional
###--bare
skip the sub-directory level.
yo tom:controller main --barewill result insrc/Main/MainController.coffeeinstead ofsrc/Main/controllers/MainController.coffee
###--nofix
skip prefix or postfix when naming.
yo tom:controller main --nofixwill result insrc/Main/controllers/Main.coffeeinstead ofsrc/Main/controllers/MainController.coffee
controller
yo tom:controller <feature> *<controller>*
Example
yo tom:controller love brain
app/src/Love/controllers/BrainController
'use strict'
class BrainController
###*
* Create a new BrainController instance
*
* @param {Config/Config} Config
* @param {$scope} $scope
* @param {$location} $location
* @return {BrainController}
###
constructor: (@Config, @$scope, @$location)->
# inject controller dependencies
BrainController.$inject = ['Config', '$scope', '$location']
module.exports = (app)->
# bring in dependencies
require('src/Config/Config')(app)
# register
app.controller 'BrainController', BrainControllerprovider
yo tom:provider <feature> *<provider>*
Example
yo tom:provider Stream --nofix
app/src/Stream/providers/Stream.coffee
'use strict'
class Stream
###*
* This method is used by Angular to inject dependencies
* into this provider which in turn will be used
* to be injected into the constructor.
*
* @param {src/Config/Config} Config
* @return {Stream}
###
@$get: (Config)-> new this(Config)
###*
* Create a new Stream instance.
*
* @param {src/Config/Config} Config
* @return {Stream}
###
constructor: (@Config)->
Stream.$inject = ['Config']
module.exports = (app)->
# bring in dependencies
require('src/Config/Config')(app)
# register
app.provider 'Stream', -> Streamservice
yo tom:service <feature> *<service>*
Example
yo tom:service Api --bare
app/src/Api/ApiService.coffee
'use strict'
class ApiService
###*
* Create a new ApiService instnace.
*
* @param {$window} $window
* @return {ApiService}
###
constructor: (@$window)->
ApiService.$inject = ['$window']
module.exports = (app)-> app.service 'ApiService', ApiServicefactory
yo tom:factory <feature> *<factory>*
Example
yo tom:factory chocolate --bare
app/src/Chocolate/ChocolateFactory.coffee
'use strict'
class ChocolateFactory
###*
* Create a new ChocolateFactory.
*
* @return {ChocolateFactory}
###
constructor: ->
module.exports = (app)-> app.factory 'ChocolateFactory', ChocolateFactorydirective
nofixoption is ignored
options
--restrict
set the restrict option. Default 'EA'
yo tom:directive --restrict=E
--controller
create a controller along with this directive
yo tom:directive --controller
or you may specify the controller prefix
yo tom:directive --controller=edit will resutl in EditController
--templateUrl
set a URL for your template, will also create an empty template defaulting to the name of the directive in app/views/<directive>.html
--transclude
set the transclude option to true. Default not included
Examples
yo tom:directive customers myCustomer --transclude --restrict=E --bare
app/src/Customers/myCustomer.coffee
'use strict'
module.exports = (app)-> app.directive 'myCustomer', ->
{
restrict: 'E'
transclude: yes
template: ''
scope: { }
link: (scope, element, attrs)->
}yo tom:directive customers myCustomer --bare --controller --templateUrl=app/views/my-customer.html
app/src/Customers/myCustomer.coffee
'use strict'
class MyCustomerController
###*
* Create a new MyCustomerController instance
*
* @param {Config/Config} Config
* @param {$scope} $scope
* @return {MyCustomerController}
###
constructor: (@Config, @$scope)->
# inject controller dependencies
MyCustomerController.$inject = ['Config', '$scope']
module.exports = (app)-> app.directive 'myCustomer', ->
{
restrict: 'EA'
templateUrl: 'app/views/mycustomer.html'
controller: MyCustomerController
}using controllers
when implementing a controller for a directive, you might like to have methods accessible to the directive directly, these methods should be registered to $scope:
class MyCustomerController
constructor: (@Config, @$scope)->
@$scope.customers = [
{
name: 'Naomi'
address: '1600 Amphetheatre'
active: no
}
@$scope.activate = => @$scope.customer.active = yes
@$scope.disactivate = => @$scope.customer.active = no
# you can also proxy to the class preserving context
@$scope.do = @justDoIt
justDoIt: -> # √
# inject controller dependencies
MyCustomerController.$inject = ['Config', '$scope']
module.exports = (app)-> app.directive 'myCustomer', ->
{
restrict: 'EA'
controller: MyCustomerController
templateUrl: '/app/views/customer.html'
}and the directive can access it directly:
<div>{{customer.name}} - {{customer.address}}</div>
<div ng-show="customer.active">Active!</div>
<a href ng-hide="customer.active" ng-click="activate()">Activate!</a>
<a href ng-show="customer.active" ng-click="disactivate()">Disactivate</a>
<a href ng-click="do()">Just Do It</a>TODO
- sub generator for tests
- exception handling
License
MIT license Copyright © Vinelab