<|start_filename|>app/templates/client/index/index.js<|end_filename|>
import Page from '../common';
import './style.less';

export default class Index extends Page {
    constructor() {
        super();
        console.log('Index\'s constructor is called');
    }
    init() {
        console.log('init');
    }
}

const index = new Index(); // eslint-disable-line


<|start_filename|>app/templates/config/default.js<|end_filename|>
const path = require('path');

module.exports = {
    app: {
        title: '<%= name %>',
    },
    ports: {
        server: process.env.PORT || 7000,
        assets: process.env.ASSET_PORT || 8081,
        livereload: 8010,
    },
    root: path.normalize(`${__dirname}/..`),
    log: {
        logPath: './logs/framework.log',
        logFormat: 'date=:date[iso] | ip=:remote-addr | method=:method | url=:url | status=:status | time=:response-time | bytes=:res[content-length] | referrer=":referrer" | user-agent=":user-agent" | cookie=":req[cookie]"',      // eslint-disable-line
    },
    cdn: {
        qiniu: {
            private: false,
            bucket: '',
            accessKey: '',
            secretKey: '',
        },
        domain: '//www.<%= name %>..com/',
        prefix: '<%= name %>/dist/',
    },
};


<|start_filename|>app/templates/index.js<|end_filename|>
if (!process.env.NODE_ENV) {
    process.env.NODE_ENV = 'development';
}

require('./server/server.js');


<|start_filename|>component/templates/js_es6.js<|end_filename|>
<% if (type === 'vue') { %>import Vue from 'vue';
import App from './index.vue';<% } %><% if (type === 'react') { %>import React from 'react';
import ReactDOM from 'react-dom';
import App from './index.jsx';<% } %><% if (isPage) { %>
import Page from '../common';<% } %><% if (needStyle) { %>
import './style.less';<% } %>

