├── .gitattributes
├── app
├── templates
│ ├── core
│ │ ├── favicon.ico
│ │ ├── _.gitattributes
│ │ ├── _.htmlhintrc
│ │ ├── src
│ │ │ ├── components
│ │ │ │ └── application
│ │ │ │ │ ├── config
│ │ │ │ │ ├── constants.json
│ │ │ │ │ ├── states.json
│ │ │ │ │ ├── error-handling.js
│ │ │ │ │ └── routing.js
│ │ │ │ │ ├── application.html
│ │ │ │ │ ├── application-controller.js
│ │ │ │ │ ├── application-test.js
│ │ │ │ │ ├── stylesheets
│ │ │ │ │ └── application.scss
│ │ │ │ │ ├── application-spec.js
│ │ │ │ │ ├── application-route.js
│ │ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── _.eslintrc.json
│ │ ├── _.gitignore
│ │ ├── gulp-tasks
│ │ │ ├── dist.js
│ │ │ ├── default.js
│ │ │ ├── build.js
│ │ │ ├── webdriver-standalone.js
│ │ │ ├── copy-static.js
│ │ │ ├── htmlhint.js
│ │ │ ├── eslint.js
│ │ │ ├── serve.js
│ │ │ ├── test.js
│ │ │ ├── webdriver-update.js
│ │ │ ├── watch.js
│ │ │ ├── notify-recompiled.js
│ │ │ ├── compile-source.js
│ │ │ ├── test-e2e.js
│ │ │ ├── compile-stylesheets.js
│ │ │ └── bundle.js
│ │ ├── gulpfile.js
│ │ ├── config
│ │ │ ├── system.js
│ │ │ ├── gulp.js
│ │ │ ├── protractor.js
│ │ │ └── karma.js
│ │ ├── _.editorconfig
│ │ ├── index.html
│ │ └── _package.json
│ └── i18n
│ │ ├── default-locale-config.js
│ │ ├── language.js
│ │ └── translations.js
├── USAGE
└── index.js
├── .gitignore
├── component
├── templates
│ ├── stylesheet.scss
│ ├── template.html
│ ├── controller.js
│ ├── test.js
│ ├── component.js
│ ├── language.js
│ ├── spec.js
│ ├── translations.js
│ └── index.js
├── USAGE
└── index.js
├── state
├── templates
│ ├── stylesheet.scss
│ ├── template.html
│ ├── controller.js
│ ├── test.js
│ ├── language.js
│ ├── spec.js
│ ├── translations.js
│ ├── route.js
│ └── index.js
├── USAGE
└── index.js
├── .eslintignore
├── .eslintrc.json
├── directive
├── templates
│ ├── controller.js
│ ├── test.js
│ ├── directive.js
│ ├── language.js
│ ├── spec.js
│ ├── translations.js
│ └── index.js
├── USAGE
└── index.js
├── extended-lodash.js
├── .npmignore
├── spec
├── support
│ └── jasmine.json
├── files
│ ├── yo-rc-no-i18n.json
│ ├── yo-rc.json
│ └── package.json
├── utils
│ └── disable-i18n.js
├── expectations
│ ├── component-i18n.js
│ ├── state-i18n.js
│ ├── directive-i18n.js
│ ├── app-i18n.js
│ ├── directive.js
│ ├── component.js
│ ├── state.js
│ └── app.js
├── generators
│ ├── component.js
│ ├── directive.js
│ ├── state.js
│ └── app.js
├── component-generator-spec.js
├── directive-generator-spec.js
├── generator-spec.js
├── state-generator-spec.js
└── app-generator-spec.js
├── jasmine-runner.js
├── .editorconfig
├── .travis.yml
├── scripts
└── prepublish.js
├── appveyor.yml
├── license.md
├── package.json
├── state-utils.js
├── base.js
└── readme.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
--------------------------------------------------------------------------------
/app/templates/core/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/core/_.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | npm-debug.log
4 | tmp
5 |
--------------------------------------------------------------------------------
/component/templates/stylesheet.scss:
--------------------------------------------------------------------------------
1 | .<%= tagName %> {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/state/templates/stylesheet.scss:
--------------------------------------------------------------------------------
1 | .<%= componentName %> {
2 |
3 | }
--------------------------------------------------------------------------------
/app/templates/core/_.htmlhintrc:
--------------------------------------------------------------------------------
1 | {
2 | "doctype-first": false
3 | }
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/config/constants.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/app/templates/core/_.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-angular-lazy"
3 | }
4 |
--------------------------------------------------------------------------------
/state/templates/template.html:
--------------------------------------------------------------------------------
1 |
<%= componentName %>
--------------------------------------------------------------------------------
/component/templates/template.html:
--------------------------------------------------------------------------------
1 | <%= tagName %> component
2 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/application.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/templates/core/src/index.js:
--------------------------------------------------------------------------------
1 | import 'babel/external-helpers';
2 | import 'components/application/index';
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | app/templates
2 | component/templates
3 | directive/templates
4 | node_modules
5 | state/templates
6 | tmp
7 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-angular-lazy",
3 | "rules": {
4 | "prefer-rest-params": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/app/templates/core/_.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | build
4 | jspm_packages
5 | test-coverage
6 | node_modules
7 | npm-debug.log
8 |
--------------------------------------------------------------------------------
/state/templates/controller.js:
--------------------------------------------------------------------------------
1 | class <%= controllerName %> {
2 |
3 | }
4 |
5 | export default [
6 | <%= controllerName %>
7 | ];
8 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/dist.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | gulp.task('dist', ['bundle']);
5 | };
6 |
--------------------------------------------------------------------------------
/component/templates/controller.js:
--------------------------------------------------------------------------------
1 | class <%= controllerName %> {
2 |
3 | }
4 |
5 | export default [
6 | <%= controllerName %>
7 | ];
8 |
--------------------------------------------------------------------------------
/directive/templates/controller.js:
--------------------------------------------------------------------------------
1 | class <%= controllerName %> {
2 |
3 | }
4 |
5 | export default [
6 | <%= controllerName %>
7 | ];
8 |
--------------------------------------------------------------------------------
/component/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates a simple component.
3 |
4 | Example:
5 | yo angular-lazy:component component-name [--prefix=]
6 |
--------------------------------------------------------------------------------
/extended-lodash.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const s = require('underscore.string');
3 |
4 | _.mixin(s.exports());
5 |
6 | module.exports = _;
7 |
--------------------------------------------------------------------------------
/directive/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates a simple attribute directive.
3 |
4 | Example:
5 | yo angular-lazy:directive directive-name [--prefix=]
6 |
--------------------------------------------------------------------------------
/app/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates the project structure including the application component and the index state.
3 |
4 | Example:
5 | yo angular-lazy [--skip-install]
6 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/application-controller.js:
--------------------------------------------------------------------------------
1 | class ApplicationController {
2 |
3 | }
4 |
5 | export default [
6 | ApplicationController
7 | ];
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | scripts
2 | spec
3 | tmp
4 | .DS_Store
5 | .editorconfig
6 | .eslintignore
7 | .eslintrc.json
8 | .git
9 | .gitattributes
10 | .gitignore
11 | .idea
12 | .travis.yml
13 | appveyor.yml
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/config/states.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "app",
4 | "url": "/",
5 | "type": "load",
6 | "src": "components/application/index"
7 | }
8 | ]
9 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | gulp.task('default', [
5 | 'build',
6 | 'watch',
7 | 'serve'
8 | ]);
9 | };
10 |
--------------------------------------------------------------------------------
/directive/templates/test.js:
--------------------------------------------------------------------------------
1 | describe('<%= _.titleize(_.humanize(componentName)) %>', () => {
2 | it('should pass the dummy test to verify the protractor setup', () => {
3 | expect(true).toBe(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/state/templates/test.js:
--------------------------------------------------------------------------------
1 | describe('<%= _.titleize(_.humanize(componentName)) %>', () => {
2 | it('should pass the dummy test to verify the protractor setup', () => {
3 | expect(true).toBe(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/component/templates/test.js:
--------------------------------------------------------------------------------
1 | describe('<%= _.titleize(_.humanize(tagName)) %> Component', () => {
2 | it('should pass the dummy test to verify the protractor setup', () => {
3 | expect(true).toBe(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | gulp.task('build', [
5 | 'copy-static',
6 | 'compile-source',
7 | 'compile-stylesheets'
8 | ]);
9 | };
10 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/webdriver-standalone.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const protractor = require('gulp-protractor');
4 |
5 | module.exports = (gulp) => {
6 | gulp.task('webdriver-standalone', protractor.webdriver_standalone);
7 | };
8 |
--------------------------------------------------------------------------------
/spec/support/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "spec",
3 | "spec_files": [
4 | "**/*[sS]pec.js"
5 | ],
6 | "helpers": [
7 | "helpers/**/*.js"
8 | ],
9 | "stopSpecOnExpectationFailure": false,
10 | "random": false
11 | }
12 |
--------------------------------------------------------------------------------
/directive/templates/directive.js:
--------------------------------------------------------------------------------
1 | function <%= directiveName %>() {
2 | return {
3 | restrict: 'A',
4 | controller: '<%= controllerName %> as $ctrl'
5 | };
6 | }
7 |
8 | export default[
9 | <%= directiveName %>
10 | ];
11 |
--------------------------------------------------------------------------------
/app/templates/i18n/default-locale-config.js:
--------------------------------------------------------------------------------
1 | function defaultLanguageConfig($translateProvider) {
2 | $translateProvider.preferredLanguage('<%= defaultLocale %>');
3 | }
4 |
5 | export default [
6 | '$translateProvider',
7 | defaultLanguageConfig
8 | ];
9 |
--------------------------------------------------------------------------------
/component/templates/component.js:
--------------------------------------------------------------------------------
1 | import template from './<%= tagName %>-component.html!text';
2 |
3 | export default {
4 | bindings: { },
5 | bindToController: true,
6 | controller: '<%= controllerName %>',
7 | require: { },
8 | template
9 | };
10 |
--------------------------------------------------------------------------------
/spec/files/yo-rc-no-i18n.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-angular-lazy": {
3 | "appName": "Application Generator Test",
4 | "i18n": false,
5 | "bootstrapCss": true,
6 | "bootstrapJs": true,
7 | "indexRouteName": "home",
8 | "root": null
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/copy-static.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp, config) => {
4 | gulp.task('copy-static', () => gulp
5 | .src(config.paths.static.concat(config.paths.html))
6 | .pipe(gulp.dest(config.paths.build.output))
7 | );
8 | };
9 |
--------------------------------------------------------------------------------
/state/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates a simple state component. If you want to create a child for an existing state use the dot notation known
3 | from UI Router.
4 |
5 | Example:
6 | yo angular-lazy:state state-name [--prefix=]
7 | yo angular-lazy:state parent-state.child-state [--prefix=]
8 |
--------------------------------------------------------------------------------
/app/templates/core/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('./config/gulp');
4 | const glob = require('glob');
5 | const gulp = require('gulp');
6 | const sync = require('gulp-sync')(gulp);
7 |
8 | glob.sync('./gulp-tasks/*.js').forEach((file) => {
9 | require(file)(gulp, config, sync);
10 | });
11 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/htmlhint.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const htmlhint = require('gulp-htmlhint');
4 |
5 | module.exports = (gulp, config) => {
6 | gulp.task('htmlhint', () => gulp
7 | .src(config.paths.html)
8 | .pipe(htmlhint('.htmlhintrc'))
9 | .pipe(htmlhint.reporter())
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/application-test.js:
--------------------------------------------------------------------------------
1 | describe('Application Component', () => {
2 | beforeEach(() => {
3 | browser.get(browser.baseUrl);
4 | });
5 |
6 | it('should pass the dummy test to verify the protractor setup', () => {
7 | expect(true).toBe(true);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/app/templates/i18n/language.js:
--------------------------------------------------------------------------------
1 | function <%= _.camelize(_.slugify(locale, true)) %>($translateProvider) {
2 | $translateProvider.translations('<%= locale %>', {
3 | 'application.foo': 'bar'
4 | });
5 | }
6 |
7 | export default [
8 | '$translateProvider',
9 | <%= _.camelize(_.slugify(locale, true)) %>
10 | ];
11 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/eslint.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const eslint = require('gulp-eslint');
4 |
5 | module.exports = (gulp, config) => {
6 | gulp.task('eslint', () => gulp
7 | .src(config.paths.scripts.concat(config.paths.configs))
8 | .pipe(eslint())
9 | .pipe(eslint.format())
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/component/templates/language.js:
--------------------------------------------------------------------------------
1 | function <%= _.camelize(_.slugify(locale, true)) %>($translateProvider) {
2 | $translateProvider.translations('<%= locale %>', {
3 | '<%= _.camelize(tagName) %>.foo': 'bar'
4 | });
5 | }
6 |
7 | export default [
8 | '$translateProvider',
9 | <%= _.camelize(_.slugify(locale, true)) %>
10 | ];
11 |
--------------------------------------------------------------------------------
/jasmine-runner.js:
--------------------------------------------------------------------------------
1 | const Jasmine = require('jasmine');
2 | const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
3 |
4 | const runner = new Jasmine();
5 | runner.configureDefaultReporter({ print: () => {} });
6 | runner.env.clearReporters();
7 | runner.addReporter(new SpecReporter());
8 | runner.loadConfigFile();
9 | runner.execute();
10 |
--------------------------------------------------------------------------------
/state/templates/language.js:
--------------------------------------------------------------------------------
1 | function <%= _.camelize(_.slugify(locale, true)) %>($translateProvider) {
2 | $translateProvider.translations('<%= locale %>', {
3 | '<%= _.camelize(componentName) %>.foo': 'bar'
4 | });
5 | }
6 |
7 | export default [
8 | '$translateProvider',
9 | <%= _.camelize(_.slugify(locale, true)) %>
10 | ];
11 |
--------------------------------------------------------------------------------
/directive/templates/language.js:
--------------------------------------------------------------------------------
1 | function <%= _.camelize(_.slugify(locale, true)) %>($translateProvider) {
2 | $translateProvider.translations('<%= locale %>', {
3 | '<%= _.camelize(componentName) %>.foo': 'bar'
4 | });
5 | }
6 |
7 | export default [
8 | '$translateProvider',
9 | <%= _.camelize(_.slugify(locale, true)) %>
10 | ];
11 |
--------------------------------------------------------------------------------
/spec/utils/disable-i18n.js:
--------------------------------------------------------------------------------
1 | const appGenerator = require('../generators/app');
2 | const fs = require('fs-extra');
3 | const path = require('path');
4 |
5 | module.exports = function () {
6 | fs.copySync(
7 | path.join(__dirname, '..', 'files', 'yo-rc-no-i18n.json'),
8 | path.join(appGenerator.testDirectory, '.yo-rc.json')
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/spec/expectations/component-i18n.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, prefix) {
2 | return [
3 | `src/components/${prefix || ''}${name}/i18n/de.js`,
4 | `src/components/${prefix || ''}${name}/i18n/en.js`,
5 | `src/components/${prefix || ''}${name}/i18n/fr.js`,
6 | `src/components/${prefix || ''}${name}/i18n/translations.js`
7 | ];
8 | };
9 |
--------------------------------------------------------------------------------
/spec/files/yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-angular-lazy": {
3 | "appName": "Application Generator Test",
4 | "i18n": true,
5 | "bootstrapCss": true,
6 | "bootstrapJs": true,
7 | "indexRouteName": "home",
8 | "locales": [
9 | "de",
10 | "en",
11 | "fr"
12 | ],
13 | "defaultLocale": "en",
14 | "root": null
15 | }
16 | }
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/serve.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const connect = require('gulp-connect');
4 |
5 | module.exports = (gulp, config) => {
6 | gulp.task('serve', ['build'], () => {
7 | connect.server({
8 | port: config.serverPort,
9 | livereload: config.livereload,
10 | root: ['.']
11 | });
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | [**.scss]
15 | indent_size = 2
16 |
17 | [**.json]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/app/templates/core/config/system.js:
--------------------------------------------------------------------------------
1 | System.config({
2 | defaultJSExtensions: true,
3 | buildCSS: true,
4 | transpiler: false,
5 | babelOptions: {
6 | externalHelpers: true,
7 | optional: [
8 | 'runtime',
9 | 'optimisation.modules.system'
10 | ]
11 | },
12 | paths: {
13 | '*': 'build/*'
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/spec/expectations/state-i18n.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, prefix) {
2 | return [
3 | `src/components/${prefix || ''}${name}-state/i18n/de.js`,
4 | `src/components/${prefix || ''}${name}-state/i18n/en.js`,
5 | `src/components/${prefix || ''}${name}-state/i18n/fr.js`,
6 | `src/components/${prefix || ''}${name}-state/i18n/translations.js`
7 | ];
8 | };
9 |
--------------------------------------------------------------------------------
/state/templates/spec.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-mocks';
3 | import component from './index';
4 |
5 | describe('<%= _.titleize(_.humanize(componentName)) %>', () => {
6 | beforeEach(angular.mock.module(component.name));
7 |
8 | it('should pass the dummy test to verify the karma setup', () => {
9 | expect(true).toEqual(true);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/directive/templates/spec.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-mocks';
3 | import component from './index';
4 |
5 | describe('<%= _.titleize(_.humanize(componentName)) %>', () => {
6 | beforeEach(angular.mock.module(component.name));
7 |
8 | it('should pass the dummy test to verify the karma setup', () => {
9 | expect(true).toEqual(true);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/component/templates/spec.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-mocks';
3 | import component from './index';
4 |
5 | describe('<%= _.titleize(_.humanize(tagName)) %> Component', () => {
6 | beforeEach(angular.mock.module(component.name));
7 |
8 | it('should pass the dummy test to verify the karma setup', () => {
9 | expect(true).toEqual(true);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/app/templates/core/_.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | [**.scss]
15 | indent_size = 2
16 |
17 | [**.json]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/spec/expectations/directive-i18n.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, prefix) {
2 | return [
3 | `src/components/${prefix || ''}${name}-directive/i18n/de.js`,
4 | `src/components/${prefix || ''}${name}-directive/i18n/en.js`,
5 | `src/components/${prefix || ''}${name}-directive/i18n/fr.js`,
6 | `src/components/${prefix || ''}${name}-directive/i18n/translations.js`
7 | ];
8 | };
9 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const KarmaServer = require('karma').Server;
4 |
5 | module.exports = (gulp) => {
6 | gulp.task('test', ['build'], (done) => {
7 | new KarmaServer({
8 | configFile: `${__dirname}/../config/karma.js`,
9 | singleRun: true
10 | }, () => {
11 | done();
12 | }).start();
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/config/error-handling.js:
--------------------------------------------------------------------------------
1 | function errorHandlingConfig($rootScope) {
2 | $rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error) => {
3 | throw new Error(`error occurred while transitioning to ${toState.name}: ${error}`);
4 | });
5 | }
6 |
7 | export default [
8 | '$rootScope',
9 | errorHandlingConfig
10 | ];
11 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | <% if(bootstrapCss) { %>/**
2 | TODO: By default the whole bootstrap framework is imported. But you also have the possibility to only import the grid
3 | "bootstrap/grid" and it's dependencies if you only want to use this for example.
4 | */
5 | @import "bootstrap";
6 |
7 | <% } %>body {
8 | font-family: sans-serif;
9 | padding: 20px;
10 | }
11 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/application-spec.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-mocks';
3 | import applicationComponent from './index';
4 |
5 | describe('Application Component', () => {
6 | beforeEach(angular.mock.module(applicationComponent.name));
7 |
8 | it('should pass the dummy test to verify the karma setup', () => {
9 | expect(true).toEqual(true);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/spec/expectations/app-i18n.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return [
3 | 'src/components/application/config/default-locale.js',
4 | 'src/components/application/i18n',
5 | 'src/components/application/i18n/de.js',
6 | 'src/components/application/i18n/en.js',
7 | 'src/components/application/i18n/fr.js',
8 | 'src/components/application/i18n/translations.js'
9 | ];
10 | };
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | cache:
3 | directories:
4 | $HOME/.npm
5 | $HOME/.cache
6 |
7 | language: node_js
8 | node_js:
9 | - "4"
10 | - "6"
11 | - node
12 |
13 | os:
14 | - linux
15 |
16 | before_install:
17 | - npm config set progress false
18 | - npm i -g npm@3
19 |
20 | install:
21 | - node --version
22 | - npm --version
23 | - git --version
24 | - npm install --no-optional
25 |
26 | script: npm test
27 |
--------------------------------------------------------------------------------
/scripts/prepublish.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const packageInfo = require('../package.json');
3 |
4 | const packageInfoTemplate = fs.readFileSync('app/templates/core/_package.json').toString();
5 | const requiredDependency = `"generator-angular-lazy": "~${packageInfo.version}"`;
6 |
7 | if (packageInfoTemplate.match(requiredDependency) === null) {
8 | throw new Error('App template package.json might contain a wrong version of generator-angular-lazy!');
9 | }
10 |
--------------------------------------------------------------------------------
/app/templates/i18n/translations.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-translate';
3 | <% locales.forEach(function (locale) { %>import <%= _.camelize(_.slugify(locale)) %> from './<%= _.slugify(locale) %>';
4 | <% }) %>
5 | const dependencies = [
6 | 'pascalprecht.translate'
7 | ];
8 |
9 | export default angular
10 | .module('application-translations', dependencies)<% locales.forEach(function (locale) { %>
11 | .config(<%= _.camelize(_.slugify(locale)) %>)<% }) %>;
12 |
--------------------------------------------------------------------------------
/component/templates/translations.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-translate';
3 | <% locales.forEach(function (locale) { %>import <%= _.camelize(_.slugify(locale)) %> from './<%= _.slugify(locale) %>';
4 | <% }) %>
5 | const dependencies = [
6 | 'pascalprecht.translate'
7 | ];
8 |
9 | export default angular
10 | .module('<%= tagName %>-component-translations', dependencies)<% locales.forEach(function (locale) { %>
11 | .config(<%= _.camelize(_.slugify(locale)) %>)<% }) %>;
12 |
--------------------------------------------------------------------------------
/state/templates/translations.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-translate';
3 | <% locales.forEach(function (locale) { %>import <%= _.camelize(_.slugify(locale)) %> from './<%= _.slugify(locale) %>';
4 | <% }) %>
5 | const dependencies = [
6 | 'pascalprecht.translate'
7 | ];
8 |
9 | export default angular
10 | .module('<%= componentName %>-component-translations', dependencies)<% locales.forEach(function (locale) { %>
11 | .config(<%= _.camelize(_.slugify(locale)) %>)<% }) %>;
12 |
--------------------------------------------------------------------------------
/directive/templates/translations.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import 'angular-translate';
3 | <% locales.forEach(function (locale) { %>import <%= _.camelize(_.slugify(locale)) %> from './<%= _.slugify(locale) %>';
4 | <% }) %>
5 | const dependencies = [
6 | 'pascalprecht.translate'
7 | ];
8 |
9 | export default angular
10 | .module('<%= componentName %>-component-translations', dependencies)<% locales.forEach(function (locale) { %>
11 | .config(<%= _.camelize(_.slugify(locale)) %>)<% }) %>;
12 |
--------------------------------------------------------------------------------
/spec/expectations/directive.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, prefix) {
2 | return [
3 | `src/components/${prefix || ''}${name}-directive/${name}-directive-controller.js`,
4 | `src/components/${prefix || ''}${name}-directive/${name}-directive.js`,
5 | `src/components/${prefix || ''}${name}-directive/${name}-directive-spec.js`,
6 | `src/components/${prefix || ''}${name}-directive/${name}-directive-test.js`,
7 | `src/components/${prefix || ''}${name}-directive/index.js`
8 | ];
9 | };
10 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/webdriver-update.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const protractor = require('gulp-protractor');
4 |
5 | module.exports = (gulp) => {
6 | gulp.task('webdriver-update', (done) => {
7 | const browsers = ['chrome'];
8 |
9 | if (process.platform === 'win32') {
10 | browsers.push('ie');
11 | } else if (process.platform === 'darwin') {
12 | browsers.push('safari');
13 | }
14 |
15 | protractor.webdriver_update({ browsers }, done);
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp, config, sync) => {
4 | gulp.task('watch', () => {
5 | gulp.watch(config.paths.stylesheets, sync.sync(['compile-stylesheets', 'notify-recompiled']));
6 | gulp.watch(config.paths.scripts, sync.sync(['compile-source', 'eslint', 'notify-recompiled']));
7 | gulp.watch(config.paths.html, sync.sync(['copy-static', 'htmlhint', 'notify-recompiled']));
8 | gulp.watch(config.paths.static, sync.sync(['copy-static', 'notify-recompiled']));
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/notify-recompiled.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const connect = require('gulp-connect');
4 | const notify = require('gulp-notify');
5 |
6 | module.exports = (gulp, config) => {
7 | gulp.task('notify-recompiled', () => {
8 | let task = gulp.src('index.html');
9 |
10 | if (config.livereload) {
11 | task = task.pipe(connect.reload());
12 | }
13 |
14 | if (config.notifications) {
15 | task = task.pipe(notify('recompiled changed files'));
16 | }
17 |
18 | return task;
19 | });
20 | };
21 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/application-route.js:
--------------------------------------------------------------------------------
1 | import template from './application.html!text';
2 |
3 | function applicationRouteConfig($stateProvider) {
4 | $stateProvider
5 | .state('app', {
6 | url: '/',
7 | abstract: true,
8 | views: {
9 | page: {
10 | controller: 'ApplicationController as application',
11 | template
12 | }
13 | }
14 | });
15 | }
16 |
17 | export default [
18 | '$stateProvider',
19 | applicationRouteConfig
20 | ];
21 |
--------------------------------------------------------------------------------
/spec/expectations/component.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, prefix) {
2 | return [
3 | `src/components/${prefix || ''}${name}/${name}-component-controller.js`,
4 | `src/components/${prefix || ''}${name}/${name}-component.js`,
5 | `src/components/${prefix || ''}${name}/${name}-component.scss`,
6 | `src/components/${prefix || ''}${name}/${name}-component.html`,
7 | `src/components/${prefix || ''}${name}/${name}-component-spec.js`,
8 | `src/components/${prefix || ''}${name}/${name}-component-test.js`,
9 | `src/components/${prefix || ''}${name}/index.js`
10 | ];
11 | };
12 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/compile-source.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const babel = require('gulp-babel');
4 | const plumber = require('gulp-plumber');
5 | const sourcemaps = require('gulp-sourcemaps');
6 |
7 | module.exports = (gulp, config) => {
8 | gulp.task('compile-source', () => gulp
9 | .src(config.paths.sources)
10 | .pipe(plumber())
11 | .pipe(sourcemaps.init())
12 | .pipe(babel({
13 | presets: [
14 | 'es2015',
15 | 'stage-2'
16 | ]
17 | }))
18 | .pipe(sourcemaps.write('.'))
19 | .pipe(gulp.dest(config.paths.build.output))
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/config/routing.js:
--------------------------------------------------------------------------------
1 | import futureStates from './states.json!';
2 |
3 | function routingConfig($locationProvider, $urlRouterProvider, $httpProvider, $futureStateProvider) {
4 | futureStates.forEach((state) => $futureStateProvider.futureState(state));
5 | $httpProvider.useApplyAsync(true);
6 | $locationProvider.html5Mode(false);
7 | $urlRouterProvider.otherwise('/<%= indexUrl %>');
8 | $urlRouterProvider.when('/', '/<%= indexUrl %>');
9 | }
10 |
11 | export default [
12 | '$locationProvider',
13 | '$urlRouterProvider',
14 | '$httpProvider',
15 | '$futureStateProvider',
16 | routingConfig
17 | ];
18 |
--------------------------------------------------------------------------------
/directive/templates/index.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';<% if (i18n) { %>
2 | import 'angular-translate';
3 | import translationsModule from './i18n/translations';<% } %>
4 | import <%= controllerName %> from './<%= controllerFileName %>';
5 | import <%= directiveName %> from './<%= directiveFileName %>';
6 |
7 | const dependencies = [
8 | <% if (i18n) { %> 'pascalprecht.translate',
9 | translationsModule.name<% } %>
10 | ];
11 |
12 | export default angular
13 | .module('<%= componentName %>-component', dependencies)
14 | .controller('<%= controllerName %>', <%= controllerName %>)
15 | .directive('<%= _.camelize(attributeName) %>', <%= directiveName %>);
16 |
--------------------------------------------------------------------------------
/spec/expectations/state.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, prefix) {
2 | return [
3 | `src/components/${prefix || ''}${name}-state`,
4 | `src/components/${prefix || ''}${name}-state/index.js`,
5 | `src/components/${prefix || ''}${name}-state/${name}-route.js`,
6 | `src/components/${prefix || ''}${name}-state/${name}-state-controller.js`,
7 | `src/components/${prefix || ''}${name}-state/${name}-state.html`,
8 | `src/components/${prefix || ''}${name}-state/${name}-state.scss`,
9 | `src/components/${prefix || ''}${name}-state/${name}-state-spec.js`,
10 | `src/components/${prefix || ''}${name}-state/${name}-state-test.js`
11 | ];
12 | };
13 |
--------------------------------------------------------------------------------
/component/templates/index.js:
--------------------------------------------------------------------------------
1 | import './<%= tagName %>-component.css!';
2 | import angular from 'angular';<% if (i18n) { %>
3 | import 'angular-translate';
4 | import translationsModule from './i18n/translations';<% } %>
5 | import <%= controllerName %> from './<%= controllerFileName %>';
6 | import <%= componentName %> from './<%= componentFileName %>';
7 |
8 | const dependencies = [
9 | <% if (i18n) { %> 'pascalprecht.translate',
10 | translationsModule.name<% } %>
11 | ];
12 |
13 | export default angular
14 | .module('<%= tagName %>-component', dependencies)
15 | .controller('<%= controllerName %>', <%= controllerName %>)
16 | .component('<%= _.camelize(tagName) %>', <%= componentName %>);
17 |
--------------------------------------------------------------------------------
/state/templates/route.js:
--------------------------------------------------------------------------------
1 | import template from './<%= templateName %>.html!text';
2 |
3 | function <%= routeName %>Config($stateProvider) {
4 | $stateProvider
5 | .state('app.<%= stateName %>', {
6 | url: '<%= url %>',
7 | <% if (target) { %>views: {
8 | <%= target %>: {
9 | controller: '<%= controllerName %> as <%= controllerInstanceName %>',
10 | template
11 | }
12 | }<% } else { %>controller: '<%= controllerName %> as <%= controllerInstanceName %>',
13 | template<% } %>
14 | });
15 | }
16 |
17 | export default [
18 | '$stateProvider',
19 | <%= routeName %>Config
20 | ];
21 |
--------------------------------------------------------------------------------
/state/templates/index.js:
--------------------------------------------------------------------------------
1 | import './<%= componentName %>.css!';
2 | import angular from 'angular';
3 | import 'angular-ui-router';<% if (i18n) { %>
4 | import 'angular-translate';
5 | import translationsModule from './i18n/translations';<% } %>
6 | import <%= controllerName %> from './<%= controllerFileName %>';
7 | import <%= routeName %>Config from './<%= routeFileName %>';
8 |
9 | const dependencies = [
10 | 'ui.router'<% if (i18n) { %>,
11 | 'pascalprecht.translate',
12 | translationsModule.name<% } %>
13 | ];
14 |
15 | export default angular
16 | .module('<%= componentName %>-component', dependencies)
17 | .controller('<%= controllerName %>', <%= controllerName %>)
18 | .config(<%= routeName %>Config);
19 |
--------------------------------------------------------------------------------
/app/templates/core/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= _.humanize(appName) %>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/spec/generators/component.js:
--------------------------------------------------------------------------------
1 | const appGenerator = require('./app');
2 | const fs = require('fs-extra');
3 | const helpers = require('yeoman-test');
4 | const path = require('path');
5 |
6 | function run(name, options, runner, setup) {
7 | helpers
8 | .run(path.join(__dirname, '..', '..', 'component'))
9 | .inDir(appGenerator.testDirectory, () => {
10 | fs.copySync(
11 | path.join(__dirname, '..', 'files', 'yo-rc.json'),
12 | path.join(appGenerator.testDirectory, '.yo-rc.json')
13 | );
14 |
15 | if (setup) {
16 | setup();
17 | }
18 | })
19 | .withArguments([name, '--force'])
20 | .withOptions(options || {})
21 | .on('end', () => {
22 | runner();
23 | });
24 | }
25 |
26 | module.exports = {
27 | run
28 | };
29 |
--------------------------------------------------------------------------------
/spec/generators/directive.js:
--------------------------------------------------------------------------------
1 | const appGenerator = require('./app');
2 | const fs = require('fs-extra');
3 | const helpers = require('yeoman-test');
4 | const path = require('path');
5 |
6 | function run(name, options, runner, setup) {
7 | helpers
8 | .run(path.join(__dirname, '..', '..', 'directive'))
9 | .inDir(appGenerator.testDirectory, () => {
10 | fs.copySync(
11 | path.join(__dirname, '..', 'files', 'yo-rc.json'),
12 | path.join(appGenerator.testDirectory, '.yo-rc.json')
13 | );
14 |
15 | if (setup) {
16 | setup();
17 | }
18 | })
19 | .withArguments([name, '--force'])
20 | .withOptions(options || {})
21 | .on('end', () => {
22 | runner();
23 | });
24 | }
25 |
26 | module.exports = {
27 | run
28 | };
29 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/test-e2e.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const connect = require('gulp-connect');
4 | const protractor = require('gulp-protractor');
5 |
6 | module.exports = (gulp, config) => {
7 | gulp.task('test-e2e', ['build', 'webdriver-update'], (done) => {
8 | connect.server({
9 | port: config.serverPortTest,
10 | root: ['.']
11 | });
12 |
13 | gulp
14 | .src(`${config.paths.build.output}/**/*-test.js`)
15 | .pipe(protractor.protractor({
16 | configFile: `${__dirname}/../config/protractor.js`
17 | }))
18 | .on('error', (err) => {
19 | connect.serverClose();
20 |
21 | throw err;
22 | })
23 | .on('end', () => {
24 | connect.serverClose();
25 |
26 | done();
27 | });
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # http://www.appveyor.com/docs/appveyor-yml
2 |
3 | # Fix line endings in Windows. (runs before repo cloning)
4 | init:
5 | - git config --global core.autocrlf true
6 |
7 | environment:
8 | matrix:
9 | - nodejs_version: "LTS"
10 | - nodejs_version: "Stable"
11 |
12 | platform:
13 | - x64
14 |
15 | # Install scripts. (runs after repo cloning)
16 | install:
17 | - git rev-parse HEAD
18 | - ps: Install-Product node $env:nodejs_version
19 | - npm install -g npm@3
20 | - npm version
21 | - npm install
22 |
23 | cache:
24 | - '%APPDATA%\npm-cache'
25 |
26 | branches:
27 | except:
28 | - webpack-test
29 |
30 | # Post-install test scripts.
31 | test_script:
32 | # Output useful info for debugging.
33 | - npm version
34 | - cmd: npm test
35 |
36 | # Don't actually build.
37 | build: off
38 |
39 | # Set build version format here instead of in the admin panel.
40 | version: "{build}"
41 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/compile-stylesheets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const autoprefixer = require('gulp-autoprefixer');
4 | const path = require('path');
5 | const plumber = require('gulp-plumber');
6 | const sass = require('gulp-sass');
7 | const sourcemaps = require('gulp-sourcemaps');
8 |
9 | module.exports = (gulp, config) => {
10 | gulp.task('compile-stylesheets', () => gulp
11 | .src(config.paths.stylesheets)
12 | .pipe(plumber())
13 | .pipe(sourcemaps.init())
14 | .pipe(sass({
15 | outputStyle: 'expanded',
16 | includePaths: [
17 | path.resolve('src')<% if (bootstrapCss) { %>,
18 | path.resolve('node_modules/bootstrap-sass/assets/stylesheets')<% } %>
19 | ]
20 | }))
21 | .pipe(autoprefixer())
22 | .pipe(sourcemaps.write('.'))
23 | .pipe(gulp.dest(config.paths.build.output))
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/spec/generators/state.js:
--------------------------------------------------------------------------------
1 | const appGenerator = require('./app');
2 | const fs = require('fs-extra');
3 | const helpers = require('yeoman-test');
4 | const path = require('path');
5 |
6 | const statesFile = 'src/components/application/config/states.json';
7 |
8 | function run(name, options, runner, setup, prompts) {
9 | helpers
10 | .run(path.join(__dirname, '..', '..', 'state'))
11 | .inDir(appGenerator.testDirectory, () => {
12 | fs.copySync(
13 | path.join(__dirname, '..', 'files', 'yo-rc.json'),
14 | path.join(appGenerator.testDirectory, '.yo-rc.json')
15 | );
16 |
17 | fs.outputFileSync(statesFile, '[]');
18 |
19 | if (setup) {
20 | setup();
21 | }
22 | })
23 | .withArguments([name, '--force'])
24 | .withOptions(options || {})
25 | .withPrompts(prompts || {})
26 | .on('end', () => {
27 | runner();
28 | });
29 | }
30 |
31 | module.exports = {
32 | statesFile,
33 | run
34 | };
35 |
--------------------------------------------------------------------------------
/app/templates/core/config/gulp.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | paths: {
5 | build: {
6 | output: 'build'
7 | },
8 | sources: ['src/**/*.js'],
9 | configs: ['config/**/!(system).js', 'gulp-tasks/**/*.js', 'gulpfile.js'],
10 | stylesheets: ['src/**/*.scss'],
11 | scripts: [
12 | 'src/**/*.js',
13 | 'gulpfile.js'
14 | ],
15 | html: [
16 | 'src/**/*.html',
17 | 'index.html'
18 | ],
19 | static: [
20 | './src/**/*.json',
21 | './src/**/*.svg',
22 | './src/**/*.woff',
23 | './src/**/*.woff2',
24 | './src/**/*.ttf',
25 | './src/**/*.png',
26 | './src/**/*.gif',
27 | './src/**/*.ico',
28 | './src/**/*.jpg',
29 | './src/**/*.eot',
30 | './config/system.js'
31 | ]
32 | },
33 | serverPort: 8088,
34 | serverPortTest: 8089,
35 | livereload: false,
36 | notifications: true
37 | };
38 |
--------------------------------------------------------------------------------
/app/templates/core/gulp-tasks/bundle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Bundler = require('angular-lazy-bundler');
4 |
5 | module.exports = (gulp) => {
6 | gulp.task('bundle', ['build'], (done) => {
7 | const bundler = new Bundler({
8 | systemJsConfig: 'build/system.js'
9 | });
10 |
11 | bundler
12 | .bundle({
13 | components: [
14 | 'application',
15 | '<%= indexComponent %>'
16 | ],
17 | packages: [
18 | 'angular',
19 | 'angular-ui-router',
20 | 'ui-router-extras',
21 | 'oclazyload',<% if (i18n) { %>
22 | 'angular-translate',<% } %>
23 | 'css',
24 | 'json',
25 | 'text'
26 | ]
27 | }, 'main')
28 | .then(() => bundler.bundleRemainingComponents())
29 | .then(() => bundler.bundleRemainingPackages())
30 | .then(() => bundler.saveConfig())
31 | .then(() => done())
32 | .catch((err) => done(err));
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/spec/files/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "version": "0.0.0",
4 | "devDependencies": {
5 | "angular-lazy-bundler": "^0.2.3",
6 | "babel": "^6.5.0",
7 | "babel-core": "^6.5.0",
8 | "babel-preset-es2015": "^6.5.0",
9 | "babel-preset-stage-2": "^6.5.0",
10 | "eslint-config-angular-lazy": "^0.1.0",
11 | "generator-angular-lazy": "^0.4.1",
12 | "glob": "^6.0.4",
13 | "gulp": "^3.9.0",
14 | "gulp-autoprefixer": "^3.1.0",
15 | "gulp-babel": "^6.1.0",
16 | "gulp-connect": "^2.3.0",
17 | "gulp-eslint": "^1.1.1",
18 | "gulp-htmlhint": "^0.3.0",
19 | "gulp-notify": "^2.2.0",
20 | "gulp-plumber": "^1.0.0",
21 | "gulp-protractor": "^2.1.0",
22 | "gulp-sass": "^2.2.0",
23 | "gulp-sourcemaps": "^1.6.0",
24 | "gulp-sync": "^0.1.4",
25 | "htmlhint": "^0.9.7",
26 | "jasmine-core": "^2.4.0",
27 | "jspm": "^0.16.27",
28 | "karma": "^0.13.9",
29 | "karma-chrome-launcher": "^0.2.0",
30 | "karma-coverage": "^0.5.2",
31 | "protractor": "^3.0.0"
32 | },
33 | "dependencies": {
34 |
35 | },
36 | "scripts": {
37 | "setup": "npm install -g gulp jspm && npm install && jspm install",
38 | "build": "gulp build",
39 | "dist": "gulp dist",
40 | "test": "gulp test",
41 | "test-e2e": "gulp test-e2e"
42 | },
43 | "engines": {
44 | "node": ">=4.2.0",
45 | "npm": ">=3.0.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/templates/core/config/protractor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 |
5 | let protractorBase = `${__dirname}/../node_modules/protractor/`;
6 |
7 | if (!fs.existsSync(protractorBase)) {
8 | protractorBase = `${__dirname}/../node_modules/gulp-protractor/node_modules/protractor/`;
9 | }
10 |
11 | const webdriverVersions = require(`${protractorBase}/node_modules/webdriver-manager/config.json`).webdriverVersions;
12 |
13 | const capabilities = [
14 | {
15 | browserName: 'chrome',
16 | chromeOptions: {
17 | args: ['no-sandbox']
18 | }
19 | },
20 | {
21 | browserName: 'firefox'
22 | }
23 | ];
24 |
25 | if (process.platform === 'win32') {
26 | capabilities.push({
27 | browserName: 'internet explorer',
28 | platform: 'ANY',
29 | version: '11'
30 | });
31 | } else if (process.platform === 'darwin') {
32 | capabilities.push({
33 | browserName: 'safari'
34 | });
35 | }
36 |
37 | module.exports.config = {
38 | multiCapabilities: capabilities,
39 | seleniumServerJar: `${protractorBase}selenium/selenium-server-standalone-${webdriverVersions.selenium}.jar`,
40 | baseUrl: 'http://localhost:8089/index.html#',
41 | rootElement: '#applicationContainer',
42 | framework: 'jasmine2',
43 | specs: ['../build/**/*-test.js'],
44 | maxSessions: 1,
45 | jasmineNodeOpts: {
46 | defaultTimeoutInterval: 360000
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/app/templates/core/src/components/application/index.js:
--------------------------------------------------------------------------------
1 | import './stylesheets/application.css!';
2 | import 'babel/external-helpers';
3 | import angular from 'angular';
4 | import 'angular-ui-router';
5 | import 'ui-router-extras';
6 | import ocLazyLoad from 'oclazyload';
7 | import ngLazy from 'angular-lazy';<% if (i18n) { %>
8 | import 'angular-translate';
9 | import translationsModule from './i18n/translations';
10 | import defaultLocaleConfig from './config/default-locale';<% } %>
11 | import routingConfig from './config/routing';
12 | import errorHandlingConfig from './config/error-handling';
13 | import constants from './config/constants.json!';
14 | import ApplicationController from './application-controller';
15 | import applicationRoute from './application-route';
16 |
17 | const dependencies = [
18 | 'ui.router',
19 | ocLazyLoad,
20 | 'ct.ui.router.extras',
21 | 'ct.ui.router.extras.future',
22 | ngLazy.name<% if (i18n) { %>,
23 | 'pascalprecht.translate',
24 | translationsModule.name<% } %>
25 | ];
26 |
27 | const app = angular
28 | .module('application-component', dependencies)
29 | .controller('ApplicationController', ApplicationController)
30 | .config(routingConfig)
31 | .config(applicationRoute)<% if (i18n) { %>
32 | .config(defaultLocaleConfig)<% } %>
33 | .run(errorHandlingConfig);
34 |
35 | Object.keys(constants).forEach((constantName) => {
36 | app.constant(constantName, constants[constantName]);
37 | });
38 |
39 | export default app;
40 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | **Copyright (c) 2016, Mato Ilic**
2 |
3 | **All rights reserved.**
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10 |
11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator-angular-lazy",
3 | "version": "0.6.7",
4 | "description": "Yeoman generator for creating Angular applications which lazy load components as needed at runtime. Based on SystemJS, JSPM, Babel and Gulp.",
5 | "repository": {
6 | "type": "git",
7 | "url": "git://github.com/matoilic/generator-angular-lazy.git"
8 | },
9 | "keywords": [
10 | "yeoman-generator",
11 | "angular",
12 | "angularjs",
13 | "systemjs",
14 | "jspm",
15 | "app",
16 | "scaffold"
17 | ],
18 | "author": "Mato Ilic ",
19 | "license": "BSD-3-Clause",
20 | "bugs": {
21 | "url": "https://github.com/matoilic/generator-angular-lazy/issues"
22 | },
23 | "homepage": "https://github.com/matoilic/generator-angular-lazy#readme",
24 | "dependencies": {
25 | "glob": "~7.1.1",
26 | "lodash": "~4.17.4",
27 | "underscore.string": "~3.3.4",
28 | "yeoman-generator": "~1.0.1"
29 | },
30 | "engines": {
31 | "node": ">=4.2.0",
32 | "npm": ">=3.0.0"
33 | },
34 | "devDependencies": {
35 | "eslint": "~3.13.0",
36 | "eslint-config-angular-lazy": "^0.1.1",
37 | "fs-extra": "^1.0.0",
38 | "jasmine": "~2.5.2",
39 | "jasmine-spec-reporter": "^3.1.0",
40 | "npm-check": "^5.4.0",
41 | "semver": "^5.3.0",
42 | "yeoman-assert": "~2.2.1",
43 | "yeoman-test": "~1.6.0",
44 | "yo": "^1.8.5"
45 | },
46 | "scripts": {
47 | "prepublish": "node scripts/prepublish.js",
48 | "test": "eslint . && node jasmine-runner.js"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/spec/generators/app.js:
--------------------------------------------------------------------------------
1 | const helpers = require('yeoman-test');
2 | const path = require('path');
3 | const fs = require('fs-extra');
4 | const _ = require('lodash');
5 |
6 | const testDirectory = path.join(__dirname, '..', '..', 'tmp');
7 | const defaultOptions = {
8 | appName: 'Application Generator Test',
9 | i18n: true,
10 | locales: ['de', 'en', 'fr'],
11 | defaultLocale: 'en',
12 | bootstrapCss: true,
13 | bootstrapJs: true,
14 | indexRouteName: 'home'
15 | };
16 |
17 | function run(options, prompts, runner, setup) {
18 | helpers
19 | .run(path.join(__dirname, '..', '..', 'app'))
20 | .inDir(testDirectory, () => {
21 | fs.copySync(
22 | path.join(__dirname, '..', 'files', 'package.json'),
23 | path.join(testDirectory, 'package.json')
24 | );
25 |
26 | fs.ensureDirSync(path.join(testDirectory, 'node_modules'));
27 | fs.ensureSymlinkSync(
28 | path.join(__dirname, '..', '..'),
29 | path.join(testDirectory, 'node_modules', 'generator-angular-lazy')
30 | );
31 |
32 | if (setup) {
33 | setup();
34 | }
35 | })
36 | .withArguments(['--force'])
37 | .withOptions(options || {})
38 | .withPrompts(_.merge({}, defaultOptions, prompts || {}))
39 | .on('end', () => {
40 | runner();
41 | });
42 | }
43 |
44 | module.exports = {
45 | testDirectory,
46 | defaultOptions,
47 | run
48 | };
49 |
--------------------------------------------------------------------------------
/spec/component-generator-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('yeoman-assert');
4 | const componentFileExpectations = require('./expectations/component');
5 | const componentI18nFileExpectations = require('./expectations/component-i18n');
6 | const componentGenerator = require('./generators/component');
7 | const disableI18n = require('./utils/disable-i18n');
8 |
9 | describe('Component generator', () => {
10 | it('generates the component structure', (done) => {
11 | componentGenerator.run('custom', null, () => {
12 | assert.file(componentFileExpectations('custom'));
13 | assert.file(componentI18nFileExpectations('custom'));
14 |
15 | done();
16 | });
17 | });
18 |
19 | it('generates the component structure in a subdirectory', (done) => {
20 | const prefix = 'subdir';
21 |
22 | componentGenerator.run('custom', { prefix }, () => {
23 | const files = componentFileExpectations('custom', `${prefix}/`);
24 | const i18nFiles = componentI18nFileExpectations('custom', `${prefix}/`);
25 |
26 | assert.file(files);
27 | assert.file(i18nFiles);
28 |
29 | done();
30 | });
31 | });
32 |
33 | it('excludes i18n when told so', (done) => {
34 | componentGenerator.run('custom', null, () => {
35 | assert.file(componentFileExpectations('custom'));
36 | assert.noFile(componentI18nFileExpectations('custom'));
37 |
38 | done();
39 | }, disableI18n);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/spec/directive-generator-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('yeoman-assert');
4 | const directiveFileExpectations = require('./expectations/directive');
5 | const directiveI18nFileExpectations = require('./expectations/directive-i18n');
6 | const directiveGenerator = require('./generators/directive');
7 | const disableI18n = require('./utils/disable-i18n');
8 |
9 | describe('Directive generator', () => {
10 | it('generates the directive structure', (done) => {
11 | directiveGenerator.run('custom', null, () => {
12 | assert.file(directiveFileExpectations('custom'));
13 | assert.file(directiveI18nFileExpectations('custom'));
14 |
15 | done();
16 | });
17 | });
18 |
19 | it('generates the directive structure in a subdirectory', (done) => {
20 | const prefix = 'subdir';
21 |
22 | directiveGenerator.run('custom', { prefix }, () => {
23 | const files = directiveFileExpectations('custom', `${prefix}/`);
24 | const i18nFiles = directiveI18nFileExpectations('custom', `${prefix}/`);
25 |
26 | assert.file(files);
27 | assert.file(i18nFiles);
28 |
29 | done();
30 | });
31 | });
32 |
33 | it('excludes i18n when told so', (done) => {
34 | directiveGenerator.run('custom', null, () => {
35 | assert.file(directiveFileExpectations('custom'));
36 | assert.noFile(directiveI18nFileExpectations('custom'));
37 |
38 | done();
39 | }, disableI18n);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/state-utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('./extended-lodash');
4 |
5 | module.exports = {
6 | determineParentComponent(stateName) {
7 | const normalizedStateName = this.normalizeStateName(stateName);
8 |
9 | if (normalizedStateName.indexOf('.') === -1) {
10 | return 'application';
11 | }
12 |
13 | return this.stateToComponentName(
14 | normalizedStateName.slice(0, normalizedStateName.lastIndexOf('.'))
15 | );
16 | },
17 |
18 | normalizeStateName(stateName) {
19 | let normalizedStateName = stateName;
20 |
21 | if (normalizedStateName.indexOf('app.') === 0) {
22 | normalizedStateName = normalizedStateName.slice(4);
23 | }
24 |
25 | return normalizedStateName
26 | .split('.')
27 | .map((part) => _.slugify(_.humanize(part)))
28 | .join('.');
29 | },
30 |
31 | normalizeUrl(stateName, url) {
32 | const leadingSlashRequired = stateName.indexOf('.') > -1;
33 | const hasLeadingSlash = url[0] === '/';
34 | let normalizedUrl = url;
35 |
36 | if (leadingSlashRequired && !hasLeadingSlash) {
37 | normalizedUrl = `/${normalizedUrl}`;
38 | } else if (!leadingSlashRequired && hasLeadingSlash) {
39 | normalizedUrl = normalizedUrl.slice(1);
40 | }
41 |
42 | if (normalizedUrl.slice(-1) === '/') {
43 | normalizedUrl = normalizedUrl.slice(0, -1);
44 | }
45 |
46 | return normalizedUrl;
47 | },
48 |
49 | stateToComponentName(stateName) {
50 | return `${stateName.replace(/\./g, '-')}-state`;
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/base.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Generator = require('yeoman-generator');
4 | const _ = require('./extended-lodash');
5 |
6 | class GeneratorBase extends Generator {
7 | getRootPath() {
8 | if (!this.hasOwnProperty('_rootPath')) {
9 | this._rootPath = this.config.get('root');
10 | }
11 |
12 | return this._rootPath;
13 | }
14 |
15 | setRootPath(value) {
16 | this._rootPath = value;
17 | }
18 |
19 | _componentDestinationPath() {
20 | let dest = ['src', 'components'];
21 |
22 | if (this.options.prefix) {
23 | dest.push(this.options.prefix);
24 | }
25 |
26 | dest = dest.concat(Array.prototype.slice.apply(arguments));
27 |
28 | return this.rootedDestinationPath.apply(this, dest);
29 | }
30 |
31 | _copyFile(componentName, src, dest, extension, context) {
32 | this.fs.copyTpl(
33 | this.templatePath(src + extension),
34 | this._componentDestinationPath(componentName, dest + extension),
35 | context
36 | );
37 | }
38 |
39 | _createContext() {
40 | return _.merge({
41 | windows: process.platform === 'win32',
42 | darwin: process.platform === 'darwin',
43 | _
44 | }, this.config.getAll());
45 | }
46 |
47 | rootedDestinationPath() {
48 | const dest = Array.prototype.slice.apply(arguments);
49 |
50 | if (this.getRootPath()) {
51 | dest.unshift(this.getRootPath());
52 | }
53 |
54 | return this.destinationPath.apply(this, dest);
55 | }
56 |
57 | _enablePrefix() {
58 | this.option('prefix', {
59 | desc: 'Write component files to a subdirectory.',
60 | type: String,
61 | required: false
62 | });
63 | }
64 |
65 | _requireName() {
66 | this.argument('name', {
67 | type: String,
68 | required: true
69 | });
70 | }
71 | }
72 |
73 | module.exports = GeneratorBase;
74 |
--------------------------------------------------------------------------------
/spec/expectations/app.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return [
3 | '.editorconfig',
4 | '.eslintrc.json',
5 | '.gitattributes',
6 | '.gitignore',
7 | '.htmlhintrc',
8 | 'config',
9 | 'config/gulp.js',
10 | 'config/karma.js',
11 | 'config/protractor.js',
12 | 'config/system.js',
13 | 'favicon.ico',
14 | 'gulpfile.js',
15 | 'gulp-tasks',
16 | 'gulp-tasks/build.js',
17 | 'gulp-tasks/bundle.js',
18 | 'gulp-tasks/compile-source.js',
19 | 'gulp-tasks/compile-stylesheets.js',
20 | 'gulp-tasks/copy-static.js',
21 | 'gulp-tasks/default.js',
22 | 'gulp-tasks/dist.js',
23 | 'gulp-tasks/eslint.js',
24 | 'gulp-tasks/htmlhint.js',
25 | 'gulp-tasks/notify-recompiled.js',
26 | 'gulp-tasks/serve.js',
27 | 'gulp-tasks/test-e2e.js',
28 | 'gulp-tasks/test.js',
29 | 'gulp-tasks/watch.js',
30 | 'gulp-tasks/webdriver-standalone.js',
31 | 'gulp-tasks/webdriver-update.js',
32 | 'index.html',
33 | 'package.json',
34 | 'src',
35 | 'src/components',
36 | 'src/components/application',
37 | 'src/components/application/application-controller.js',
38 | 'src/components/application/application.html',
39 | 'src/components/application/application-route.js',
40 | 'src/components/application/application-spec.js',
41 | 'src/components/application/application-test.js',
42 | 'src/components/application/config',
43 | 'src/components/application/config/constants.json',
44 | 'src/components/application/config/error-handling.js',
45 | 'src/components/application/config/routing.js',
46 | 'src/components/application/config/states.json',
47 | 'src/components/application/index.js',
48 | 'src/components/application/stylesheets',
49 | 'src/components/application/stylesheets/application.scss',
50 | 'src/index.js'
51 | ];
52 | };
53 |
--------------------------------------------------------------------------------
/spec/generator-spec.js:
--------------------------------------------------------------------------------
1 | // "strict": [2, "never"]
2 | 'use strict';
3 |
4 | const appGenerator = require('./generators/app');
5 | const childProcess = require('child_process');
6 | const EslintCliEngine = require('eslint').CLIEngine;
7 | const fs = require('fs-extra');
8 | const npmCheck = require('npm-check');
9 | const semver = require('semver');
10 |
11 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1200000;
12 |
13 | describe('Overall generator', () => {
14 | it('generates code which passes all ESLint specs', (done) => {
15 | appGenerator.run(null, { i18n: true, bootstrapJs: true, bootstrapCss: true }, () => {
16 | const processConfig = {
17 | cwd: appGenerator.testDirectory,
18 | stdio: ['ignore', 'ignore', 'pipe']
19 | };
20 |
21 | childProcess.execSync('yo angular-lazy:component custom', processConfig);
22 | childProcess.execSync('yo angular-lazy:directive custom', processConfig);
23 | childProcess.execSync('yo angular-lazy:state custom --force', processConfig);
24 |
25 | const cli = new EslintCliEngine({
26 | cwd: appGenerator.testDirectory
27 | });
28 |
29 | const report = cli.executeOnFiles(['.']);
30 |
31 | expect(report.errorCount).toEqual(0);
32 | expect(report.warningCount).toEqual(0);
33 |
34 | done();
35 | });
36 | });
37 |
38 | it('has no outdated dependencies', (done) => {
39 | appGenerator.run(null, { i18n: true, bootstrapJs: true, bootstrapCss: true }, () => {
40 | childProcess.execSync('npm install --silent --yes', {
41 | cwd: appGenerator.testDirectory
42 | });
43 |
44 | npmCheck({ skipUnused: true, cwd: appGenerator.testDirectory }).then((report) => {
45 | const packages = report.get('packages');
46 | const outdated = packages.filter((p) => semver.lt(p.installed, p.latest));
47 |
48 | expect(outdated).toEqual([]);
49 |
50 | done();
51 | });
52 | });
53 | });
54 |
55 | afterEach((done) => fs.emptyDir(appGenerator.testDirectory, () => done()));
56 | });
57 |
--------------------------------------------------------------------------------
/directive/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Base = require('../base');
4 | const _ = require('../extended-lodash');
5 |
6 | class DirectiveGenerator extends Base {
7 | constructor(args, options) {
8 | super(args, options);
9 |
10 | this._requireName();
11 | this._enablePrefix();
12 | }
13 |
14 | get writing() {
15 | return {
16 | component() {
17 | const context = this._createContext();
18 |
19 | this._copyFile(context.componentName, 'controller', context.controllerFileName, '.js', context);
20 | this._copyFile(context.componentName, 'directive', context.directiveFileName, '.js', context);
21 | this._copyFile(context.componentName, 'index', 'index', '.js', context);
22 | this._copyFile(context.componentName, 'spec', `${context.componentName}-spec`, '.js', context);
23 | this._copyFile(context.componentName, 'test', `${context.componentName}-test`, '.js', context);
24 | },
25 |
26 | i18n() {
27 | if (!this.config.get('i18n')) {
28 | return;
29 | }
30 |
31 | const context = this._createContext();
32 |
33 | this._copyFile(context.componentName, 'translations', 'i18n/translations', '.js', context);
34 | context.locales.forEach((locale) => {
35 | context.locale = locale;
36 | this._copyFile(context.componentName, 'language', `i18n/${_.slugify(locale)}`, '.js', context);
37 | });
38 | }
39 | };
40 | }
41 |
42 | _createContext() {
43 | const baseContext = super._createContext();
44 | const componentName = `${_.slugify(_.humanize(this.options.name))}-directive`;
45 |
46 | return _.merge({
47 | controllerName: `${_.classify(componentName)}Controller`,
48 | controllerFileName: `${componentName}-controller`,
49 | controllerInstanceName: _.camelize(componentName),
50 | directiveName: _.camelize(componentName),
51 | directiveFileName: componentName,
52 | attributeName: _.slugify(_.humanize(this.options.name)),
53 | componentName
54 | }, baseContext);
55 | }
56 | }
57 |
58 | module.exports = DirectiveGenerator;
59 |
--------------------------------------------------------------------------------
/component/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Base = require('../base');
4 | const _ = require('../extended-lodash');
5 |
6 | class ComponentGenerator extends Base {
7 | constructor(args, options) {
8 | super(args, options);
9 |
10 | this._requireName();
11 | this._enablePrefix();
12 | }
13 |
14 | get writing() {
15 | return {
16 | component() {
17 | const context = this._createContext();
18 |
19 | this._copyFile(context.tagName, 'controller', context.controllerFileName, '.js', context);
20 | this._copyFile(context.tagName, 'component', context.componentFileName, '.js', context);
21 | this._copyFile(context.tagName, 'index', 'index', '.js', context);
22 | this._copyFile(context.tagName, 'spec', `${context.tagName}-component-spec`, '.js', context);
23 | this._copyFile(context.tagName, 'test', `${context.tagName}-component-test`, '.js', context);
24 | this._copyFile(context.tagName, 'stylesheet', `${context.tagName}-component`, '.scss', context);
25 | this._copyFile(context.tagName, 'template', `${context.tagName}-component`, '.html', context);
26 | },
27 |
28 | i18n() {
29 | if (!this.config.get('i18n')) {
30 | return;
31 | }
32 |
33 | const context = this._createContext();
34 |
35 | this._copyFile(context.tagName, 'translations', 'i18n/translations', '.js', context);
36 | context.locales.forEach((locale) => {
37 | context.locale = locale;
38 | this._copyFile(context.tagName, 'language', `i18n/${_.slugify(locale)}`, '.js', context);
39 | });
40 | }
41 | };
42 | }
43 |
44 | _createContext() {
45 | const baseContext = super._createContext();
46 | const tagName = _.slugify(_.humanize(this.options.name));
47 |
48 | return _.merge({
49 | controllerName: `${_.classify(tagName)}ComponentController`,
50 | controllerFileName: `${tagName}-component-controller`,
51 | componentName: `${_.camelize(tagName)}Component`,
52 | componentFileName: `${tagName}-component`,
53 | tagName
54 | }, baseContext);
55 | }
56 | }
57 |
58 | module.exports = ComponentGenerator;
59 |
--------------------------------------------------------------------------------
/app/templates/core/config/karma.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = ['Chrome', 'Firefox'];
4 |
5 | if (process.platform === 'win32') {
6 | browsers.push('IE');
7 | } else if (process.platform === 'darwin') {
8 | browsers.push('Safari');
9 | }
10 |
11 | module.exports = function (config) {
12 | config.set({
13 | // base path that will be used to resolve all patterns (eg. files, exclude)
14 | basePath: `${__dirname}/../`,
15 |
16 | // frameworks to use
17 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
18 | frameworks: ['jspm', 'jasmine'],
19 |
20 | // list of files / patterns to load in the browser
21 | files: [],
22 |
23 | jspm: {
24 | baseURL: '/base',
25 | config: 'config/system.js',
26 | loadFiles: [
27 | 'build/**/*-spec.js'
28 | ],
29 | serveFiles: [
30 | 'build/**/!(*-spec).js',
31 | 'build/**/*.css',
32 | 'build/**/*.json',
33 | 'build/**/*.html',
34 | 'jspm_packages/**/*.js',
35 | 'jspm_packages/**/*.css'
36 | ]
37 | },
38 |
39 | proxies: {
40 | '/base/components/': '/base/build/components/',
41 | '/base/build/build/': '/base/build/'
42 | },
43 |
44 |
45 | // list of files to exclude
46 | exclude: [],
47 |
48 | // preprocess matching files before serving them to the browser
49 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
50 | preprocessors: {
51 | 'build/**/!(*-spec).js': ['coverage']
52 | },
53 |
54 | // test results reporter to use
55 | // possible values: 'dots', 'progress'
56 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
57 | reporters: ['progress', 'coverage'],
58 |
59 | // web server port
60 | port: 9876,
61 |
62 | // enable / disable colors in the output (reporters and logs)
63 | colors: true,
64 |
65 | // level of logging
66 | // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
67 | logLevel: config.LOG_INFO,
68 |
69 | // enable / disable watching file and executing tests whenever any file changes
70 | autoWatch: true,
71 |
72 | // start these browsers
73 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
74 | browsers,
75 |
76 | // Continuous Integration mode
77 | // if true, Karma captures browsers, runs the tests and exits
78 | singleRun: false,
79 |
80 | coverageReporter: {
81 | type: 'html',
82 | dir: `${__dirname}/../test-coverage`
83 | }
84 | });
85 | };
86 |
--------------------------------------------------------------------------------
/app/templates/core/_package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "<%= _.slugify(_.humanize(appName)) %>",
3 | "version": "0.0.1",
4 | "jspm": {
5 | "directories": {
6 | "lib": "build"
7 | },
8 | "configFile": "config/system.js",
9 | "dependencies": {
10 | "angular": "github:angular/bower-angular@~1.6.0",
11 | "angular-lazy": "github:matoilic/angular-lazy@^0.2.2",<% if(i18n) { %>
12 | "angular-translate": "github:angular-translate/bower-angular-translate@~2.13.0",<% } %><% if(bootstrapJs) { %>
13 | "angular-ui-bootstrap": "npm:angular-ui-bootstrap@~2.2.0",<% } %>
14 | "angular-ui-router": "github:angular-ui/ui-router@^0.3.1",
15 | "css": "github:systemjs/plugin-css@^0.1.22",
16 | "json": "github:systemjs/plugin-json@^0.1.2",
17 | "oclazyload": "github:ocombe/oclazyload@^1.0.9",
18 | "text": "github:systemjs/plugin-text@^0.0.8",
19 | "ui-router-extras": "github:christopherthielen/ui-router-extras@^0.1.1"
20 | },
21 | "devDependencies": {
22 | "angular-mocks": "github:angular/bower-angular-mocks@~1.6.0",
23 | "babel": "npm:babel-core@^5.8.24",
24 | "babel-runtime": "npm:babel-runtime@^5.8.20",
25 | "clean-css": "npm:clean-css@^3.4.19",
26 | "core-js": "npm:core-js@^1.1.4"
27 | }
28 | },
29 | "devDependencies": {
30 | "angular-lazy-bundler": "^0.2.3",
31 | "babel": "^6.5.0",
32 | "babel-core": "^6.5.0",
33 | "babel-preset-es2015": "^6.5.0",
34 | "babel-preset-stage-2": "^6.5.0",
35 | "babel-eslint": "^7.1.0",<% if(bootstrapCss) { %>
36 | "bootstrap-sass": "^3.3.6",<% } %>
37 | "eslint-config-angular-lazy": "^0.1.1",
38 | "generator-angular-lazy": "~0.6.7",
39 | "glob": "^7.0.3",
40 | "gulp": "^3.9.0",
41 | "gulp-autoprefixer": "^3.1.0",
42 | "gulp-babel": "^6.1.0",
43 | "gulp-connect": "^5.0.0",
44 | "gulp-eslint": "^3.0.0",
45 | "gulp-htmlhint": "^0.3.0",
46 | "gulp-notify": "^2.2.0",
47 | "gulp-plumber": "^1.0.0",
48 | "gulp-protractor": "^3.0.0",
49 | "gulp-sass": "^3.0.0",
50 | "gulp-sourcemaps": "^2.2.0",
51 | "gulp-sync": "^0.1.4",
52 | "htmlhint": "^0.9.7",
53 | "jasmine-core": "^2.4.0",
54 | "jspm": "^0.16.27",
55 | "karma": "^1.1.0",
56 | "karma-chrome-launcher": "^2.0.0",
57 | "karma-coverage": "^1.0.0",
58 | "karma-firefox-launcher": "^1.0.0",<% if(windows) { %>
59 | "karma-ie-launcher": "^1.0.0",<% } %>
60 | "karma-jasmine": "^1.0.2",
61 | "karma-jspm": "^2.0.1",<% if(darwin) { %>
62 | "karma-safari-launcher": "^1.0.0",<% } %>
63 | "protractor": "^4.0.0"
64 | },
65 | "dependencies": {
66 |
67 | },
68 | "scripts": {
69 | "setup": "npm install && jspm install",
70 | "build": "gulp build",
71 | "dist": "gulp dist",
72 | "test": "gulp test",
73 | "test-e2e": "gulp test-e2e"
74 | },
75 | "engines": {
76 | "node": ">=4.2.0",
77 | "npm": ">=3.0.0"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/spec/state-generator-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('yeoman-assert');
4 | const fs = require('fs-extra');
5 | const stateFileExpectations = require('./expectations/state');
6 | const stateI18nFileExpectations = require('./expectations/state-i18n');
7 | const stateGenerator = require('./generators/state');
8 | const disableI18n = require('./utils/disable-i18n');
9 |
10 | describe('State generator', () => {
11 | const parentStateName = 'mock-parent';
12 | const mockSimpleParent = () => {
13 | fs.outputFileSync(`src/components/${parentStateName}-state/${parentStateName}-state.html`, `
14 |
15 |
16 |
17 | `);
18 | };
19 | const mockMultiViewParent = () => {
20 | fs.outputFileSync(`src/components/${parentStateName}-state/${parentStateName}-state.html`, `
21 |
25 | `);
26 | };
27 |
28 | it('generates the state structure', (done) => {
29 | stateGenerator.run('custom', null, () => {
30 | assert.file(stateFileExpectations('custom'));
31 | assert.file(stateI18nFileExpectations('custom'));
32 |
33 | done();
34 | });
35 | });
36 |
37 | it('updates the states configuration', (done) => {
38 | stateGenerator.run('custom', null, () => {
39 | const statesFile = fs.readFileSync(stateGenerator.statesFile);
40 | const states = JSON.parse(statesFile.toString());
41 |
42 | expect(states[0].name).toEqual('app.custom');
43 |
44 | done();
45 | });
46 | });
47 |
48 | it('generates the state structure in a subdirectory', (done) => {
49 | const prefix = 'subdir';
50 |
51 | stateGenerator.run('custom', { prefix }, () => {
52 | const files = stateFileExpectations('custom', `${prefix}/`);
53 | const i18nFiles = stateI18nFileExpectations('custom', `${prefix}/`);
54 |
55 | const statesFile = fs.readFileSync(stateGenerator.statesFile);
56 | const states = JSON.parse(statesFile.toString());
57 | expect(states[0].src).toEqual(`components/${prefix}/custom-state/index`);
58 |
59 | assert.file(files);
60 | assert.file(i18nFiles);
61 |
62 | done();
63 | });
64 | });
65 |
66 | it('excludes i18n when told so', (done) => {
67 | stateGenerator.run('custom', null, () => {
68 | assert.file(stateFileExpectations('custom'));
69 | assert.noFile(stateI18nFileExpectations('custom'));
70 |
71 | done();
72 | }, disableI18n);
73 | });
74 |
75 | it('accepts a custom url option', (done) => {
76 | const url = 'custom-url/:id';
77 |
78 | stateGenerator.run('custom', { url }, () => {
79 | const routeConfig = fs.readFileSync('src/components/custom-state/custom-route.js');
80 |
81 | expect(routeConfig).toMatch(`'${url}'`);
82 |
83 | done();
84 | });
85 | });
86 |
87 | it('accepts a custom parent route', (done) => {
88 | stateGenerator.run(`${parentStateName}.custom`, null, () => {
89 | const routeConfig = fs
90 | .readFileSync(`src/components/${parentStateName}-custom-state/${parentStateName}-custom-route.js`);
91 |
92 | expect(routeConfig).toMatch(`'app.${parentStateName}.custom'`);
93 |
94 | done();
95 | }, mockSimpleParent);
96 | });
97 |
98 | it('handles parents with multiple views properly', (done) => {
99 | const target = 'view2';
100 |
101 | stateGenerator.run(`${parentStateName}.custom`, null, () => {
102 | const routeConfig = fs
103 | .readFileSync(`src/components/${parentStateName}-custom-state/${parentStateName}-custom-route.js`);
104 |
105 | expect(routeConfig).toMatch(`views: {\n ${target}: {`);
106 |
107 | done();
108 | }, mockMultiViewParent, { target });
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/spec/app-generator-spec.js:
--------------------------------------------------------------------------------
1 | // "strict": [2, "never"]
2 | 'use strict';
3 |
4 | const assert = require('yeoman-assert');
5 | const path = require('path');
6 | const fs = require('fs-extra');
7 | const appGenerator = require('./generators/app');
8 | const appFileExpectations = require('./expectations/app');
9 | const appI18nFileExpectations = require('./expectations/app-i18n');
10 | const stateFileExpectations = require('./expectations/state');
11 |
12 | describe('App generator', () => {
13 | it('generates the application structure', (done) => {
14 | appGenerator.run(null, null, () => {
15 | assert.file(appFileExpectations());
16 | assert.file(appI18nFileExpectations());
17 | assert.file(stateFileExpectations(appGenerator.defaultOptions.indexRouteName));
18 |
19 | const statesFile = fs.readFileSync('src/components/application/config/states.json');
20 | const states = JSON.parse(statesFile.toString());
21 |
22 | expect(states[1].name).toEqual(`app.${appGenerator.defaultOptions.indexRouteName}`);
23 |
24 | done();
25 | });
26 | });
27 |
28 | it('generates the application structure with a root option', (done) => {
29 | const root = 'custom-root';
30 |
31 | appGenerator.run({ root }, null, () => {
32 | const coreFiles = appFileExpectations().map((p) => path.join(root, p));
33 | const i18nFiles = appI18nFileExpectations().map((p) => path.join(root, p));
34 | const stateFiles = stateFileExpectations(appGenerator.defaultOptions.indexRouteName)
35 | .map((p) => path.join(root, p));
36 |
37 | assert.file(coreFiles);
38 | assert.file(i18nFiles);
39 | assert.file(stateFiles);
40 |
41 | done();
42 | });
43 | });
44 |
45 | it('includes i18n when told so', (done) => {
46 | appGenerator.run(null, { i18n: true }, () => {
47 | assert.file(appI18nFileExpectations());
48 |
49 | const packageFile = fs.readFileSync('package.json');
50 | const packageDefinition = JSON.parse(packageFile.toString());
51 |
52 | expect(packageDefinition.jspm.dependencies['angular-translate']).toBeTruthy();
53 |
54 | done();
55 | });
56 | });
57 |
58 | it('excludes i18n when told so', (done) => {
59 | appGenerator.run(null, { i18n: false }, () => {
60 | assert.file(appFileExpectations());
61 | assert.noFile(appI18nFileExpectations());
62 |
63 | const packageFile = fs.readFileSync('package.json');
64 | const packageDefinition = JSON.parse(packageFile.toString());
65 |
66 | expect(packageDefinition.jspm.dependencies['angular-translate']).not.toBeTruthy();
67 |
68 | done();
69 | });
70 | });
71 |
72 | it('includes angular-ui-bootstrap when told so', (done) => {
73 | appGenerator.run(null, { bootstrapJs: true }, () => {
74 | const packageFile = fs.readFileSync('package.json');
75 | const packageDefinition = JSON.parse(packageFile.toString());
76 |
77 | expect(packageDefinition.jspm.dependencies['angular-ui-bootstrap']).toBeTruthy();
78 |
79 | done();
80 | });
81 | });
82 |
83 | it('excludes angular-ui-bootstrap when told so', (done) => {
84 | appGenerator.run(null, { bootstrapJs: false }, () => {
85 | const packageFile = fs.readFileSync('package.json');
86 | const packageDefinition = JSON.parse(packageFile.toString());
87 |
88 | expect(packageDefinition.jspm.dependencies['angular-ui-bootstrap']).not.toBeTruthy();
89 |
90 | done();
91 | });
92 | });
93 |
94 | it('includes bootstrap-sass when told so', (done) => {
95 | appGenerator.run(null, { bootstrapCss: true }, () => {
96 | const packageFile = fs.readFileSync('package.json');
97 | const packageDefinition = JSON.parse(packageFile.toString());
98 |
99 | expect(packageDefinition.devDependencies['bootstrap-sass']).toBeTruthy();
100 |
101 | done();
102 | });
103 | });
104 |
105 | it('excludes bootstrap-sass when told so', (done) => {
106 | appGenerator.run(null, { bootstrapCss: false }, () => {
107 | const packageFile = fs.readFileSync('package.json');
108 | const packageDefinition = JSON.parse(packageFile.toString());
109 |
110 | expect(packageDefinition.devDependencies['bootstrap-sass']).not.toBeTruthy();
111 |
112 | done();
113 | });
114 | });
115 |
116 | it('saves the passed options', (done) => {
117 | appGenerator.run(null, null, () => {
118 | const yoFile = fs.readFileSync('.yo-rc.json');
119 | const yoConfig = JSON.parse(yoFile.toString())['generator-angular-lazy'];
120 |
121 | expect(yoConfig).toEqual(appGenerator.defaultOptions);
122 |
123 | done();
124 | });
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/state/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Base = require('../base');
4 | const _ = require('../extended-lodash');
5 | const fs = require('fs');
6 | const stateUtils = require('../state-utils');
7 |
8 | class StateGenerator extends Base {
9 | constructor(args, options) {
10 | super(args, options);
11 |
12 | this._requireName();
13 | this._enablePrefix();
14 |
15 | this.option('url', {
16 | desc: 'Url relative to the parent state. Same as state name by default.',
17 | type: String,
18 | required: false
19 | });
20 |
21 | this.option('target', {
22 | desc: 'Name of the target ui-view within the parent state.',
23 | type: String,
24 | required: false
25 | });
26 | }
27 |
28 | get prompting() {
29 | return {
30 | targetView() {
31 | const targetComponentName = stateUtils.determineParentComponent(this.options.name);
32 | const targetTemplate = this._componentDestinationPath(
33 | targetComponentName,
34 | `${targetComponentName}.html`
35 | );
36 |
37 | if (this.options.target || !fs.existsSync(targetTemplate)) {
38 | return Promise.resolve();
39 | }
40 |
41 | const templateContent = fs.readFileSync(targetTemplate);
42 | const views = [];
43 | const viewMatcher = /(?:\sui-view="([^"]+)"|)/g;
44 | let view = viewMatcher.exec(templateContent);
45 |
46 | while (view) {
47 | views.push(view[1] || view[2]);
48 | view = viewMatcher.exec(templateContent);
49 | }
50 |
51 | if (views.length < 2) {
52 | if (views.length === 1) {
53 | this.options.target = views[0];
54 | }
55 |
56 | return Promise.resolve();
57 | }
58 |
59 | return this
60 | .prompt([{
61 | type: 'list',
62 | name: 'target',
63 | message: 'Which is the target ui-view?',
64 | choices: views
65 | }])
66 | .then((answers) => {
67 | this.options.target = answers.target;
68 |
69 | return answers;
70 | });
71 | }
72 | };
73 | }
74 |
75 | get writing() {
76 | return {
77 | state() {
78 | const context = this._createContext();
79 |
80 | this._copyFile(context.componentName, 'controller', context.controllerFileName, '.js', context);
81 | this._copyFile(context.componentName, 'index', 'index', '.js', context);
82 | this._copyFile(context.componentName, 'route', context.routeFileName, '.js', context);
83 | this._copyFile(context.componentName, 'spec', `${context.componentName}-spec`, '.js', context);
84 | this._copyFile(context.componentName, 'test', `${context.componentName}-test`, '.js', context);
85 | this._copyFile(context.componentName, 'stylesheet', context.componentName, '.scss', context);
86 | this._copyFile(context.componentName, 'template', context.componentName, '.html', context);
87 |
88 | const routesFile = this.rootedDestinationPath('src/components/application/config/states.json');
89 | const routes = this.fs.readJSON(routesFile);
90 |
91 | const srcPath = ['components'];
92 | if (this.options.prefix) {
93 | srcPath.push(this.options.prefix);
94 | }
95 | srcPath.push(context.componentName, 'index');
96 |
97 | routes.push({
98 | name: `app.${context.stateName}`,
99 | url: context.url,
100 | type: 'load',
101 | src: srcPath.join('/')
102 | });
103 | this.fs.writeJSON(routesFile, routes);
104 | },
105 |
106 | i18n() {
107 | if (!this.config.get('i18n')) {
108 | return;
109 | }
110 |
111 | const context = this._createContext();
112 |
113 | this._copyFile(context.componentName, 'translations', 'i18n/translations', '.js', context);
114 | context.locales.forEach((locale) => {
115 | context.locale = locale;
116 | this._copyFile(context.componentName, 'language', `i18n/${_.slugify(locale)}`, '.js', context);
117 | });
118 | }
119 | };
120 | }
121 |
122 | _createContext() {
123 | const baseContext = super._createContext();
124 | const stateName = stateUtils.normalizeStateName(this.options.name);
125 | const url = stateUtils.normalizeUrl(stateName, this.options.url || stateName.split('.').pop());
126 | const componentName = stateUtils.stateToComponentName(stateName);
127 | const routeFileName = `${componentName.slice(0, -6)}-route`;
128 | let target = this.options.target;
129 |
130 | if (!target && stateName.indexOf('.') === -1) {
131 | target = 'application';
132 | }
133 |
134 | return _.merge({
135 | controllerName: `${_.classify(componentName)}Controller`,
136 | controllerFileName: `${componentName}-controller`,
137 | controllerInstanceName: _.camelize(componentName),
138 | routeName: _.camelize(routeFileName),
139 | templateName: componentName,
140 | componentName,
141 | stateName,
142 | url,
143 | target,
144 | routeFileName
145 | }, baseContext);
146 | }
147 | }
148 |
149 | module.exports = StateGenerator;
150 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Base = require('../base');
4 | const glob = require('glob');
5 | const path = require('path');
6 | const _ = require('../extended-lodash');
7 | const stateUtils = require('../state-utils');
8 |
9 | const whenI18nActive = (answers) => answers.i18n;
10 |
11 | class ApplicationGenerator extends Base {
12 | constructor(args, options) {
13 | super(args, options);
14 |
15 | this.option('skip-install', {
16 | desc: 'Do not install dependencies',
17 | type: Boolean,
18 | defaults: false
19 | });
20 |
21 | this.option('root', {
22 | desc: 'Use a subfolder as root directory for the project instead of the current working directory',
23 | type: String
24 | });
25 |
26 | this.config.defaults({
27 | appName: path.basename(process.cwd()),
28 | i18n: true,
29 | bootstrapCss: false,
30 | bootstrapJs: false,
31 | indexRouteName: 'index'
32 | });
33 | }
34 |
35 | get prompting() {
36 | return {
37 | app() {
38 | const locales = this.config.get('locales') ? this.config.get('locales').join(',') : null;
39 |
40 | return this
41 | .prompt([
42 | {
43 | type: 'input',
44 | name: 'appName',
45 | message: "What's the name of the App?",
46 | default: this.config.get('appName')
47 | },
48 | {
49 | type: 'confirm',
50 | name: 'i18n',
51 | message: 'Do you want to include angular-translate for i18n?',
52 | default: this.config.get('i18n')
53 | },
54 | {
55 | type: 'input',
56 | name: 'locales',
57 | message: 'What locales do you want to support (comma separated list)?',
58 | default: locales,
59 | when: whenI18nActive,
60 | filter: (answer) => _.uniq(
61 | answer
62 | .split(',')
63 | .map((locale) => locale.trim())
64 | )
65 | },
66 | {
67 | type: 'list',
68 | name: 'defaultLocale',
69 | message: 'Which should be the default locale?',
70 | default: this.config.get('defaultLocale'),
71 | choices: (answers) => answers.locales,
72 | when: whenI18nActive
73 | },
74 | {
75 | type: 'confirm',
76 | name: 'bootstrapCss',
77 | message: 'Do you want to include the Bootstrap CSS components?',
78 | default: this.config.get('bootstrapCss')
79 | },
80 | {
81 | type: 'confirm',
82 | name: 'bootstrapJs',
83 | message: 'Do you want to include the Bootstrap JavaScript components?',
84 | default: this.config.get('bootstrapJs')
85 | },
86 | {
87 | type: 'input',
88 | name: 'indexRouteName',
89 | message: 'How should the default state be called?',
90 | default: this.config.get('indexRouteName')
91 | }
92 | ])
93 | .then((answers) => {
94 | const root = this.config.get('root') || this.options.root;
95 |
96 | this.setRootPath(root);
97 |
98 | this.context = _.merge(this._createContext(), answers, { root });
99 |
100 | const index = stateUtils.normalizeStateName(answers.indexRouteName);
101 | this.context.indexUrl = stateUtils.normalizeUrl(index, index);
102 | this.context.indexComponent = stateUtils.stateToComponentName(index);
103 |
104 | return answers;
105 | });
106 | }
107 | };
108 | }
109 |
110 | get configuring() {
111 | return {
112 | save() {
113 | this.config.set('appName', this.context.appName);
114 | this.config.set('i18n', this.context.i18n);
115 | this.config.set('locales', this.context.locales);
116 | this.config.set('defaultLocale', this.context.defaultLocale);
117 | this.config.set('indexRouteName', this.context.indexRouteName);
118 | this.config.set('bootstrapCss', this.context.bootstrapCss);
119 | this.config.set('bootstrapJs', this.context.bootstrapJs);
120 | if (this.context.root) {
121 | this.config.set('root', this.context.root);
122 | }
123 |
124 | this.config.save();
125 | }
126 | };
127 | }
128 |
129 | get writing() {
130 | return {
131 | core() {
132 | const templatePathLength = this.templatePath().length + 1;
133 |
134 | glob
135 | .sync(this.templatePath('core/**/*'), { nodir: true })
136 | .map((filepath) => filepath.slice(templatePathLength))
137 | .forEach((filepath) => {
138 | const dirname = path.dirname(filepath).slice(5);
139 | const srcFilename = path.basename(filepath);
140 | const destFilename = srcFilename[0] === '_' ? srcFilename.slice(1) : srcFilename;
141 |
142 | this.fs.copyTpl(
143 | this.templatePath(filepath),
144 | this.rootedDestinationPath(path.join(dirname, destFilename)),
145 | this.context
146 | );
147 | });
148 | },
149 |
150 | i18n() {
151 | if (!this.context.i18n) {
152 | return;
153 | }
154 |
155 | this.fs.copyTpl(
156 | this.templatePath('i18n/translations.js'),
157 | this._componentDestinationPath('application', 'i18n', 'translations.js'),
158 | this.context
159 | );
160 |
161 | this.fs.copyTpl(
162 | this.templatePath('i18n/default-locale-config.js'),
163 | this._componentDestinationPath('application', 'config', 'default-locale.js'),
164 | { defaultLocale: this.context.defaultLocale }
165 | );
166 |
167 | this.context.locales.forEach((locale) => {
168 | this.fs.copyTpl(
169 | this.templatePath('i18n/language.js'),
170 | this._componentDestinationPath('application', 'i18n', `${_.slugify(locale)}.js`),
171 | { _, locale }
172 | );
173 | });
174 | },
175 |
176 | indexState() {
177 | this.composeWith(path.join(__dirname, '..', 'state'), {
178 | arguments: [this.context.indexRouteName],
179 | force: true
180 | });
181 | }
182 | };
183 | }
184 |
185 | get install() {
186 | return {
187 | npm() {
188 | if (!this.options['skip-install']) {
189 | this.runInstall('npm', null, {
190 | cwd: this.getRootPath() || '.'
191 | });
192 | }
193 | },
194 |
195 | jspm() {
196 | if (!this.options['skip-install']) {
197 | this.env.runLoop.add('install', (done) => {
198 | this.emit('jspmInstall');
199 | this
200 | .spawnCommand('jspm', ['install'], {
201 | cwd: this.getRootPath() || '.',
202 | stdio: 'inherit'
203 | })
204 | .on('exit', () => {
205 | this.emit('jspmInstall:end');
206 | done();
207 | });
208 | }, { run: false });
209 | }
210 | }
211 | };
212 | }
213 | }
214 |
215 | module.exports = ApplicationGenerator;
216 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Yeoman generator for AngularJS projects
2 |
3 | [](https://travis-ci.org/matoilic/generator-angular-lazy)
4 | [](https://ci.appveyor.com/project/matoilic/generator-angular-lazy/branch/master)
5 | [](https://david-dm.org/matoilic/generator-angular-lazy)
6 | [](https://david-dm.org/matoilic/generator-angular-lazy#info=devDependencies)
7 |
8 | > Opinionated Yeoman generator for creating Angular applications which lazy load components as needed at runtime.
9 |
10 | ## Table of contents
11 | - [Getting started](#getting-started)
12 | - [Structure](#structure)
13 | - [Application component](#application-component)
14 | - [State component](#state-component)
15 | - [Directive component](#directive-component)
16 | - [General component](#general-component)
17 | - [What's included](#what-s-included)
18 | - [SystemJS](#systemjs)
19 | - [JSPM](#jspm)
20 | - [AngularJS](#angularjs)
21 | - [UI Router](#ui-router)
22 | - [UI Router Extras](#ui-router-extras)
23 | - [ocLazyLoad](#oclazyload)
24 | - [Angular Lazy](#angular-lazy)
25 | - [Angular Lazy Bundler](#angular-lazy-bundler)
26 | - [Angular Translate](#angular-translate)
27 | - [Karma](#karma)
28 | - [Jasmine](#jasmine)
29 | - [Protractor](#protractor)
30 | - [SASS](#sass)
31 | - [Babel](#babel)
32 | - [Gulp](#gulp)
33 | - [Gulp tasks](#gulp-tasks)
34 | - [default](#default)
35 | - [build](#build)
36 | - [bundle](#bundle)
37 | - [serve](#serve)
38 | - [watch](#watch)
39 | - [test](#test)
40 | - [test-e2e](#test-e2e)
41 | - [compile-source](#compile-source)
42 | - [compile-stylesheets](#compile-stylesheets)
43 | - [htmlhint](#htmlhint)
44 | - [eslint](#eslint)
45 | - [Preparing for production](#preparing-for-production)
46 | - [Troubleshooting](#troubleshooting)
47 | - [Missing dependencies](#missing-dependencies)
48 | - [Incompatible Angular modules](#incompatible-angular-modules)
49 | - [Protractor and Safari](#protractor-and-safari)
50 | - [License](#license)
51 |
52 | ## Getting started
53 |
54 | To get started you'll need to install the Yeoman, JSPM, Gulp and Protractor CLI tools globally.
55 |
56 | ```bash
57 | $: npm install -g yo jspm gulp protractor
58 | ```
59 |
60 | Then, of course, you need to install the Angular Lazy generator itself.
61 |
62 | ```bash
63 | $: npm install -g generator-angular-lazy
64 | ```
65 |
66 | Now you can start using the generators described in the [structure section](#structure).
67 |
68 | ## Structure
69 | Angular Lazy follows a component based approach. Everything is a component, even the application itself. Components should be self-contained and should be easily reusable for multiple projects. Of course there will be cases where a component is very specific to a project and might not be reusable. But always have the goal of reusability in focus.
70 |
71 | ### General
72 | Each component has a `index.js` file which is the main access point of it. Components should not directly reference resources from each other, except using `index.js`. Within there the component should expose / export all of it's public API. Other common files across all components are the `*-spec.js` and `*-test.js` files. Spec-files contain the unit tests and test-files the end-to-end tests. For larger components tests can also be split across multiple files. The Karma test runner will scan the project for all `*-spec.js` files and Protractor will load all `*-test.js` files to run the end-to-end tests.
73 |
74 | #### i18n
75 | If you choose to activate `i18n` while generating the application, each component will have a `i18n` folder which contains it's translations.
76 |
77 | ### Application component
78 |
79 | > $: yo [angular-lazy](https://github.com/matoilic/generator-angular-lazy/blob/master/app/USAGE) [--skip-install] [--root=]
80 |
81 | #### Options
82 |
83 | | Option | Description | Default |
84 | | ------ | ----- | ---- |
85 | | --help, -h | Print the generator's options and usage | |
86 | | --skip-cache | Do not remember prompt answers | false |
87 | | --skip-install | Do not automatically install dependencies | false |
88 | | --root | Use a subfolder as root directory for the project instead of the current working directory | |
89 |
90 | #### Output
91 | ```text
92 | +src
93 | | +components
94 | | | +application
95 | | | | +config
96 | | | | | constants.json
97 | | | | | default-locale.js
98 | | | | | error-handling.js
99 | | | | | routing.js
100 | | | | | states.json
101 | | | | i18n
102 | | | | stylesheets
103 | | | | application.html
104 | | | | application-controller.js
105 | | | | application-route.js
106 | | | | application-spec.js
107 | | | | application-test
108 | | | | index.js
109 | | index.js
110 | ```
111 |
112 | #### constants.json
113 | As the filename suggests, this is the place where you define application wide constants. Those can be imported where necessary and are also available as injectable values within the application.
114 |
115 | #### default-locale.js
116 | In here, the default locale is configured. This file will only be present if you choose to activate `i18n` while generating the application structure.
117 |
118 | #### error-handling.js
119 | By default, this file only contains a small code piece which logs state transition errors. UI Router swallows transition error by default and we're left on our own to figure out what happened. This file can be extend with additional error handling functionality as needed, e.g. network errors.
120 |
121 | #### states.json
122 | This is where we define our lazy loaded routes. For the Future States feature from UI Router Extras work properly, we need to tell it what routes exist and where they can be loaded from.
123 |
124 | ```javascript
125 | [
126 | {
127 | "name": "app",
128 | "url": "/",
129 | "type": "load",
130 | "src": "components/application/index"
131 | },
132 | {
133 | "name": "home",
134 | "url": "home",
135 | "type": "load",
136 | "prefetch": [
137 | "components/login-form/index",
138 | "angular-ui-bootstrap"
139 | ]
140 | "src": "components/home-state/index"
141 | }
142 | ]
143 | ```
144 |
145 | The name and url properties must match those we use in the `$stateProvider.state(...)` call. If a route is not yet loaded, UI Router Extras will catch the `$stateNotFound` event and look it up in the list of states defined in `states.json`. If it finds a match it will load the specific component and then resume the state transition. The type property is a helper to distinguish state types. Abstract states must be defined here too, like the `app` state in the example above. There is only one value `load` by default. It is used by the future state provider in the [angular-lazy](https://github.com/matoilic/angular-lazy) package to know which states should be handled by the default loader. If we have states which need to be handled specially, we can introduce new types and loaders. In most cases the default loader will be sufficient. The `src` property tells the loader where to load the state component from. This is always relative to the `baseURL` configured within SystemJS. And finally, there is an optional `prefetch` property where we can define components which should be fetched right after the state has been loaded. This allows us to load parts of an application which are highly likely to be accessed by the user. By prefetching them we can reduce eventual wait times.
146 |
147 | #### routing.js
148 | This file contains the configuration for the state factory which lazy loads our code based on the definitions in `states.json`.
149 |
150 | #### application.html
151 | This is the template for our basic layout, common for all states. By default it only contains a `ui-view` element.
152 |
153 | #### application-controller.js
154 | This file contains our application controller which is accessible for all components. It's mainly used for handling data which is needed throughout the whole application, e.g. information about the currently logged in user. Be careful to not overload it. Functionality like loading the actual user data should always be within a service.
155 |
156 | #### application-route.js
157 | This file contains the application state. This is only an abstract state and each other state within our application should be a direct or indirect descendant of it. This enables us to load application wide data before any of the actual states get loaded.
158 |
159 | ### State component
160 | > $: yo [angular-lazy:state](https://github.com/matoilic/generator-angular-lazy/blob/master/state/USAGE) name [--prefix=] [--url=] [--target=]
161 |
162 | #### Options
163 |
164 | | Option | Description | Default |
165 | | ------ | ----- | ---- |
166 | | --help, -h | Print the generator's options and usage | |
167 | | --prefix | Write component files to a subdirectory | |
168 | | --url | Url relative to the parent state | Same as state name |
169 | | --target | Name of the target ui-view within the parent state | |
170 |
171 | #### Output
172 |
173 | ```text
174 | +src
175 | | +components
176 | | | +[name]-state
177 | | | | i18n
178 | | | | [name]-route.js
179 | | | | [name]-state.html
180 | | | | [name]-state.scss
181 | | | | [name]-state-controller.js
182 | | | | [name]-state-spec.js
183 | | | | [name]-state-test.js
184 | | | | index.js
185 | ```
186 |
187 | When running the state component generator it will automatically add the new state to `states.json` within the application component.
188 |
189 | #### [name]-route.js
190 | This file contains the state definition for UI Router. If you change the URL or the state name at some point in time don't forget to also update it in `states.json`. Otherwise the state will not be loaded properly when lazy loaded.
191 |
192 | #### [name]-state-controller.js
193 | This file contains the controller for the newly generated state.
194 |
195 | ### Directive component
196 | > $: yo [angular-lazy:directive](https://github.com/matoilic/generator-angular-lazy/blob/master/directive/USAGE) name [--prefix=]
197 |
198 | #### Options
199 |
200 | | Option | Description | Default |
201 | | ------ | ----- | ---- |
202 | | --help, -h | Print the generator's options and usage | |
203 | | --prefix | Write component files to a subdirectory | |
204 |
205 | #### Output
206 |
207 | ```text
208 | +src
209 | | +components
210 | | | +[name]-directive
211 | | | | i18n
212 | | | | [name]-directive-controller.js
213 | | | | [name]-directive-spec.js
214 | | | | [name]-directive-test.js
215 | | | | [name]-directive.js
216 | | | | index.js
217 | ```
218 |
219 | Since the [component provider](https://docs.angularjs.org/guide/component) introduced in 1.5 is restricted to elements this generator was introduced for the case we want to create a custom attribute. Attributes don't have templates nor should they influence the styling of the element they're applied on. Thus, no stylesheet or HTML template will be generated.
220 |
221 | ### General component
222 | > $: yo [angular-lazy:component](https://github.com/matoilic/generator-angular-lazy/blob/master/component/USAGE) name [--prefix=]
223 |
224 | #### Options
225 |
226 | | Option | Description | Default |
227 | | ------ | ----- | ---- |
228 | | --help, -h | Print the generator's options and usage | |
229 | | --prefix | Write component files to a subdirectory | |
230 |
231 | #### Output
232 |
233 | ```text
234 | +src
235 | | +components
236 | | | +[name]
237 | | | | i18n
238 | | | | [name]-component-controller.js
239 | | | | [name]-component-spec.js
240 | | | | [name]-component-test.js
241 | | | | [name]-component.html
242 | | | | [name]-component.js
243 | | | | [name]-component.scss
244 | | | | index.js
245 | ```
246 |
247 | This will generate a Angular component using the [component provider](https://docs.angularjs.org/guide/component) introduced in 1.5.
248 |
249 | ## What's included?
250 | These are the main tools and libraries the project stack relies on.
251 |
252 | ### [SystemJS](https://github.com/systemjs/systemjs)
253 | We're using the recently, in ECMAScript 2015, standardized module system. SystemJS builds up on these APIs to make it easier for us to modularize out code properly and to load those modules as they're needed. Since most browsers don't implement the module system natively SystemJS uses the [ES2015 Module Loader Polyfill](https://github.com/ModuleLoader/es6-module-loader) under the hood to close the gap.
254 |
255 | Since the ES2015 module loader system is farly new most of the existing JavaScript libraries didn't have the chance yet to migrate to the new syntax. AMD and CommonJS are still the most used systems. SystemJS implements adapters for those module systems so that we're not blocked when it comes to use popular libraries like AngularJS or Lodash which do not yet use the new import / export syntax.
256 |
257 | ### [JSPM](https://jspm.io)
258 | [NPM](https://www.npmjs.com) is a great package manager but it was initially designed to be used on the server side. There is no straight forward way to load NPM packages in the browser at runtime. JSPM eases that process and also overwrites some package.json properties for certain packages where necessary, e.g. the main file.
259 |
260 | ### [AngularJS](https://angularjs.org)
261 | If you're here then you should know what Angular is.
262 |
263 | ### [UI Router](http://angular-ui.github.io/ui-router/)
264 | Angular's integrated router has very limited capabilities, e.g. it doesn't support nested views. UI Router gives you much more flexibility and has become the de-facto standard router for Angular applications.
265 |
266 | ### [UI Router Extras](http://christopherthielen.github.io/ui-router-extras)
267 | UI Router Extras adds even more functionality to the router on top of UI Router. Most important, [Future States](http://christopherthielen.github.io/ui-router-extras/#/future) which enable us to describe, in an abstract way, what states our application has and where the code for those resides, without actually loading the JavaScript code itself. It is then lazy loaded at runtime when the uset accesses the state for the first time.
268 |
269 | ### [ocLazyLoad](https://oclazyload.readme.io)
270 | By default Angular requires us to load all application code upfront before it boots the application. That works well for smaller applications. For large scale applications this introduces long loading times an impacts the user experience negatively. ocLazyLoad allows us to add modules to Angular applications at runtime.
271 |
272 | ### [Angular Lazy](https://github.com/matoilic/angular-lazy)
273 | The Angular Lazy package glues UI Router Extras and ocLazyLoad together, so that we can easily lazy load our states. It also provides a component loader which makes it possible to load additional components at any time in the code.
274 |
275 | ### [Angular Lazy Bundler](https://github.com/matoilic/angular-lazy-bundler)
276 | You will realise, that you end up with a lot of small files when you use the angular-lazy generator. To reduce the number of network requests required to load a component we want to bundle those files together where possible.
277 |
278 | ### [Angular Translate](https://angular-translate.github.io/)
279 | If you choose to activate `i18n` while generating the application, the project will include Angular Translate to handle translations. Angular has no support for i18n and l10n, so we need to include this package.
280 |
281 | ### [Karma](https://karma-runner.github.io)
282 | Karma is a test runner created by the Angular team, specifically to ease the testing of Angular applications. It is only a test runner and not a test framework. To actually write our tests we're going to use Jasmine.
283 |
284 | ### [Jasmine](https://jasmine.github.io)
285 | Jasmine is the actual test framework we're using to write our tests. It's integrated into Karma through the `karma-jasmine` package.
286 |
287 | ### [Protractor](https://www.protractortest.org)
288 | Protractor's main focus is to ease the end-to-end testing of Angular applications. Under the hood it uses [Selenium WebDriver](http://www.seleniumhq.org/projects/webdriver) which is an established tool for automated browser testing. Like with Karma, we can also use Jasmine to write our tests which protractor should run.
289 |
290 | ### [SASS](http://sass-lang.com)
291 | Writing stylesheets in plain CSS for large applications is a pain in the ass. That's why Angular Lazy comes with SASS preconfigured as CSS preprocessor. Under the hood it uses [node-sass](https://github.com/sass/node-sass) which itself uses [libsass](http://sass-lang.com/libsass), a C implementation of SASS. We're not using the Ruby SASS implementation because it's much slower than libsass and it would require us to install Ruby next to Node.
292 |
293 | ### [Babel](http://babeljs.io/)
294 | Not all ES2015 features are yet supported across major browsers. Babel allows us to take advantage of all new language features by transpiling then into equivalent ES5 code.
295 |
296 | ### [Gulp](http://gulpjs.com)
297 | Angular Lazy uses Gulp for task automation and comes preconfigured with all [essential tasks](#gulp-tasks) to get started.
298 |
299 | ## Gulp tasks
300 |
301 | Each Gulp task sits in its own file. This makes it easier to navigate to the source of an individual task and it's clearer which task depends on which libraries. Thus, it also makes the tasks better maintainable.
302 |
303 | ### default
304 |
305 | > $: gulp
306 |
307 | Alias for [build](#build) ➔ [watch](#watch) ➔ [serve](#serve)
308 |
309 | ### build
310 |
311 | > $: gulp build
312 |
313 | Alias for [copy-static](#copy-static) ➔ [compile-source](#compile-source) ➔ [compile-stylesheets](#compile-stylesheets)
314 |
315 | ### serve
316 |
317 | > $: gulp serve
318 |
319 | Starts a connect based server on port `8088` which can be used during application development. Will perform a [build](#build) before starting the server.
320 |
321 | ### watch
322 |
323 | > $: gulp watch
324 |
325 | Starts a file system watcher which rebuilds our code as we change it.
326 |
327 | ### test
328 |
329 | > $: gulp test
330 |
331 | Starts the Karma server and run all unit tests (*-spec.js). The configuration for the test runner can be found in `config/karma.js`. Will perform a [build](#build) before running the tests.
332 |
333 | ### test-e2e
334 |
335 | > $: gulp test-e2e
336 |
337 | Starts a connect server on port `8089` and runs all e2e tests (*-test.js) against that server. Will perform a [build](#build) and an update of the necessary web drivers before running the tests.
338 |
339 | ### compile-source
340 |
341 | > $: gulp compile-source
342 |
343 | Transpiles our JavaScript source code from ES2015 to ES5 using Babel.
344 |
345 | ### compile-stylesheets
346 |
347 | > $: gulp compile-stylesheets
348 |
349 | Compiles our SASS stylesheets and uses [Autoprefixer](https://github.com/postcss/autoprefixer) to automatically add vendor prefixes for the most common browsers.
350 |
351 | ### bundle
352 |
353 | > $: gulp bundle
354 |
355 | Runs [Angular Lazy Bundler](https://github.com/matoilic/angular-lazy-bundler) and optimizes the loading process of our application in production. It bundles every component and 3rd-party package of our application into one file. This reduces the number of HTTP requests to load the individual parts. You can configure it further to combine multiple components which should be loaded together.
356 |
357 | ### htmlhint
358 |
359 | > $: gulp htmlhint
360 |
361 | Runs a code quality analysis for all HTML templates using [HTMLHint](http://htmlhint.com).
362 |
363 | ### eslint
364 |
365 | > $: gulp eslint
366 |
367 | Runs a code quality analysis for all JavaScript code using [ESLint](http://eslint.org).
368 |
369 | ## Preparing for production
370 |
371 | Before our appllication goes into production we want to run the [`bundle`](#bundle) Gulp task to reduce the amount of network requests needed to load everything that's needed to show the first screen to the user. The `bundle` task combines files into logical bundles so that resources which always must be loaded together sit in one file. Also, it updates SystemJS' configuration so that the loader knows it should load the bundled resources instead of the individual files. You don't need to change anything in your code to take advantage of the optimized loading process.
372 |
373 | Before running the `bundle` task we want to commit everything and revert the changes made by the bundler after the application is deployed. Otherwise SystemJS would also load the bundled resources in development. We would then have to run the `bundle` task everytime we change some thing whie developing. This would slow down the development since bundling takes quite some time. For more information on bundling see the [Angular Lazy Bundler](https://github.com/matoilic/angular-lazy-bundler#readme) and [SystemJS](https://github.com/systemjs/systemjs/blob/master/docs/config-api.md#bundle) documentations.
374 |
375 | ## Troubleshooting
376 |
377 | ### Missing dependencies
378 |
379 | A lot of packages don't declare their dependencies properly. For example, UI Router doesn't declare a dependency to Angular in it's package.json. Same with Bootstrap, which doesn't have a dependency to jQuery. This leads to errors when loading such libraries as their dependencies don't get loaded. If you encounter such issues search for special distribution build of the package on it's GitHub page, e.g. [github.com/angular/bower-angular-animate](https://github.com/angular/bower-angular-animate) for angular-animate. In that case try installing the package from there.
380 |
381 | Another possibility is to amend the missing information in `config/system.js` as already done by JSPM when it finds dependency declarations in package.json or in the JSPM registry itself.
382 |
383 | ### Incompatible Angular modules
384 |
385 | [ocLazyLoad's FAQ](https://oclazyload.readme.io/docs/faq) mentions some Angular modules which cannot be lazy loaded. If we want to use one of those, e.g. `angular-animate`, we need to import them in the application component and make them a dependency of it. And then do the same in the component where they are effectively required.
386 |
387 | ### Protractor and Safari
388 | The Safari Selenium Driver cannot be installed automatically. We need to [install it manually](https://code.google.com/p/selenium/wiki/SafariDriver) before we can run any Protractor tests against it. Safari 9.0.1 has a bug where it's only possible to install the plugin in safe mode. Hold the shift key while booting to start in safe mode.
389 |
390 | ## License
391 |
392 | Licensed under [BSD 3-Clause](https://github.com/matoilic/generator-angular-lazy/blob/master/license.md).
393 |
--------------------------------------------------------------------------------