├── template
├── .gitignore
├── server
│ └── plugins
│ │ └── HapiWebpackMiddleware
│ │ ├── package.json
│ │ └── index.js
├── .editorconfig
├── test
│ ├── unit
│ │ ├── .eslintrc
│ │ ├── index.js
│ │ ├── specs
│ │ │ └── App.spec.js
│ │ └── karma.conf.js
│ └── api
│ │ └── spec
│ │ └── apiCall.js
├── .babelrc
├── client
│ ├── main.js
│ └── App.vue
├── build
│ └── index_dev.html
├── .eslintrc.js
├── README.md
├── gulpfile.js
├── config
│ ├── production.webpack.config.js
│ └── webpack.config.js
├── app.js
└── package.json
├── meta.js
└── README.md
/template/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/template/server/plugins/HapiWebpackMiddleware/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hapi-webpack-middleware"
3 | }
4 |
--------------------------------------------------------------------------------
/template/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/template/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | },
9 | "rules": {
10 | "no-unused-expressions": "off" // https://github.com/eslint/eslint/issues/2102
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/template/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {
4 | "modules": false
5 | }],
6 | "stage-2"
7 | ],
8 | "plugins": ["transform-runtime"],
9 | "comments": false,
10 | "env": {
11 | "test": {
12 | "plugins": ["istanbul"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/template/client/main.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable*/
2 |
3 | import Vue from 'vue'
4 | import App from './App.vue'
5 |
6 | // Axios doesn't include Promise polyfill (e.g. 'es6-promise'), if you need one, here is a good place to initiate it
7 |
8 | new Vue({
9 | el: '#app',
10 | components: {
11 | App
12 | }
13 | })
14 |
--------------------------------------------------------------------------------
/template/build/index_dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ name }}
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/template/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
4 | extends: 'standard',
5 | // required to lint *.vue files
6 | plugins: [
7 | 'html'
8 | ],
9 | // add your custom rules here
10 | 'rules': {
11 | // allow paren-less arrow functions
12 | 'arrow-parens': 0,
13 | // allow debugger during development
14 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
15 | // require semicolons
16 | 'semi': ['error', 'always']
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/template/test/unit/index.js:
--------------------------------------------------------------------------------
1 | {{#if_eq headlessBrowser "PhantomJS"}}
2 | // Polyfill fn.bind() for PhantomJS
3 | /* eslint-disable no-extend-native */
4 | Function.prototype.bind = require('function-bind');
5 | {{/if_eq}}
6 |
7 | // require all test files (files that ends with .spec.js)
8 | var testsContext = require.context('./specs', true, /\.spec$/);
9 | testsContext.keys().forEach(testsContext);
10 |
11 | // require all src files except main.js for coverage.
12 | // you can also change this to match only the subset of files that
13 | // you want coverage for.
14 | var srcContext = require.context('../../client', true, /^\.\/(?!main(\.js)?$)/);
15 | srcContext.keys().forEach(srcContext);
16 |
--------------------------------------------------------------------------------
/template/test/api/spec/apiCall.js:
--------------------------------------------------------------------------------
1 | var Chai = require('chai'); // assertion library
2 | var Lab = require('lab');
3 | var lab = exports.lab = Lab.script();
4 |
5 | // environment
6 | var describe = lab.describe;
7 | var it = lab.it;
8 | var before = lab.before;
9 | var beforeEach = lab.beforeEach;
10 | var expect = Chai.expect;
11 |
12 | var server = require('../../../app.js');
13 |
14 | describe('apiCall', function () {
15 | it('should reply with "Hello!" messsage', async function () {
16 | var options = {
17 | method: 'GET',
18 | url: '/api/call'
19 | };
20 |
21 | var response = await server.inject(options);
22 |
23 | expect(response).to.be.an('object');
24 | expect(response.statusCode).to.be.equal(200);
25 | expect(response.result.message).to.be.equal('Hello!');
26 | });
27 | });
28 |
29 |
--------------------------------------------------------------------------------
/template/client/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
\{{ msg }}
4 |
5 |
API says: \{{ api }}
6 |
7 |
8 |
9 |
35 |
36 |
41 |
--------------------------------------------------------------------------------
/template/README.md:
--------------------------------------------------------------------------------
1 | # {{ name }}
2 |
3 | {{ description }}
4 |
5 | ## Mini-doc
6 |
7 | **/build/** - Contains files needed for build and hot development
8 | **/build/index_dev.html** - Template for index.html, it will be used by HMR when developing in memory and during production build
9 | **/client/** - Vue.js app source
10 | **/config/** - Configuration files
11 | **/public/** - Public folder served by Hapi
12 | **/server/** - Server side logic
13 | **/test/** - Contains test files
14 | **/app.js** - Production server
15 | **/gulpfile.js** - Gulp setup file
16 |
17 | ## Dev Setup
18 |
19 | ``` bash
20 | # install dependencies
21 | npm install
22 |
23 | {{#unit}}
24 | # run unit tests
25 | npm run unit
26 | {{/unit}}
27 |
28 | {{#unitApi}}
29 | # run api unit tests
30 | npm run unit-api
31 | {{/unitApi}}
32 |
33 | # serve with hot reload at localhost:4000 (proxy from localhost:3000)
34 | npm run dev
35 | ```
36 |
37 | ## Production Setup
38 |
39 | ``` bash
40 | # install dependencies
41 | npm install
42 |
43 | # build for production with minification
44 | npm run build
45 |
46 | # run application at localhost:3000
47 | node app.js
48 | ```
49 |
50 | Credits:
51 | [Vue](https://vuejs.org/)
52 | [Hapi](http://hapijs.com/)
53 | [Gulp](https://gulpjs.com/)
54 | [BrowserSync](https://www.browsersync.io/)
55 | [Webpack](https://webpack.js.org/)
56 |
--------------------------------------------------------------------------------
/template/test/unit/specs/App.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from 'client/App';
3 | import axios from 'axios';
4 |
5 | const appInstance = new Vue(App);
6 |
7 | describe('App.vue component', function () {
8 | it('should set correct default data', function () {
9 | expect(typeof App.data).to.be.equal('function');
10 | var defaultData = App.data();
11 | expect(defaultData.msg).to.be.equal('Welcome!');
12 | });
13 |
14 | let promiseCall;
15 |
16 | before(function () {
17 | promiseCall = sinon.stub(axios, 'get').returnsPromise();
18 | });
19 |
20 | after(function () {
21 | axios.get.restore();
22 | });
23 |
24 | it('should contain proper methods', function (done) {
25 | expect(typeof appInstance.helloCall).to.be.equal('function');
26 | done();
27 | });
28 |
29 | // https://github.com/substantial/sinon-stub-promise
30 | it('helloCall should set proper data from AJAX response [success]', function (done) {
31 | promiseCall.resolves({
32 | data: {
33 | message: 'Hello!'
34 | }
35 | });
36 | appInstance.helloCall();
37 | expect(appInstance.api).to.be.equal('Hello!');
38 | done();
39 | });
40 |
41 | it('helloCall should set proper data from AJAX response [fail]', function (done) {
42 | promiseCall.rejects({
43 | data: {
44 | 'statusCode': 400,
45 | 'error': 'Bad Request',
46 | 'message': 'invalid query'
47 | }
48 | });
49 | appInstance.helloCall();
50 | expect(appInstance.error).to.be.not.empty;
51 | done();
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/template/server/plugins/HapiWebpackMiddleware/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Webpack = require('webpack');
4 | const WebpackDevMiddleware = require('webpack-dev-middleware');
5 | const WebpackHotMiddleware = require('webpack-hot-middleware');
6 |
7 | const register = function (server, options, next) {
8 | const webpackCompiler = Webpack(options.config);
9 | const devMiddleware = WebpackDevMiddleware(webpackCompiler, options.devOptions);
10 | const hotMiddleware = WebpackHotMiddleware(webpackCompiler, options.hotOptions);
11 |
12 | server.ext({
13 | type: 'onRequest',
14 | method: (request, h) => {
15 | return new Promise((resolve, reject) => {
16 | const rawRequest = request.raw.req;
17 | const rawResponse = request.raw.res;
18 | devMiddleware(rawRequest, rawResponse, (error) => {
19 | if (error) {
20 | return reject(error);
21 | }
22 | return resolve(h.continue);
23 | });
24 | });
25 | }
26 | });
27 |
28 | server.ext({
29 | type: 'onRequest',
30 | method: (request, h) => {
31 | return new Promise((resolve, reject) => {
32 | const rawRequest = request.raw.req;
33 | const rawResponse = request.raw.res;
34 | hotMiddleware(rawRequest, rawResponse, (error) => {
35 | if (error) {
36 | return reject(error);
37 | }
38 | return resolve(h.continue);
39 | });
40 | });
41 | }
42 | });
43 | };
44 |
45 | exports.plugin = {
46 | register: register,
47 | pkg: require('./package.json')
48 | };
49 |
--------------------------------------------------------------------------------
/template/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp'),
2 | browserSync = require('browser-sync'),
3 | path = require('path'),
4 | nodemon = require('gulp-nodemon');
5 |
6 | // Run Nodemon and connect to BrowserSync
7 | gulp.task('nodemon', function (cb) {
8 | var called = false;
9 | return nodemon({
10 |
11 | // Nodemon the dev server
12 | script: 'app.js',
13 |
14 | // Watch core server file(s) that require restart on change
15 | watch: ['app.js', 'server/**/*.*']
16 | })
17 | .on('start', function onStart() {
18 |
19 | // Ensure only one call
20 | if (!called) {
21 | called = true;
22 | return cb();
23 | }
24 | })
25 | .on('restart', function onRestart() {
26 | // Reload connected browsers after a slight delay
27 | console.log('Reload');
28 | setTimeout(function () {
29 | browserSync.reload({
30 | stream: false
31 | });
32 | }, 1000);
33 | });
34 | });
35 |
36 | gulp.task('browser-sync', ['nodemon'], function () {
37 |
38 | // Config options: http://www.browsersync.io/docs/options/
39 | browserSync.init({
40 |
41 | // Watch the following files, inject changes or refresh
42 | files: ['public/assets/js/**/*.*', 'public/assets/css/**/*.*', 'public/assets/images/**/*.*'],
43 |
44 | // Proxy our Hapi app
45 | proxy: 'http://localhost:3000',
46 | // Use the following port for the proxied app to avoid clash
47 | port: 4000,
48 | reloadDelay: 500,
49 | injectChanges: true,
50 | open: false
51 | });
52 | console.log('Initiate BrowserSync');
53 | });
54 |
55 | // BUILD
56 |
57 | gulp.task('default', ['browser-sync']);
58 |
--------------------------------------------------------------------------------
/template/config/production.webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 |
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
6 |
7 | module.exports = {
8 | mode: 'production',
9 | entry: ['./client/main.js'],
10 | output: {
11 | path: path.resolve(__dirname, '../public/'),
12 | publicPath: '/',
13 | filename: 'build/build.js'
14 | },
15 | resolve: {
16 | modules: [path.join(__dirname, '../node_modules')],
17 | extensions: ['.js', '.vue'],
18 | alias: {
19 | 'client': path.resolve(__dirname, '../client'),
20 | 'components': path.resolve(__dirname, '../clients/components'),
21 | 'vue$': 'vue/dist/vue.common.js'
22 | }
23 | },
24 | module: {
25 | rules: [{
26 | test: /\.vue$/,
27 | loader: 'vue-loader',
28 | options: {
29 | loaders: {
30 | 'css': ExtractTextPlugin.extract(['css-loader']),
31 | 'sass': ExtractTextPlugin.extract(['css-loader', 'sass-loader'])
32 | }
33 | }
34 | }, {
35 | test: /\.js$/,
36 | loader: 'babel-loader',
37 | exclude: /node_modules/
38 | }, {
39 | test: /\.html$/,
40 | loader: 'vue-html-loader'
41 | }, {
42 | test: /\.(png|jpg|gif|svg)$/,
43 | loader: 'url-loader',
44 | query: {
45 | limit: 10000,
46 | name: '[name].[ext]?[hash]'
47 | }
48 | }]
49 | },
50 | plugins: [
51 | new webpack.optimize.UglifyJsPlugin({
52 | sourceMap: true
53 | }),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: path.resolve(__dirname, '../public/index.html'),
57 | template: path.resolve(__dirname, '../build/index_dev.html')
58 | }),
59 | new ExtractTextPlugin('build/style.css')
60 | ],
61 | devtool: 'source-map'
62 | }
63 |
--------------------------------------------------------------------------------
/template/config/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const projectRoot = path.resolve(__dirname, '../')
4 |
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | {{#lint}}
7 | const EslintFriendlyFormatter = require('eslint-friendly-formatter')
8 | {{/lint}}
9 |
10 | module.exports = {
11 | mode: 'development',
12 | entry: ['webpack-hot-middleware/client', './client/main.js'],
13 | output: {
14 | path: path.resolve(__dirname, '../public/'),
15 | publicPath: '/',
16 | filename: 'build/build.js'
17 | },
18 | resolve: {
19 | modules: [path.join(__dirname, '../node_modules'), 'node_modules'],
20 | extensions: ['.js', '.vue'],
21 | alias: {
22 | 'client': path.resolve(__dirname, '../client'),
23 | 'components': path.resolve(__dirname, '../clients/components'),
24 | 'vue$': 'vue/dist/vue.common.js'
25 | }
26 | },
27 | module: {
28 | rules: [
29 | {{#lint}}
30 | {
31 | enforce: "pre",
32 | test: /\.(js|vue)$/,
33 | loader: 'eslint-loader',
34 | include: projectRoot,
35 | exclude: /node_modules/,
36 | options:{
37 | formatter: EslintFriendlyFormatter
38 | }
39 | },
40 | {{/lint}}
41 | {
42 | test: /\.vue$/,
43 | loader: 'vue-loader',
44 | options: {
45 | loaders: {
46 | 'sass': 'vue-style-loader!css-loader!sass-loader'
47 | }
48 | }
49 | }, {
50 | test: /\.js$/,
51 | loader: 'babel-loader',
52 | exclude: /node_modules/
53 | }, {
54 | test: /\.html$/,
55 | loader: 'vue-html-loader'
56 | }, {
57 | test: /\.(png|jpg|gif|svg)$/,
58 | loader: 'url-loader',
59 | query: {
60 | limit: 10000,
61 | name: '[name].[ext]?[hash]'
62 | }
63 | }]
64 | },
65 | plugins: [
66 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
67 | new webpack.HotModuleReplacementPlugin(),
68 | // https://github.com/ampedandwired/html-webpack-plugin
69 | new HtmlWebpackPlugin({
70 | filename: path.resolve(__dirname, '../public/index.html'),
71 | template: path.resolve(__dirname, '../build/index_dev.html'),
72 | })
73 | ],
74 | devtool: 'eval'
75 | }
76 |
--------------------------------------------------------------------------------
/meta.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "prompts": {
3 | "name": {
4 | "type": "string",
5 | "required": true,
6 | "label": "Project name"
7 | },
8 | "description": {
9 | "type": "string",
10 | "required": true,
11 | "label": "Project description",
12 | "default": "A Hapi Vue project"
13 | },
14 | "author": {
15 | "type": "string",
16 | "label": "Author"
17 | },
18 | "private": {
19 | "type": "boolean",
20 | "default": true
21 | },
22 | "lint": {
23 | "type": "confirm",
24 | "message": "Include code linter (ESlint)?",
25 | "default": true
26 | },
27 | "unit": {
28 | "type": "confirm",
29 | "message": "Include unit tests (Karma + Mocha + Sinon + Chai)?",
30 | "default": false
31 | },
32 | "headlessBrowser": {
33 | "when": "unit",
34 | "type": "list",
35 | "message": "Pick a browser",
36 | "choices": [
37 | {
38 | "name": "ChromeHeadless (Chrome >=59)",
39 | "value": "ChromeHeadless"
40 | },
41 | {
42 | "name": "ChromiumHeadless (Chromium >=59)",
43 | "value": "ChromiumHeadless"
44 | },
45 | {
46 | "name": "FirefoxHeadless (FF [Linux] >=55 [Win/Mac] >=56)",
47 | "value": "FirefoxHeadless"
48 | },
49 | {
50 | "name": "PhantomJS",
51 | "value": "PhantomJS"
52 | }
53 | ]
54 | },
55 | "unitApi": {
56 | "type": "confirm",
57 | "message": "Include dedicated, Hapi unit test utility (Lab + Chai)?",
58 | "default": false
59 | }
60 | },
61 | "helpers": {
62 | "if_or": function (a, b, opts) {
63 | if (a || b) {
64 | return opts.fn(this)
65 | }
66 | return opts.inverse(this)
67 | },
68 | "if_eq_or": function (option, a, b, opts) {
69 | if (option === a || option === b) {
70 | return opts.fn(this)
71 | }
72 | return opts.inverse(this)
73 | }
74 | },
75 | "filters": {
76 | ".eslintrc.js": "lint",
77 | "test": "unit || unitApi",
78 | "test/unit/**/*": "unit",
79 | "test/api/**/*": "unitApi"
80 | },
81 | "completeMessage": "To run dev version:\n\n cd {{destDirName}}\n npm install\n npm run dev\n\n"
82 | }
83 |
--------------------------------------------------------------------------------
/template/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Hapi = require('hapi');
4 | const Inert = require('inert');
5 | const path = require('path');
6 |
7 | const server = new Hapi.Server({
8 | port: 3000
9 | });
10 |
11 | async function initWebpackTools (middleware, config) {
12 | await server.register({
13 | plugin: middleware,
14 | options: {
15 | config: config,
16 | devOptions: {
17 | logLevel: 'warn',
18 | publicPath: config.output.publicPath,
19 | stats: {
20 | colors: true
21 | }
22 | }
23 | }
24 | });
25 | }
26 |
27 | // Register webpack HMR, fallback to development environment
28 | if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
29 | const WebpackConfig = require('./config/webpack.config.js'); // Webpack config
30 | const HapiWebpackMiddleware = require('./server/plugins/HapiWebpackMiddleware');
31 | initWebpackTools(HapiWebpackMiddleware, WebpackConfig);
32 | }
33 |
34 | server.register({
35 | plugin: Inert
36 | }).then(() => {
37 | server.route({
38 | method: 'GET',
39 | path: '/assets/{filepath*}',
40 | config: {
41 | auth: false,
42 | cache: {
43 | expiresIn: 24 * 60 * 60 * 1000,
44 | privacy: 'public'
45 | }
46 | },
47 | handler: {
48 | directory: {
49 | path: path.join(__dirname, '/public/assets/'),
50 | listing: false,
51 | index: false
52 | }
53 | }
54 | });
55 |
56 | server.route({
57 | method: 'GET',
58 | path: '/build/{filepath*}',
59 | config: {
60 | auth: false,
61 | cache: {
62 | expiresIn: 24 * 60 * 60 * 1000,
63 | privacy: 'public'
64 | }
65 | },
66 | handler: {
67 | directory: {
68 | path: path.join(__dirname, '/public/build/'),
69 | listing: false,
70 | index: false
71 | }
72 | }
73 | });
74 |
75 | // Example api call
76 | server.route({
77 | method: 'GET',
78 | path: '/api/call',
79 | handler: function (request, h) {
80 | return {
81 | message: 'Hello!'
82 | };
83 | }
84 | });
85 |
86 | server.route({
87 | method: 'GET',
88 | path: '/{path*}',
89 | handler: {
90 | file: './public/index.html'
91 | }
92 | });
93 |
94 | server.start();
95 | });
96 |
97 | server.events.on('start', (route) => {
98 | console.log('Server started');
99 | });
100 |
101 | module.exports = server;
102 |
--------------------------------------------------------------------------------
/template/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var path = require('path')
7 | var merge = require('webpack-merge')
8 | var baseConfig = require('../../config/webpack.config')
9 | var webpack = require('webpack')
10 | var projectRoot = path.resolve(__dirname, '../../')
11 |
12 | var webpackConfig = merge(baseConfig, {
13 | // use inline sourcemap for karma-sourcemap-loader
14 | devtool: '#inline-source-map'
15 | })
16 |
17 | // no need for app entry during tests
18 | delete webpackConfig.entry
19 |
20 | webpackConfig.module.rules = webpackConfig.module.rules || []
21 | webpackConfig.module.rules.unshift({
22 | test: /\.js$/,
23 | include: path.resolve(projectRoot, 'client')
24 | })
25 |
26 | webpackConfig.module.rules.some(function(loader, i) {
27 | if (loader.loader === 'babel') {
28 | loader.include = path.resolve(projectRoot, 'test/unit')
29 | return true
30 | }
31 | })
32 |
33 | module.exports = function(config) {
34 | config.set({
35 | // to run in additional browsers:
36 | // 1. install corresponding karma launcher
37 | // http://karma-runner.github.io/0.13/config/browsers.html
38 | // 2. add it to the `browsers` array below.
39 | {{#if_eq headlessBrowser "PhantomJS"}}
40 | browsers: ['PhantomJS'],
41 | {{/if_eq}}
42 | {{#if_eq headlessBrowser "ChromeHeadless"}}
43 | browsers: ['ChromeHeadless'],
44 | {{/if_eq}}
45 | {{#if_eq headlessBrowser "ChromiumHeadless"}}
46 | browsers: ['ChromiumHeadless'],
47 | {{/if_eq}}
48 | {{#if_eq headlessBrowser "FirefoxHeadless"}}
49 | browsers: ['FirefoxHeadless'],
50 | customLaunchers: {
51 | FirefoxHeadless: {
52 | base: 'Firefox',
53 | flags: [ '-headless' ]
54 | }
55 | },
56 | {{/if_eq}}
57 | frameworks: ['mocha', 'sinon-stub-promise', 'sinon-chai'],
58 | reporters: ['spec', 'coverage'],
59 | files: ['./index.js'],
60 | preprocessors: {
61 | './index.js': ['webpack', 'sourcemap']
62 | },
63 | webpack: webpackConfig,
64 | webpackMiddleware: {
65 | noInfo: true
66 | },
67 | coverageReporter: {
68 | dir: './coverage',
69 | reporters: [{
70 | type: 'lcov',
71 | subdir: '.'
72 | }, {
73 | type: 'text-summary'
74 | }]
75 | },
76 | client: {
77 | captureConsole: true
78 | },
79 | singleRun: true
80 | })
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hapi Vue
2 |
3 | > Development setup utilising Vue (Webpack HMR + Vue loader) and Hapi (Nodemon) running with Gulp and BrowserSync. It includes CSS extraction (build only), linting (ESlint) and unit testing (Karma, Mocha, Chai, Sinon, Lab).
4 |
5 | [Important!] Hapi Vue has been updated to Hapi 17, which introduced major, breaking changes - you can read more in the official [GitHub issue](https://github.com/hapijs/hapi/issues/3658). Version of Hapi Vue based on Hapi 16 can be found in the "archive" branch, [archive-hapi-16](https://github.com/Belar/hapi-vue/tree/archive-hapi-16).
6 |
7 | ## Mini-doc
8 |
9 | **/build/** - Contains files needed for build and hot development
10 | **/build/index_dev.html** - Template for index.html, it will be used by HMR when developing in memory and during production build
11 | **/client/** - Vue.js app source
12 | **/config/** - Configuration files
13 | **/public/** - Public folder served by Hapi
14 | **/server/** - Server side logic
15 | **/test/** - Contains test files
16 | **/app.js** - Production server
17 | **/gulpfile.js** - Gulp setup file
18 |
19 | ### Usage
20 |
21 | This is a project template for [vue-cli](https://github.com/vuejs/vue-cli).
22 |
23 | ``` bash
24 | $ npm install -g vue-cli
25 | $ vue init belar/hapi-vue
26 | $ cd
27 | ```
28 |
29 | For tips and guides regarding integration of additional mechanisms (e.g vue-router), visit [Hapi Vue wiki](https://github.com/Belar/hapi-vue/wiki).
30 |
31 | NOTE: If you decide to run api (Hapi) unit tests using Mocha, remember about proper NODE_ENV (test), which with Lab is being set automatically.
32 |
33 | ## Dev Setup
34 |
35 | ``` bash
36 | # install dependencies
37 | npm install
38 |
39 | # run unit tests
40 | npm run unit
41 |
42 | # run api unit tests
43 | npm run unit-api
44 |
45 | # serve with hot reload at localhost:4000 (proxy from localhost:3000)
46 | npm run dev
47 | ```
48 |
49 | ### Hapi Webpack Middleware
50 |
51 | Hapi Vue uses Hapi plugin system for integration of Webpack middlewares [web-dev-middleware](https://github.com/webpack/webpack-dev-middleware) and [webpack-hot-middleware](https://github.com/glenjamin/webpack-hot-middleware).
52 |
53 | Plugin accepts following options on registration:
54 | **config** - Webpack configuration file, `./config/webpack.config.js` by default.
55 | **devOptions** - Options for webpack-dev-middleware, as in the official documentation.
56 | **hotOptions** - Options for webpack-hot-middleware, as in the official documentation.
57 | ## Production Setup
58 |
59 | ``` bash
60 | # install dependencies
61 | npm install
62 |
63 | # build for production with minification
64 | npm run build
65 |
66 | # run application at localhost:3000
67 | node app.js
68 | ```
69 |
70 | Credits:
71 | [Vue](https://vuejs.org/)
72 | [Hapi](http://hapijs.com/)
73 | [Gulp](https://gulpjs.com/)
74 | [BrowserSync](https://www.browsersync.io/)
75 | [Webpack](https://webpack.js.org/)
76 |
--------------------------------------------------------------------------------
/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{ name }}",
3 | "description": "{{ description }}",
4 | "author": "{{ author }}",
5 | {{#private}}
6 | "private": true,
7 | {{/private}}
8 | "scripts": {
9 | "dev": "gulp",
10 | {{#unit}}
11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
12 | {{/unit}}
13 | {{#unitApi}}
14 | "unit-api": "lab test/api/spec/ -C -v -c -r console -o stdout -r html -o test/api/coverage/index.html --coverage-exclude .eslintrc.js",
15 | {{/unitApi}}
16 | "build": "webpack --progress --hide-modules --config config/production.webpack.config.js"
17 | },
18 | "dependencies": {
19 | "axios": "^0.18.0",
20 | "babel-runtime": "^6.23.0",
21 | "hapi": "^17.1.1",
22 | "inert": "^5.0.1",
23 | "vue": "^2.3.4"
24 | },
25 | "devDependencies": {
26 | "browser-sync": "^2.18.12",
27 | "cross-env": "^5.0.1",
28 | "gulp": "^3.9.1",
29 | "gulp-nodemon": "^2.2.1",
30 | "babel-core": "^6.25.0",
31 | "babel-loader": "^7.1.1",
32 | "babel-plugin-transform-runtime": "^6.23.0",
33 | "babel-preset-es2015": "^6.24.1",
34 | "babel-preset-stage-2": "^6.24.1",
35 | "html-webpack-plugin": "^3.0.6",
36 | "vue-hot-reload-api": "^2.1.0",
37 | "vue-html-loader": "^1.2.4",
38 | "vue-loader": "^14.2.1",
39 | "vue-template-compiler": "^2.3.4",
40 | "vue-style-loader": "^4.0.2",
41 | "css-loader": "^0.28.0",
42 | "file-loader": "^1.1.6",
43 | "url-loader": "^1.0.1",
44 | "node-sass": "^4.5.3",
45 | "sass-loader": "^6.0.6",
46 | {{#lint}}
47 | "eslint": "^4.1.1",
48 | "eslint-config-standard": "^11.0.0",
49 | "eslint-friendly-formatter": "^3.0.0",
50 | "eslint-loader": "^2.0.0",
51 | "eslint-plugin-html": "^4.0.1",
52 | "eslint-plugin-import": "^2.6.1",
53 | "eslint-plugin-node": "^6.0.1",
54 | "eslint-plugin-promise": "^3.5.0",
55 | "eslint-plugin-standard": "^3.0.1",
56 | {{/lint}}
57 | {{#if_or unit unitApi}}
58 | "chai": "^4.0.2",
59 | {{/if_or}}
60 | {{#unit}}
61 | "karma": "^2.0.0",
62 | "karma-coverage": "^1.1.1",
63 | "karma-mocha": "^1.3.0",
64 | "karma-sinon-chai": "^1.3.1",
65 | "karma-sinon-stub-promise": "^1.0.0",
66 | "karma-sourcemap-loader": "^0.3.7",
67 | "karma-spec-reporter": "0.0.32",
68 | "karma-webpack": "^2.0.3",
69 | "lolex": "^2.1.2",
70 | "mocha": "^5.0.4",
71 | "sinon": "^4.1.3",
72 | "sinon-chai": "^3.0.0",
73 | "sinon-stub-promise": "^4.0.0",
74 | "inject-loader": "^3.0.0",
75 | "babel-plugin-istanbul": "^4.1.4",
76 | {{#if_eq headlessBrowser "PhantomJS"}}
77 | "function-bind": "^1.1.0",
78 | "karma-phantomjs-launcher": "^1.0.4",
79 | "phantomjs-prebuilt": "^2.1.14",
80 | {{/if_eq}}
81 | {{#if_eq_or headlessBrowser "ChromeHeadless" "ChromiumHeadless"}}
82 | "karma-chrome-launcher": "^2.2.0",
83 | {{/if_eq_or}}
84 | {{#if_eq headlessBrowser "FirefoxHeadless"}}
85 | "karma-firefox-launcher": "^1.1.0",
86 | {{/if_eq}}
87 | {{/unit}}
88 | {{#unitApi}}
89 | "lab": "^15.1.2",
90 | {{/unitApi}}
91 | "webpack": "^4.1.1",
92 | "webpack-dev-middleware": "^3.0.1",
93 | "webpack-hot-middleware": "^2.18.0",
94 | "webpack-merge": "^4.1.0",
95 | "extract-text-webpack-plugin": "^3.0.0"
96 | }
97 | }
98 |
--------------------------------------------------------------------------------