<% if (isPage) { %>export default class <%= name %> extends Page {<% } else { %>export default class <%= name %> {<% } %>
    constructor() {
        super();
        console.log('<%= name %>\'s constructor is called');<% if (isPage && type === 'vue') { %>
        this.renderVue();<% } %><% if (isPage && type === 'react') { %>
        this.renderReact();<% } %>
    }<% if (isPage && type === 'vue') { %>
    renderVue() {
        // eslint suggest not to use the new keyword if it hasn't been assigned to a variable.
        // so we just extend Vue with a create function in common/index.js to avoid using eslint-disable-line
        Vue.create({
            el: '#app',
            render: h => h(App)
        });
    }<% } %><% if (isPage && type === 'react') { %>
    renderReact() {
        ReactDOM.render(
            <App />,
            document.getElementById('app')
        );
    }<% } %>
}

<% if (isPage) { %>new <%= name %>(); // eslint-disable-line<% } %>


<|start_filename|>controller/templates/example.js<|end_filename|>
const express = require('express');
const assetsConfig = require('../../webpack-assets');
const router = express.Router();

module.exports = function (app) {
    app.use('/<%= router %>', router);
};

router.get('/', function (req, res) {
    res.render('<%= template %>/<%= template %>', {
        // IN NEED
        pagename: '<%= template %>',
        assetsConfig, // static files config in need
        RRC: {},
        // DATA
        data: {
        }
    });
});


<|start_filename|>app/templates/client/index/index.spec.js<|end_filename|>
const index = require('./index.js');
const expect = require('chai').expect;

describe('client.index', function () {
    it('init should be a function', function () {
        expect(index.init).to.be.a.function; // eslint-disable-line
    });
});


<|start_filename|>app/templates/server/controllers/index.js<|end_filename|>
const BaseController = require('./base');

module.exports = class IndexController extends BaseController {
    index() {
        this.render('index/index', {
            pagename: 'index',
            message: 'Hello World!',
        });
    }
};


<|start_filename|>app/index.js<|end_filename|>
var generators = require('yeoman-generator');

module.exports = generators.Base.extend({
    /*
     * Running Loop
     * constructor -> prompting -> configuring -> default -> writing -> conflicts -> install -> end
     */
    prompting: function () {
        return this.prompt([
            {
                type    : 'input',
                name    : 'name',
                message : '为你的项目起个名字吧？',
                default : this.appname // Default to current folder name
            },
        ]).then(function (answers) {
            this.props = answers;
            this.log('app', answers.name, 'has been created!');
            this.log('Happy Coding! You can look for docs at https://github.com/orange727/rock/README.md');
        }.bind(this));
    },
    // 配置文件
    configuring: {
        readme: function() {
            this.fs.copy(
                this.templatePath('README.md'),
                this.destinationPath('README.md')
            );
        },
        packageJson: function() {
            this.fs.copyTpl(
                this.templatePath('package.json'),
                this.destinationPath('package.json'), {
                    name: this.props.name
                }
            );
        },
        editConfig: function() {
            this.fs.copyTpl(
                this.templatePath('config/*'),
                this.destinationPath('config'), {
                    name: this.props.name
                }
            );
        },
        config: function() {
            this.fs.copyTpl(
                this.templatePath('.editorconfig'),
                this.destinationPath('.editorconfig'), {
                    name: this.props.name.toUpperCase()
                }
            );
        },
        lint: function() {
            this.fs.copy(
                this.templatePath('.eslintrc.js'),
                this.destinationPath('.eslintrc.js')
            );

            this.fs.copy(
                this.templatePath('.stylelintrc.js'),
                this.destinationPath('.stylelintrc.js')
            );
        },
        git: function() {
            this.fs.copy(
                this.templatePath('.npmignore'),
                this.destinationPath('.gitignore')
            );
        },
        build: function() {
            this.fs.copyTpl(
                this.templatePath('gulpfile.js'),
                this.destinationPath('gulpfile.js'), {
                    name: this.props.name,
                }
            );
            this.fs.copyTpl(
                this.templatePath('webpack/*'),
                this.destinationPath('webpack'), {
                    name: this.props.name,
                }
            );
        },
        deploy: function() {
            this.fs.copyTpl(
                this.templatePath('scripts/**/*'),
                this.destinationPath('scripts'), {
                    name: this.props.name
                }
            );
        },
        babel: function() {
            this.fs.copy(
                this.templatePath('.babelrc'),
                this.destinationPath('.babelrc')
            );
        },
    },
    // write routes, controllers, clients files
    writing: {
        server: function() {
            this.fs.copy(
                this.templatePath('index.js'),
                this.destinationPath('index.js')
            );
            this.fs.copy(
                this.templatePath('server/**/*'),
                this.destinationPath('server')
            );
        },
        client: function() {
            this.fs.copyTpl(
                this.templatePath('client/**/*'),
                this.destinationPath('client'),
                { name: this.props.name },
                { delimiter: '?' }
            );
        },
        test: function() {
            this.fs.copy(
                this.templatePath('tests/**/*'),
                this.destinationPath('tests')
            );
        }
    },
    install: {
        installDeps: function() {
            this.installDependencies({
                npm: true,
                bower: false,
                callback: function() {
                    console.log('npm is installed!');
                }
            });
        }
    },
    end: {
    }
});


<|start_filename|>component/index.js<|end_filename|>
var generators = require('yeoman-generator');

module.exports = generators.Base.extend({
    /*
     * Running Loop
     * constructor -> prompting -> configuring -> default -> writing -> conflicts -> install -> end
     */
    prompting: function () {
        return this.prompt([
        {
            type: 'list',
            name: 'isPage',
            message: '你要创建什么类型的模块？',
            choices: ['页面级别模块', '页面下的子模块'],
            filter: val => {
                return {
                    '页面级别模块': true,
                    '页面下的子模块': false
                }[val];
            },
        },
        {
            type: 'input',
            name: 'name',
            message: '给你的组件起个名字吧？',
        },
        {
            type: 'list',
            name: 'type',
            message: '你想哪种方式(框架)来组织你的组件？(建议使用React, Vue 或 ES6)',
            choices: ['React', 'Vue', 'ES6_modules', 'CommonJs', 'AMD'],
            filter: val => {
              return {
                'React': 'react',
                'Vue': 'vue',
                'ES6_modules': 'es6',
                'CommonJs': 'commonjs',
                'AMD': 'amd',
              }[val];
            },
        },
        {
            type: 'confirm',
            name: 'js',
            message: '是否需要包含js文件？',
            when: answers => !answers.isPage
        },
        {
            type: 'confirm',
            name: 'template',
            message: '是否需要包含模板文件？',
            when: answers => !answers.isPage
        },
        {
            type: 'confirm',
            name: 'style',
            message: '是否需要包含样式文件?',
        },
        {
            type: 'input',
            name: 'dest',
            message: '最后，输入你想创建的位置吧: 子模块请输出入client/所在页面/component',
            when: answers => !answers.isPage,
        }
    ]).then(function (answers) {
            this.component = {
                name: answers.name,
                type: answers.type,
                dest: answers.dest || 'client',
                isPage: answers.isPage,
                needJs: answers.isPage || !!answers.js,
                needTemplate: answers.isPage || !!answers.template,
                needStyle: !!answers.style,
            };
        }.bind(this));
    },
    writing: function() {
        const { name, type, dest, isPage, needJs, needTemplate, needStyle } = this.component;

        if (!isPage && !needJs && !needTemplate && !needStyle) {
            console.log('你难道什么文件都不想创建？');
            return;
        }

        const destDir = `${dest}/${name}`;
        this.mkdir(destDir);
        if (needTemplate) {
            if (isPage) {
                this.fs.copyTpl(
                    this.templatePath('pageTemplate.ejs'),
                    this.destinationPath(`${destDir}/${name}.ejs`),
                    { name, type },
                    { delimiter: '?' }
                );
            } else {
                this.fs.copyTpl(
                    this.templatePath('componentTemplate.ejs'),
                    this.destinationPath(`${destDir}/${name}.ejs`),
                    { name, type },
                    { delimiter: '?' }
                );
            }
        }

        if (needStyle) {
            this.write(`${destDir}/style.less`, '');
        }

        if (needJs) {
            const className = name.split('-').map((item) => item.slice(0, 1).toUpperCase() + item.slice(1)).join('');
            switch(type) {
                case 'react':
                    this.fs.copyTpl(
                        this.templatePath('example.jsx'),
                        this.destinationPath(`${destDir}/index.jsx`),
                        { name: className }
                    );
                    if (isPage) {
                        this.fs.copyTpl(
                            this.templatePath('js_es6.js'),
                            this.destinationPath(`${destDir}/index.js`),
                            { name: className, isPage, type, needStyle }
                        );
                    }
                    break;
                case 'vue':
                    this.fs.copyTpl(
                        this.templatePath('example.vue'),
                        this.destinationPath(`${destDir}/index.vue`),
                        { name: className }
                    );
                    if (isPage) {
                        this.fs.copyTpl(
                            this.templatePath('js_es6.js'),
                            this.destinationPath(`${destDir}/index.js`),
                            { name: className, isPage, type, needStyle }
                        );
                    }
                    break;
                case 'es6':
                    this.fs.copyTpl(
                        this.templatePath('js_es6.js'),
                        this.destinationPath(`${destDir}/index.js`),
                        { name: className, isPage, type, needStyle }
                    );
                    break;
                case 'commonjs':
                    this.fs.copyTpl(
                        this.templatePath('js_cmj.js'),
                        this.destinationPath(`${destDir}/index.js`),
                        { name: className }
                    );
                    break;
                case 'amd':
                    this.fs.copyTpl(
                        this.templatePath('js_amd.js'),
                        this.destinationPath(`${destDir}/index.js`),
                        { name: className }
                    );
                    break;
            }
            // if there is a js, there must have a test file
            this.write(`${destDir}/index.spec.js`, '');
        }
    },
    end: {
    }
});


<|start_filename|>app/templates/server/controllers/base.js<|end_filename|>
const _ = require('lodash');
const render = require('../libs/render');

module.exports = class BaseController {
    constructor(req, res, next) {
        this.req = req;
        this.res = res;
        this.next = next;
    }

    dispatch(action = 'index') {
        const Controller = this.constructor;
        return function (req, res, next) {
            const controller = new Controller(req, res, next);
            if (_.isFunction(controller[action] === false)) {
                return next(new Error(`Controller action does not exist {${controller}#${action}}`));
            }
            controller[action](req, res, next);
        };
    }

    render(view, data) {
        this.req._pagePath = view;
        this.req._pageData = data;

        render(this.req, this.res, this.next);
    }
};


<|start_filename|>app/templates/tests/libs/render.spec.js<|end_filename|>
const expect = require('chai').expect;
const render = require('../../server/libs/render.js');

describe('server.libs.render', function () {
    describe('#render', function () {
        it('render should be a function', function () {
            expect(render).to.be.a.function; // eslint-disable-line
        });
    });
});


<|start_filename|>app/templates/server/libs/render.js<|end_filename|>
const fs = require('fs');
const path = require('path');
const config = require('config');

const assetsPath = path.join(config.root, './webpack-assets.json');

module.exports = function render(req, res, next) {
    const { _pagePath, _pageData } = req;

    if (!_pagePath) {
        return next(new Error('req._pagePath is required'), null);
    }

    const pagePath = path.join(__dirname, `../../client/${_pagePath}.ejs`);

    fs.access(pagePath, fs.R_OK, (err) => {
        if (err) {
            return next(new Error(`page file not found ${pagePath}`), null);
        }

        const data = {
            // Common Data
            pagename: _pageData.pagename || _pagePath,
            DATA: _pageData,
            assetsConfig: __DEVELOPMENT__ ? JSON.parse(fs.readFileSync(assetsPath)) : require(assetsPath), //eslint-disable-line
            // pageData
            data: _pageData,
        };
        res.render(_pagePath, data);
    });
};


<|start_filename|>app/templates/gulpfile.js<|end_filename|>
/**
 * tasks
 * 1. default, deploy: 负责构建 EJS 中的静态资源（如图片），包括：拷贝，压缩，替换版本号
 * 2. cdn: 同步到CDN服务，需要自行配置
 * 3. lint: git pre-commint 代码检查
 */
const gulp = require('gulp');
const config = require('config');
const $ = require('gulp-load-plugins')();
const runSequence = require('run-sequence');
const pngquant = require('imagemin-pngquant');
const imageminMozjpeg = require('imagemin-mozjpeg');

const paths = {
    source_ejs: 'client/**/*.ejs',                // 模板源文件路径
    dist_ejs: 'server/compiled/views',
    source_image: 'client/**/image/**',           // 图片源文件路径
    dist_image: 'public/dist/rock/image/',
    source_rev: 'rev/**/*.json',                  // 部署时，存放图片版本号
    dist_rev: 'rev/image',
    lint_dir: ['./**/*.js', '!client/common/libs/**', '!public/**', '!node_modules/**'],
    public_dir: 'public/dist/rock/**/*',          // 静态资源路径
};

/**
 * Linter
 * run before git-commit
 */
gulp.task('lint', function () {
    return gulp.src(paths.lint_dir)
        .pipe($.eslint())
        .pipe($.eslint.format())
        .pipe($.eslint.failAfterError());
});

gulp.task('lint-fix', function () {
    return gulp.src(paths.lint_dir)
        .pipe($.eslint({
            fix: true,
        }))
        .pipe($.eslint.format())
        .pipe($.eslint.failAfterError());
});

gulp.task('image.copy', function () {
    return gulp.src([paths.source_image])
    .pipe($.flatten())
    .pipe(gulp.dest(paths.dist_image))
    .pipe($.livereload());
});

gulp.task('watch', function () {
    $.livereload.listen();
    gulp.watch(paths.source_image, function (e) {
        console.log(`File ${e.path} is ${e.type}, running tasks...`);
        runSequence('image.copy');
    });
});

/* Development
 * 几乎所有的编译工作都通过webpack实现，只有模板文件中引用的静态文件地址，是使用gulp来拷贝到目标文件中的
 * 顺序：image.copy
 * 顺序：watch
 */
gulp.task('default', function () {
    runSequence(
        'image.copy',
        'watch'
    );
});

/**
 * 图片处理：压缩，拷贝，打版本号
 */
gulp.task('image.minify', function () {
    return gulp.src([paths.source_image])
        .pipe($.flatten())
        .pipe($.imagemin([
            pngquant({ quality: '65-65' }),
            imageminMozjpeg({ quality: 60 }),
            $.imagemin.gifsicle(),
            $.imagemin.svgo()
        ]))
        .pipe($.rev())
        .pipe(gulp.dest(paths.dist_image))
        .pipe($.rev.manifest())
        .pipe(gulp.dest(paths.dist_rev));
});

/**
 * 替换模板中引用图片的路径
 */
const patterns = {
    image: /\/dist\/rock\/(image\/[\w-/$\{}.=\u2E80-\u9FFF~]*\.(png|jpg|jpeg|gif|ico|svg|eot|woff|ttf|swf|mp4))/g, // eslint-disable-line
};
const replacements = {
    image: `${config.cdn.domain}${config.cdn.prefix}$1`,
};
gulp.task('templates.replace', function () {
    return gulp.src([paths.source_rev, paths.source_ejs])
        .pipe($.revCollector({ replaceReved: true }))
        .pipe($.replace(patterns.image, replacements.image))
        .pipe(gulp.dest(paths.dist_ejs));
});

/**
 * Deploy
 * 构建静态资源、打版本号、替换引用
 * 顺序：image.minify(copied)
 * 顺序：template.replaced(replaced -> copy)
 */

gulp.task('deploy', function () {
    runSequence(
        'image.minify',
        'templates.replace'
    );
});

/**
 * 上传到CDN
 * Notice: 这里需要自行配置
 */
gulp.task('cdn', function () {
    return gulp.src([paths.public_dir])
        .pipe($.qiniu({
            accessKey: config.cdn.qiniu.accessKey,
            secretKey: config.cdn.qiniu.secretKey,
            bucket: config.cdn.qiniu.bucket,
            private: config.cdn.qiniu.private,
        }, { dir: config.cdn.prefix }));
});


<|start_filename|>component/templates/js_amd.js<|end_filename|>
/*
 * 说明：
 * 如需引入模块的样式
 * require('./style.less');
 */

define('<%= name %>', [], () => {
    const <%= name %> = {
        init: () => {}
    }
    return <%= name %>;
})

/*
 * 注意：
 * 页面级别的模块，define即会执行，而子组件级别的模块，只有require了才会执行
 */

/*
 * 说明：
 * 如需引用子模块,路径相对于client
 * require(['<%= name %>/component/amd-module'], (AmdModule) => {
 * })
 */

/*
 * 建议：
 * 根据需要，异步加载所需模块
 * require(['jquery'], ($) => {
 *     $('.btn').click(() => {
 *         require(['<%= name %>/component/amd-module'], () => {
 *         });
 *     });
 * })
 */


<|start_filename|>package.json<|end_filename|>
{
  "name": "generator-rock",
  "version": "1.0.7",
  "description": "Nodejs fullstack generator, using Express, Webpack, with React or Vue as client framework",
  "main": "app/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "yeoman-generator",
    "fullstack",
    "webpack",
    "express",
    "react",
    "vue"
  ],
  "author": "liujing",
  "license": "ISC",
  "dependencies": {
    "yeoman-generator": "^0.24.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/renrenche/rock"
  }
}


<|start_filename|>controller/index.js<|end_filename|>
var generators = require('yeoman-generator');

module.exports = generators.Base.extend({
    /*
     * Running Loop
     * constructor -> prompting -> configuring -> default -> writing -> conflicts -> install -> end
     */
    prompting: function () {
        return this.prompt([
        {
            type: 'input',
            name: 'router',
            message: '路由是？',
        },
        {
            type: 'input',
            name: 'template',
            message: '模板是？',
        },
    ]).then(function (answers) {
            this.component = {
                router: answers.router,
                template: answers.template,
            };
        }.bind(this));
    },
    writing: function() {
        const { router, template } = this.component;

        this.fs.copyTpl(
            this.templatePath('example.js'),
            this.destinationPath(`server/controllers/${template}.js`), {
                router,
                template,
            }
        );
    },
    end: {
    }
});


<|start_filename|>app/templates/.eslintrc.js<|end_filename|>
module.exports = {
    parser: 'babel-eslint',
    plugins: [
        // supports extracting and linting the JavaScript inside *.vue files
        // https://github.com/BenoitZugmeyer/eslint-plugin-html
        'html',
        // React specific linting rules
        // https://github.com/yannickcr/eslint-plugin-react
        'react',
        // Static AST checker for accessibility rules on JSX elements.
        // https://github.com/evcohen/eslint-plugin-jsx-a11y
        'jsx-a11y'
    ],
    // Extends eslint-config-airbnb-base
    // Rules for ES6+, requires eslint and eslint-plugin-import
    // https://www.npmjs.com/package/eslint-config-renrenche
    extends: 'renrenche',
    env: {
        es6: true,
        browser: true,
        node: true,
        mocha: true,
    },
    globals: {
        define: true,
        // Window.DATA for client usage
        DATA: true,
        __DEVELOPMENT__: true,
    },
    rules: {
        // Prevent React to be incorrectly marked as unused
        // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
        'react/jsx-uses-react': ['error'],

        // Prevent variables used in JSX to be incorrectly marked as unused
        // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
        'react/jsx-uses-vars': 'error',
    }
};


<|start_filename|>app/templates/server/server.js<|end_filename|>
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const util = require('util');
const config = require('config');
const moment = require('moment');
const morgan = require('morgan');
const express = require('express');
const engine = require('ejs-mate');
const winston = require('winston');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const methodOverride = require('method-override');
const FileStreamRotator = require('file-stream-rotator');

const logger = winston;
const app = express();

/*
 * Globales
 */
global.__CLIENT__ = false;
global.__SERVER__ = true;
global.__DEVELOPMENT__ = ['development', 'test'].indexOf(process.env.NODE_ENV) > -1;

/*
 * Security related
 */
app.disable('x-powered-by');
app.set('trust proxy', true);

/*
 * Logger
 * overwrite morgan datetime to China TimeZone
 * https://github.com/expressjs/morgan/issues/13
 */
morgan.token('date', function () {
    return moment().format(config.log.timeFormat || 'YYYY-MM-DD HH:mm:ss');
});

if (__DEVELOPMENT__) {
    app.use(morgan('dev'));
    winston.remove(winston.transports.Console);
    winston.add(winston.transports.Console, { colorize: true, level: 'debug', json: false });
} else {
    const logFolder = path.dirname(config.log.logPath);
    try {
        fs.mkdirSync(logFolder);
        logger.info('log folder created', logFolder);
    } catch (err) {
        logger.info('log folder already exist', logFolder);
    }
    app.use(morgan(config.log.logFormat, {
        stream: FileStreamRotator.getStream({  // eslint-disable-line
            date_format: 'YYYY-MM-DD',
            filename: `${path.dirname(config.log.logPath)}/access.log.%DATE%`,
            frequency: 'daily',
            verbose: false,
        })
    }));

    winston.remove(winston.transports.Console);
    winston.add(require('winston-daily-rotate-file'), {
        filename: config.log.logPath,
        dirname: path.dirname(config.log.logPath),
        datePattern: '.yyyy-MM-dd',
        colorize: false,
        level: 'debug',
        json: true,
        timestamp() {
            return moment().format(config.log.timeFormat || 'YYYY-MM-DD HH:mm:ss');
        },
    });
}

/*
 * Template
 */
app.set('view engine', 'ejs');
app.engine('ejs', engine);
app.set('views', __DEVELOPMENT__ ? `${config.root}/client` : `${config.root}/server/compiled/views`);

/*
 * default middlewares
 */
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(cookieParser());

/*
 * Router
 */
const routes = glob.sync(`${config.root}/server/routes/*.js`);
routes.forEach((route) => {
    require(route)(app);
});


/*
 * Server static files
 */
if (__DEVELOPMENT__) {
    app.use(express.static(`${config.root}/public`));
}

/*
 * Healthcheck for SLB
 */
app.use('/healthcheck', (req, res) => {
    res.status(200).send('I am alive');
});

/*
 * Otherwise, return 404
 */
app.use((req, res) => {
    res.status(404).send(`Request URL ${req.url} Not Found`);
});

/*
 * Debugger
 */
if (__DEVELOPMENT__) {
    require('express-debug')(app, { depth: 10 });
}

/*
 * Error Handling
 */
app.use((err, req, res, next) => { // eslint-disable-line
    logger.error(util.inspect({
        url: req.originalUrl,
        error: err,
        headers: req.headers,
        params: req.params,
        query: req.query,
    }, { depth: 4, color: true }));

    if (req.path.indexOf('/api/') === 0) {
        res.status(200).jsonp({ status: 500, errmsg: err.message || err.toString() });
    } else if (__DEVELOPMENT__) {
        res.send(`<pre>${err.stack}</pre>`);
    } else {
        res.status(500).send('Something broke!');
    }
});

app.listen(config.ports.server, function (err) {
    if (err) {
        console.error(err);
    }
    console.info('\n==> ✅  %s is running, talking to API server on %s.', config.app.title, config.ports.server);
    console.info('==> 💻  Open http://localhost:%s in a browser to view the app.', config.ports.assets);
});

// Export server for test use
module.exports = {
    server: app,
};


<|start_filename|>app/templates/server/routes/index.js<|end_filename|>
const express = require('express');

// TODO: do not need to require like keystone
const IndexController = require('../controllers/index');

module.exports = (app) => {
    const router = express.Router(); // eslint-disable-line new-cap
    router.get('/', IndexController.prototype.dispatch('index'));

    app.use('/', router);
};


<|start_filename|>component/templates/js_cmj.js<|end_filename|>
/*
 * 说明：
 * 如需引入模块的样式
 * require('./style.less');
 */

/*
 * 说明：
 * 支持相对路径和绝对路径加载模块
 * 绝对路径：查找node_modules下安装的模块
 * 相对路径：基于当前文件查找
 * 以commonjs的方式引用的模块，都会打包进“当前文件”——public/dist/[current].js
 */

const xx = require('xx');

const <%= name %> = {
    init: () => {}
};

module.exports = <%= name %>;


<|start_filename|>app/templates/webpack/webpack-dev-server.js<|end_filename|>
const ip = require('ip');
const config = require('config');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');

const webpackConfig = require('./webpack.dev.js');

// TODO: hot reload not work
// http://webpack.github.io/docs/webpack-dev-server.html#hot-mode
// https://github.com/gaearon/react-hot-loader/issues/141
for (const key in webpackConfig.entry) {
    webpackConfig.entry[key].unshift(`webpack-dev-server/client?http://localhost:${config.ports.assets}/`, 'webpack/hot/dev-server');
}

const compiler = webpack(webpackConfig);

const host = ip.address();
const port = config.ports.assets;
const server = new WebpackDevServer(compiler, {
    // contentBase: `//${host}:${port}`,
    publicPath: webpackConfig.output.publicPath,

    // Enable special support for Hot Module Replacement
    // Page is no longer updated, but a "webpackHotUpdate" message is sent to the content
    // Use "webpack/hot/dev-server" as additional module in your entry point
    hot: true,

    // Set this if you want webpack-dev-server to delegate a single path to an arbitrary server.
    // Use "**" to proxy all paths to the specified server.
    // This is useful if you want to get rid of 'http://localhost:8080/' in script[src],
    // and has many other use cases (see https://github.com/webpack/webpack-dev-server/pull/127 ).
    proxy: {
        '*': `localhost://${host}:${config.ports.server}`,
    },

    // Set this if you want to enable gzip compression for assets
    compress: true,

    // don’t output anything to the console.
    quiet: false,

    // suppress boring information.
    noInfo: false,

    // no watching, compiles on request (cannot be combined with --hot).
    lazy: false,
    watchOptions: {
        aggregateTimeout: 300,
        poll: 1000,
    },
    headers: { 'Access-Control-Allow-Origin': '*' },
    // silence output
    // Reference: https://github.com/webpack/webpack/issues/1191
    stats: {
        colors: true,
        hash: false,
        chunks: false,
        errors: true,
        errorDetails: true,
    },
});

server.listen(port, function (err) {
    if (err) {
        console.error(err);
    } else {
        console.info('==> 🚧  Webpack development server listening on port %s', port);
    }
});


<|start_filename|>app/templates/client/common/index.js<|end_filename|>
import Vue from 'vue';
import './style.less';

export default class Page {
    constructor() {
        console.log('Page\'s constructor is called');
        this.initVueFn();
    }
    initVueFn() {
        Vue.create = function (options) {
            return new Vue(options);
        };
    }
}


<|start_filename|>app/templates/webpack/webpack.make.js<|end_filename|>
const ip = require('ip');
const config = require('config');
const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const CleanPlugin = require('clean-webpack-plugin');
const AssetsPlugin = require('assets-webpack-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = function makeWebpackConfig(options) {
    /**
     * Environment type
     * BUILD is for generating minified builds
     */
    const BUILD = !!options.BUILD;
    const DEV = !!options.DEV;

    /**
     * Configuration
     * Reference: https://webpack.js.org/concepts/configuration/
     * This is the object where all configuration gets set
     */
    const webpackConfig = {};

    /**
     * Entry
     * Reference: https://webpack.js.org/concepts/entry-points/
     * As Multi Page Application
     */
    const entries = {};
    glob.sync('./client/*').forEach(function (filepath) {
        const dirName = path.basename(filepath);
        entries[dirName] = [path.resolve(__dirname, '../', filepath)];
    });
    webpackConfig.entry = entries;

    /**
     * Output
     * Reference: https://webpack.js.org/concepts/output/
     */
    webpackConfig.output = {
        // filename for entry points
        // Only add file hash in build mode
        filename: BUILD ? '[name]-[chunkhash:10].js' : '[name].js',

        // filename for non-entry points
        chunkFilename: BUILD ? 'chunk/[name]-[chunkhash:10].js' : 'chunk/[name].js',

        // Absolute path of output directory
        path: path.resolve(__dirname, '../public/dist/<%= name %>'),
        publicPath: BUILD ? `${config.cdn.domain}/<%= name %>/dist/` : `//${ip.address()}:${config.ports.assets}/dist/<%= name %>/`,
    };

    /**
     * Devtool
     * Reference: https://webpack.js.org/configuration/devtool/
     * control if and how Source Maps are generated
     */
    if (DEV) {
        webpackConfig.devtool = 'source-map';
    }

    /**
     * Loaders
     * Reference: https://webpack.js.org/concepts/loaders/
     * functions that transform resource file to new source
     */
    webpackConfig.module = {
        rules: [{
            /**
             * loader: babel-loader
             * Reference: http://babeljs.io/docs/setup/#installation
             */
            test: /\.jsx?$/,
            loader: 'babel-loader',
            query: {
                presets: ['babel-preset-es2015', 'babel-preset-react', 'babel-preset-stage-0'],
                plugins: ['transform-object-assign'],
            },
            exclude: [/node_modules/, /client\/common\/libs/],
        }, {
            /** loader: vue-loader
             * Reference: http://vue-loader.vuejs.org/en/
             */
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
                loaders: {
                    js: 'babel-loader?presets[]=es2015&plugins[]=transform-object-assign',
                    css: ExtractTextPlugin.extract({
                        use: `css-loader?minimize=${!!BUILD}`,
                        fallback: 'vue-style-loader',
                    }),
                    less: ExtractTextPlugin.extract('css!less'),
                },
            },
        }, {
            // loader: style-loader, css-loader, postcss-loader
            test: /\.css/,
            /** extract-text-webpack-plugin: move require('.css') in entry chunks to seperate css file
             * Reference: https://github.com/webpack/extract-text-webpack-plugin
             */
            loader: ExtractTextPlugin.extract({
                /** style-loader: add css to DOM by injecting a <style> tag
                 * Reference: https://github.com/webpack/style-loader
                 */
                fallback: 'style-loader',
                use: [{
                    /** css-loader: @import and url() are interpreted like @import and resolved
                     * Reference: https://github.com/webpack-contrib/css-loader
                     */
                    loader: 'css-loader',
                    options: { importLoaders: 1, minimize: !!BUILD },
                }, {
                    /** postcss-loader: transform styles
                     * Reference: https://github.com/postcss/postcss-loader
                     */
                    loader: 'postcss-loader',
                    options: {
                        plugins: () => [
                            /** autoprefixer
                             * Reference: https://github.com/postcss/autoprefixer
                             * add prefixes to css rules according to "Browserslist"
                             */
                            autoprefixer({ browsers: ['>1%', 'last 2 versions', 'iOS 7'] }),
                        ]
                    },
                }],
            }),
        }, {
            // loader: style-loader, css-loader, postcss-loader, less-loader
            test: /\.less$/,
            loader: ExtractTextPlugin.extract({
                fallback: 'style-loader',
                use: [{
                    loader: 'css-loader',
                    options: { importLoaders: 1, minimize: !!BUILD },
                }, {
                    loader: 'postcss-loader',
                    options: {
                        plugins: () => [
                            autoprefixer({
                                browsers: ['>1%', 'last 2 versions', 'iOS 7'],
                            }),
                        ]
                    },
                }, 'less-loader'],
            }),
        }, {
            /** url-loader
             * Reference: https://github.com/webpack-contrib/url-loader
             * Copy files to output with rename usding the hash
             * If the file is smaller tha 1000 bytes, will return a Data Url
             */
            test: /\.(jpe?g|png|gif|svg)$/,
            use: [
                'url-loader?limit=1000&name=./image/[name]-[hash:10].[ext]',
                /** image-webpack-loader
                 * Reference: https://github.com/tcoopman/image-webpack-loader
                 * load and optimize with mozjpeg for .jpg and pngquant for .png
                 */
                {
                    loader: 'image-webpack-loader',
                    options: {
                        mozjpeg: { quality: 65 },
                        pngquant: { quality: '65-90', speed: 4 },
                    },
                },
            ],
        }, {
            test: /\.woff(\?t=\d+)?$/,
            loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=./image/[name]-[hash:10].[ext]',
        }, {
            test: /\.woff2(\?t=\d+)?$/,
            loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=./image/[name]-[hash:10].[ext]',
        }, {
            test: /\.ttf(\?t=\d+)?$/,
            loader: 'url-loader?limit=10000&mimetype=application/octet-stream&name=./image/[name]-[hash:10].[ext]',
        }, {
            test: /\.eot(\?t=\d+)?$/,
            loader: 'file-loader?name=./image/[name]-[hash:10].[ext]',
        }, {
            test: /\.svg(\?t=\d+)?$/,
            loader: 'url-loader?limit=10000&mimetype=image/svg+xml',
        }],
    };

    if (DEV) {
        /** eslint-loader
         * Reference: https://github.com/MoOx/eslint-loader
         */
        webpackConfig.module.rules.unshift({
            test: /\.(js|jsx|vue)$/,
            loader: 'eslint-loader',
            enforce: 'pre',
            exclude: [/node_modules/, /client\/common\/libs/],
            options: { configFile: '.eslintrc.js' },
        });
    }

    /**
     * Plugins
     * Reference: https://webpack.js.org/concepts/plugins/
     * List of Plugins: http://webpack.github.io/docs/list-of-plugins.html
     * A webpack plugin is a Javascript object with an apply property, can be called by the webpack compiler
     * Do anything else that a loader cannot do
     */
    webpackConfig.plugins = [
        /**
         * Plugin: assets-webpack-plugin
         * Description: emits a JSON file(webpack-assets.json) with assets path
         * Reference: https://github.com/kossnocorp/assets-webpack-plugin
         */
        new AssetsPlugin({
            prettyPrint: true,

            // path where to save the created JSON file
            path: path.resolve(__dirname, '../'),
        }),
    ];

    if (DEV) {
        webpackConfig.plugins.push(
            /**
             * Plugin: extract-text-webpack-plugin
             * Description: extract text form bundle into a file, especially move require('style.less') into a seperate css file
             * Reference: https://github.com/webpack-contrib/extract-text-webpack-plugin
             */
            new ExtractTextPlugin({ filename: 'css/[name].css', allChunks: true }),

            /**
             * Plugin: optimize.CommonsChunkPlugin
             * Reference: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
             */
            new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', chunks: Object.keys(entries) }),

            /**
             * Plugin: HotModuleReplacementPlugin
             * Description: enable hot module replacement
             * Reference: http://webpack.github.io/docs/list-of-plugins.html#hotmodulereplacementplugin
             */
            new webpack.HotModuleReplacementPlugin(),

            /**
             * Plugin: LiveReloadPlugin
             * Description: enable reload client
             * Reference: https://github.com/statianzo/webpack-livereload-plugin
             */
            new LiveReloadPlugin({
                appendScriptTag: true,
                port: config.ports.livereload,
            })
        );
    }

    if (BUILD) {
        const uglifyCompressOptions = {
            sequences: true,        // join consecutive statemets with the “comma operator”
            properties: true,       // optimize property access: a["foo"] → a.foo
            dead_code: true,        // discard unreachable code
            drop_debugger: true,    // discard “debugger” statements
            unsafe: false,          // some unsafe optimizations (see below)
            conditionals: true,     // optimize if-s and conditional expressions
            comparisons: true,      // optimize comparisons
            evaluate: true,         // evaluate constant expressions
            booleans: true,         // optimize boolean expressions
            loops: true,            // optimize loops
            unused: true,           // drop unused variables/functions
            hoist_funs: true,       // hoist function declarations
            hoist_vars: false,      // hoist variable declarations
            if_return: true,        // optimize if-s followed by return/continue
            join_vars: true,        // join var declarations
            cascade: true,          // try to cascade `right` into `left` in sequences
            side_effects: true,     // drop side-effect-free statements
            warnings: true,         // warn about potentially dangerous optimizations/code
            global_defs: {          // global definitions
                __DEVELOPMENT__: false,
            },
        };
        webpackConfig.plugins.push(
            /**
             * Plugin: clean-webpack-plugin
             * Reference: https://github.com/johnagan/clean-webpack-plugin
             */
            new CleanPlugin(['public'], { root: path.resolve(__dirname, '../') }),

            /**
             * Plugin: Optimize
             * Reference: http://webpack.github.io/docs/list-of-plugins.html#optimize
             */
            new webpack.optimize.UglifyJsPlugin({ mangle: true, compress: uglifyCompressOptions }),

            new ExtractTextPlugin({ filename: 'css/[name]-[contenthash:10].css', allChunks: true })
        );
    }

    /**
     * Resolve
     * Reference: https://webpack.js.org/configuration/resolve/
     * change how modules ares resolved
     */
    webpackConfig.resolve = {
        // modules: absolute path will only search in below directory.
        // e.g. client/ takes precedences over node_modules/
        modules: [path.resolve(__dirname, '../client'), 'node_modules'],

        // alias: create aliases to import or require modules more easily
        // e.g. import 'jquery' instead of import '../common/libs/jquery.js'
        alias: {
            jquery: 'common/libs/jquery.js',
        },
    };

    return webpackConfig;
};
