├── .env.example ├── .eslintrc.js ├── .gitignore ├── LICENSE.md ├── README.md ├── app.js ├── bin └── www ├── config ├── app.js └── bookshelf.js ├── controllers └── .gitkeep ├── knexfile.js ├── migrations └── 20171203143245_users.js ├── models └── User.js ├── package.json ├── postcss.config.js ├── public ├── css │ └── style.css ├── img │ ├── cc-voyager-logo.svg │ └── cc-voyager.svg ├── js │ ├── global.bundle.js │ └── home.bundle.js └── robots.txt ├── routes └── web.js ├── src ├── global.js ├── home.js ├── img │ ├── .gitkeep │ ├── cc-voyager-logo.svg │ └── cc-voyager.svg └── scss │ ├── fonts │ ├── .gitkeep │ ├── lato.scss │ └── roboto-mono.scss │ ├── global │ ├── _variables.scss │ └── util.scss │ ├── pages │ └── index.scss │ ├── partials │ ├── footer.scss │ └── header.scss │ └── style.scss ├── test └── app.test.js ├── views ├── error.hbs ├── index.hbs ├── layouts │ └── main.hbs └── partials │ ├── footer.hbs │ └── header.hbs ├── webpack.config.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | DB_HOST= 2 | DB_USER= 3 | DB_PASSWORD= 4 | DB_NAME= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: 'eslint:recommended', 7 | parserOptions: { 8 | sourceType: 'module', 9 | ecmaVersion: 2017 10 | }, 11 | rules: { 12 | 'linebreak-style': ['error', 'unix'], 13 | quotes: ['error', 'single'], 14 | semi: ['error', 'never'] 15 | }, 16 | globals: { 17 | module: true, 18 | require: true, 19 | __dirname: true, 20 | process: true, 21 | describe: true, 22 | it: true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://docs.npmjs.com/cli/shrinkwrap#caveats 27 | node_modules 28 | 29 | # Debug log from npm 30 | npm-debug.log 31 | yarn-error.log 32 | 33 | .env 34 | *.todo 35 | .ds_store -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## The MIT License (MIT) 2 | 3 | Copyright © 2017-2018 Chris Courses 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ## About Voyager 4 | 5 | Voyager is a Node and SQL based web application framework that provides everything you need to produce robust, scalable, database-driven web apps, including: 6 | 7 | * Built-in user authentication (opt-in) 8 | * [Simplistic database migrations (Knex)](http://knexjs.org/) 9 | * [Easy-to-use ORM (Sequelize)](http://docs.sequelizejs.com/) 10 | * [Clean, full-featured layout templating (Handlebars)](http://handlebarsjs.com/) 11 | * [Robust front-end build system (Webpack)](https://webpack.js.org/) 12 | * Modern ES6 syntax 13 | 14 | Built on top of Express, and modeled after Rails and Laravel, Voyager provides a complete solution to getting up and running with a user-based app as quickly as possible. 15 | 16 | ## Getting Started 17 | 18 | 1. Download the [Voyager command line interface (CLI)](<(https://github.com/chriscourses/voyager-cli)>) with NPM: 19 | 20 | npm install -g voyager-cli 21 | 22 | 2. Create a new Voyager project, with or without auth: 23 | 24 | voyager new newApp 25 | 26 | or 27 | 28 | voyager new newApp --auth 29 | 30 | _Notice: You must run the built-in Knex migrations and add a .env file with valid credentials for the auth version to work. Further auth related instructions can be found under **Auth Setup** below._ 31 | 32 | 3. Change directory to `newApp` and start the Voyager server: 33 | 34 | cd newApp 35 | voyager start 36 | 37 | 4. Open up a new terminal tab and run webpack (requires webpack installed globally): 38 | 39 | webpack 40 | 41 | 5. Your app should open up automatically at `http://localhost:3001` and you should see the Voyager start up page. 42 | 43 | ## Auth Setup 44 | 45 | To get started with Voyager's built-in authentication, you must follow a few steps required to connect your app to a database and utilize key functionalities such as email confirmations and password resets. 46 | 47 | 1. Create a file called `.env` in the root of your newly generated Voyager project 48 | 2. Copy the contents of `.env.example` and paste inside of the newly created `.env` file: 49 | 50 | DB_HOST= 51 | DB_USER= 52 | DB_PASSWORD= 53 | DB_NAME= 54 | 55 | MAILGUN_KEY= 56 | MAILGUN_DOMAIN= 57 | 58 | 3. Create a MySQL database for your app, grab a [Mailgun API key](https://www.mailgun.com/), and insert the corresponding values into the `.env` file. A finished version will look something like this for a local server: 59 | 60 | DB_HOST=localhost 61 | DB_USER=root 62 | DB_PASSWORD=root 63 | DB_NAME=newApp 64 | 65 | MAILGUN_KEY=key-kfvud83k3kf3vbn22k223222 66 | MAILGUN_DOMAIN=mailgun.yourdomain.com 67 | 68 | 4. Install knex if you haven't already: 69 | 70 | npm install knex -g 71 | 72 | 5. Run knex migrations: 73 | 74 | knex migrate:latest 75 | 76 | 6. Restart the Voyager server: 77 | 78 | voyager start 79 | 80 | 7. Open a new tab in terminal and start webpack: 81 | 82 | webpack 83 | 84 | You should now have a fully functioning app with auth features such as user registration, user login, email confirmation, and password reset functionality. For more information and instruction regarding Voyager auth, check the [Chris Courses YouTube channel](https://www.youtube.com/c/chriscourses) for Voyager tutorials and more. 85 | 86 | ## Quick Docs 87 | 88 | #### Knex Migration API 89 | 90 | Run Migrations: `knex migrate:latest` 91 | 92 | Rollback Last Migrations: `knex migrate:rollback` 93 | 94 | ## License 95 | 96 | Voyager is an open-source framework licensed under the [MIT License](https://opensource.org/licenses/MIT). 97 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const favicon = require('serve-favicon') 4 | const logger = require('morgan') 5 | const cookieParser = require('cookie-parser') 6 | const bodyParser = require('body-parser') 7 | const config = require('./config/app') 8 | 9 | const exphbs = require('express-handlebars') 10 | 11 | if (config.useEnv) require('dotenv-safe').load() // Must load as early as possible 12 | 13 | const routes = require('./routes/web') 14 | 15 | const app = express() 16 | 17 | // view engine setup 18 | const hbs = exphbs.create({ 19 | extname: '.hbs', 20 | defaultLayout: 'main', 21 | helpers: { 22 | ifeq(a, b, options) { 23 | if (a === b) return options.fn(this) 24 | return options.inverse(this) 25 | }, 26 | toJSON(object) { 27 | return JSON.stringify(object) 28 | } 29 | } 30 | }) 31 | 32 | app.engine('.hbs', hbs.engine) 33 | app.set('views', path.join(__dirname, 'views')) 34 | app.set('view engine', '.hbs') 35 | 36 | // uncomment after placing your favicon in /public 37 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 38 | app.use(logger('dev')) 39 | app.use(bodyParser.json()) 40 | app.use(bodyParser.urlencoded({ extended: false })) 41 | app.use(cookieParser()) 42 | app.use(express.static(path.join(__dirname, 'public'))) 43 | 44 | app.use('/', routes) 45 | 46 | // catch 404 and forward to error handler 47 | app.use((req, res, next) => { 48 | const err = new Error('Not Found') 49 | err.status = 404 50 | next(err) 51 | }) 52 | 53 | // error handler 54 | app.use((err, req, res) => { 55 | // set locals, only providing error in development 56 | res.locals.message = err.message 57 | res.locals.error = req.app.get('env') === 'development' ? err : {} 58 | 59 | // render the error page 60 | res.status(err.status || 500) 61 | res.render('error') 62 | }) 63 | 64 | module.exports = app 65 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | const app = require('../app') 6 | const debug = require('debug')('chris-courses-2018:server') 7 | const http = require('http') 8 | 9 | /** 10 | * Get port from environment and store in Express. 11 | */ 12 | 13 | const port = normalizePort(process.env.PORT || '3000') 14 | app.set('port', port) 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | 20 | const server = http.createServer(app) 21 | 22 | /** 23 | * Listen on provided port, on all network interfaces. 24 | */ 25 | 26 | server.listen(port) 27 | server.on('error', onError) 28 | server.on('listening', onListening) 29 | 30 | /** 31 | * Normalize a port into a number, string, or false. 32 | */ 33 | 34 | function normalizePort(val) { 35 | const port = parseInt(val, 10) 36 | 37 | if (isNaN(port)) { 38 | // named pipe 39 | return val 40 | } 41 | 42 | if (port >= 0) { 43 | // port number 44 | return port 45 | } 46 | 47 | return false 48 | } 49 | 50 | /** 51 | * Event listener for HTTP server "error" event. 52 | */ 53 | 54 | function onError(error) { 55 | if (error.syscall !== 'listen') { 56 | throw error 57 | } 58 | 59 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port 60 | 61 | // handle specific listen errors with friendly messages 62 | switch (error.code) { 63 | case 'EACCES': 64 | console.error(bind + ' requires elevated privileges') 65 | process.exit(1) 66 | break 67 | case 'EADDRINUSE': 68 | console.error(bind + ' is already in use') 69 | process.exit(1) 70 | break 71 | default: 72 | throw error 73 | } 74 | } 75 | 76 | /** 77 | * Event listener for HTTP server "listening" event. 78 | */ 79 | 80 | function onListening() { 81 | const addr = server.address() 82 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port 83 | debug('Listening on ' + bind) 84 | } 85 | -------------------------------------------------------------------------------- /config/app.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useEnv: false 3 | } 4 | -------------------------------------------------------------------------------- /config/bookshelf.js: -------------------------------------------------------------------------------- 1 | const config = require('../knexfile') 2 | const knex = require('knex')(config) 3 | const bookshelf = require('bookshelf')(knex) 4 | 5 | knex.migrate.latest() 6 | 7 | module.exports = bookshelf 8 | -------------------------------------------------------------------------------- /controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/controllers/.gitkeep -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv') 2 | 3 | dotenv.load() 4 | 5 | module.exports = { 6 | client: 'mysql', 7 | connection: process.env.DATABASE_URL || { 8 | host: process.env.DB_HOST, 9 | user: process.env.DB_USER, 10 | password: process.env.DB_PASSWORD, 11 | database: process.env.DB_NAME 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /migrations/20171203143245_users.js: -------------------------------------------------------------------------------- 1 | exports.up = function(knex, Promise) { 2 | return Promise.all([ 3 | knex.schema.createTable('users', function(table) { 4 | table.increments() 5 | table.string('username', 20).unique() 6 | table.string('email', 100).unique() 7 | table.binary('password', 60) 8 | table.timestamps() 9 | }) 10 | ]) 11 | } 12 | 13 | exports.down = function(knex, Promise) { 14 | return Promise.all([knex.schema.dropTable('users')]) 15 | } 16 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const bookshelf = require('../config/bookshelf') 2 | 3 | const User = bookshelf.Model.extend({ 4 | tableName: 'users' 5 | }) 6 | 7 | module.exports = User 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voyager", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "nodemon ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.18.2", 10 | "bookshelf": "^0.12.0", 11 | "cookie-parser": "~1.4.3", 12 | "debug": "~2.6.9", 13 | "dotenv-safe": "^4.0.4", 14 | "express": "~4.15.5", 15 | "express-handlebars": "^3.0.0", 16 | "hbs": "~4.0.1", 17 | "knex": "^0.14.2", 18 | "morgan": "~1.9.0", 19 | "mysql": "^2.15.0", 20 | "serve-favicon": "~2.4.5" 21 | }, 22 | "devDependencies": { 23 | "babel-core": "^6.26.0", 24 | "babel-loader": "^7.1.2", 25 | "babel-preset-env": "^1.6.1", 26 | "browser-sync": "^2.18.13", 27 | "browser-sync-webpack-plugin": "^1.2.0", 28 | "chai": "^4.1.2", 29 | "css-loader": "^0.28.7", 30 | "extract-text-webpack-plugin": "^3.0.2", 31 | "file-loader": "^1.1.6", 32 | "mocha": "^4.0.1", 33 | "node-sass": "^4.7.2", 34 | "postcss-loader": "^2.0.9", 35 | "prettier": "^1.10.2", 36 | "sass-loader": "^6.0.6", 37 | "style-loader": "^0.19.1", 38 | "supertest": "^3.0.0", 39 | "webpack": "^3.10.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({ 4 | /* ...options */ 5 | }) 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* latin-ext */ 2 | @font-face { 3 | font-family: 'Lato'; 4 | font-style: normal; 5 | font-weight: 300; 6 | src: local("Lato Light"), local("Lato-Light"), url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwaPGQ3q5d0N7w.woff2) format("woff2"); 7 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, U+2113, U+2c60-2c7f, U+A720-A7FF; } 8 | 9 | /* latin */ 10 | @font-face { 11 | font-family: 'Lato'; 12 | font-style: normal; 13 | font-weight: 300; 14 | src: local("Lato Light"), local("Lato-Light"), url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2) format("woff2"); 15 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } 16 | 17 | /* cyrillic-ext */ 18 | @font-face { 19 | font-family: 'Roboto Mono'; 20 | font-style: normal; 21 | font-weight: 400; 22 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2) format("woff2"); 23 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F, U+FE2E-FE2F; } 24 | 25 | /* cyrillic */ 26 | @font-face { 27 | font-family: 'Roboto Mono'; 28 | font-style: normal; 29 | font-weight: 400; 30 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2) format("woff2"); 31 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; } 32 | 33 | /* greek-ext */ 34 | @font-face { 35 | font-family: 'Roboto Mono'; 36 | font-style: normal; 37 | font-weight: 400; 38 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhHq3-cXbKDO1w.woff2) format("woff2"); 39 | unicode-range: U+1f00-1fff; } 40 | 41 | /* greek */ 42 | @font-face { 43 | font-family: 'Roboto Mono'; 44 | font-style: normal; 45 | font-weight: 400; 46 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2) format("woff2"); 47 | unicode-range: U+0370-03ff; } 48 | 49 | /* vietnamese */ 50 | @font-face { 51 | font-family: 'Roboto Mono'; 52 | font-style: normal; 53 | font-weight: 400; 54 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2) format("woff2"); 55 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab; } 56 | 57 | /* latin-ext */ 58 | @font-face { 59 | font-family: 'Roboto Mono'; 60 | font-style: normal; 61 | font-weight: 400; 62 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2) format("woff2"); 63 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, U+2113, U+2c60-2c7f, U+A720-A7FF; } 64 | 65 | /* latin */ 66 | @font-face { 67 | font-family: 'Roboto Mono'; 68 | font-style: normal; 69 | font-weight: 400; 70 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2) format("woff2"); 71 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } 72 | 73 | /* cyrillic-ext */ 74 | @font-face { 75 | font-family: 'Roboto Mono'; 76 | font-style: normal; 77 | font-weight: 700; 78 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq8f7-pAVU_Lrg.woff2) format("woff2"); 79 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F, U+FE2E-FE2F; } 80 | 81 | /* cyrillic */ 82 | @font-face { 83 | font-family: 'Roboto Mono'; 84 | font-style: normal; 85 | font-weight: 700; 86 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq1f7-pAVU_Lrg.woff2) format("woff2"); 87 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; } 88 | 89 | /* greek-ext */ 90 | @font-face { 91 | font-family: 'Roboto Mono'; 92 | font-style: normal; 93 | font-weight: 700; 94 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq9f7-pAVU_Lrg.woff2) format("woff2"); 95 | unicode-range: U+1f00-1fff; } 96 | 97 | /* greek */ 98 | @font-face { 99 | font-family: 'Roboto Mono'; 100 | font-style: normal; 101 | font-weight: 700; 102 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqyf7-pAVU_Lrg.woff2) format("woff2"); 103 | unicode-range: U+0370-03ff; } 104 | 105 | /* vietnamese */ 106 | @font-face { 107 | font-family: 'Roboto Mono'; 108 | font-style: normal; 109 | font-weight: 700; 110 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq-f7-pAVU_Lrg.woff2) format("woff2"); 111 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab; } 112 | 113 | /* latin-ext */ 114 | @font-face { 115 | font-family: 'Roboto Mono'; 116 | font-style: normal; 117 | font-weight: 700; 118 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq_f7-pAVU_Lrg.woff2) format("woff2"); 119 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, U+2113, U+2c60-2c7f, U+A720-A7FF; } 120 | 121 | /* latin */ 122 | @font-face { 123 | font-family: 'Roboto Mono'; 124 | font-style: normal; 125 | font-weight: 700; 126 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqxf7-pAVU_.woff2) format("woff2"); 127 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } 128 | 129 | .m0 { 130 | margin: 0; } 131 | 132 | .mt0 { 133 | margin-top: 0; } 134 | 135 | .mb0 { 136 | margin-bottom: 0; } 137 | 138 | .mmt10 { 139 | margin-top: 10px; } 140 | @media screen and (min-width: 576px) { 141 | .mmt10 { 142 | margin-top: 0; } } 143 | 144 | .mmt25 { 145 | margin-top: 25px; } 146 | @media screen and (min-width: 576px) { 147 | .mmt25 { 148 | margin-top: 0; } } 149 | 150 | .mmt50 { 151 | margin-top: 50px; } 152 | @media screen and (min-width: 576px) { 153 | .mmt50 { 154 | margin-top: 0; } } 155 | 156 | .mmt75 { 157 | margin-top: 75px; } 158 | @media screen and (min-width: 576px) { 159 | .mmt75 { 160 | margin-top: 0; } } 161 | 162 | .mmt100 { 163 | margin-top: 100px; } 164 | @media screen and (min-width: 576px) { 165 | .mmt100 { 166 | margin-top: 0; } } 167 | 168 | .mmb10 { 169 | margin-bottom: 10px; } 170 | @media screen and (min-width: 576px) { 171 | .mmb10 { 172 | margin-bottom: 0; } } 173 | 174 | .mmb25 { 175 | margin-bottom: 25px; } 176 | @media screen and (min-width: 576px) { 177 | .mmb25 { 178 | margin-bottom: 0; } } 179 | 180 | .mmb50 { 181 | margin-bottom: 50px; } 182 | @media screen and (min-width: 576px) { 183 | .mmb50 { 184 | margin-bottom: 0; } } 185 | 186 | .mmb75 { 187 | margin-bottom: 75px; } 188 | @media screen and (min-width: 576px) { 189 | .mmb75 { 190 | margin-bottom: 0; } } 191 | 192 | .mmb100 { 193 | margin-bottom: 100px; } 194 | @media screen and (min-width: 576px) { 195 | .mmb100 { 196 | margin-bottom: 0; } } 197 | 198 | .mpt10 { 199 | padding-top: 10px; } 200 | @media screen and (min-width: 576px) { 201 | .mpt10 { 202 | padding-top: 0; } } 203 | 204 | .mpt25 { 205 | padding-top: 25px; } 206 | @media screen and (min-width: 576px) { 207 | .mpt25 { 208 | padding-top: 0; } } 209 | 210 | .mpt50 { 211 | padding-top: 50px; } 212 | @media screen and (min-width: 576px) { 213 | .mpt50 { 214 | padding-top: 0; } } 215 | 216 | .mpt75 { 217 | padding-top: 75px; } 218 | @media screen and (min-width: 576px) { 219 | .mpt75 { 220 | padding-top: 0; } } 221 | 222 | .mpt100 { 223 | padding-top: 100px; } 224 | @media screen and (min-width: 576px) { 225 | .mpt100 { 226 | padding-top: 0; } } 227 | 228 | .mpb10 { 229 | padding-bottom: 10px; } 230 | @media screen and (min-width: 576px) { 231 | .mpb10 { 232 | padding-bottom: 0; } } 233 | 234 | .mpb25 { 235 | padding-bottom: 25px; } 236 | @media screen and (min-width: 576px) { 237 | .mpb25 { 238 | padding-bottom: 0; } } 239 | 240 | .mpb50 { 241 | padding-bottom: 50px; } 242 | @media screen and (min-width: 576px) { 243 | .mpb50 { 244 | padding-bottom: 0; } } 245 | 246 | .mpb75 { 247 | padding-bottom: 75px; } 248 | @media screen and (min-width: 576px) { 249 | .mpb75 { 250 | padding-bottom: 0; } } 251 | 252 | .mpb100 { 253 | padding-bottom: 100px; } 254 | @media screen and (min-width: 576px) { 255 | .mpb100 { 256 | padding-bottom: 0; } } 257 | 258 | @media screen and (min-width: 576px) { 259 | .mt10 { 260 | margin-top: 10px; } 261 | .mt25 { 262 | margin-top: 25px; } 263 | .mt50 { 264 | margin-top: 50px; } 265 | .mt75 { 266 | margin-top: 75px; } 267 | .mt100 { 268 | margin-top: 100px; } 269 | .mt150 { 270 | margin-top: 150px; } 271 | .mt200 { 272 | margin-top: 200px; } 273 | .mb10 { 274 | margin-bottom: 10px; } 275 | .mb15 { 276 | margin-bottom: 15px; } 277 | .mb25 { 278 | margin-bottom: 25px; } 279 | .mb50 { 280 | margin-bottom: 50px; } 281 | .mb75 { 282 | margin-bottom: 75px; } 283 | .mb100 { 284 | margin-bottom: 100px; } 285 | .pt10 { 286 | padding-top: 10px; } 287 | .pt25 { 288 | padding-top: 25px; } 289 | .pt50 { 290 | padding-top: 50px; } 291 | .pt75 { 292 | padding-top: 75px; } 293 | .pt100 { 294 | padding-top: 100px; } 295 | .pt125 { 296 | padding-top: 125px; } 297 | .pt150 { 298 | padding-top: 150px; } 299 | .pt175 { 300 | padding-top: 175px; } 301 | .pt200 { 302 | padding-top: 200px; } 303 | .pb10 { 304 | padding-bottom: 10px; } 305 | .pb25 { 306 | padding-bottom: 25px; } 307 | .pb50 { 308 | padding-bottom: 50px; } 309 | .pb75 { 310 | padding-bottom: 75px; } 311 | .pb100 { 312 | padding-bottom: 100px; } 313 | .pb150 { 314 | padding-bottom: 150px; } 315 | .pb200 { 316 | padding-bottom: 200px; } } 317 | 318 | @media screen and (min-width: 576px) { 319 | .hide-desktop { 320 | display: none; } } 321 | 322 | .hide-mobile { 323 | display: none; } 324 | @media screen and (min-width: 576px) { 325 | .hide-mobile { 326 | display: block; } } 327 | 328 | header { 329 | padding: 12px; 330 | border-bottom: 1px solid #eee; 331 | background: white; } 332 | 333 | .header-container { 334 | display: -webkit-box; 335 | display: -ms-flexbox; 336 | display: flex; 337 | -webkit-box-pack: justify; 338 | -ms-flex-pack: justify; 339 | justify-content: space-between; 340 | -webkit-box-align: center; 341 | -ms-flex-align: center; 342 | align-items: center; } 343 | 344 | .header-logo { 345 | width: 100px; } 346 | @media screen and (min-width: 576px) { 347 | .header-logo { 348 | width: 150px; } } 349 | 350 | .header-nav a { 351 | display: inline-block; 352 | margin: 0 15px; 353 | font-family: "Roboto Mono", monospace; } 354 | .header-nav a:nth-last-child(1) { 355 | margin-right: 0; } 356 | 357 | .index-container { 358 | height: calc(100vh - 75px); 359 | display: -webkit-box; 360 | display: -ms-flexbox; 361 | display: flex; 362 | -webkit-box-orient: vertical; 363 | -webkit-box-direction: normal; 364 | -ms-flex-direction: column; 365 | flex-direction: column; 366 | -webkit-box-align: center; 367 | -ms-flex-align: center; 368 | align-items: center; 369 | -webkit-box-pack: center; 370 | -ms-flex-pack: center; 371 | justify-content: center; } 372 | .index-container h1 { 373 | margin-bottom: 0; } 374 | 375 | .body-logo { 376 | width: 90%; 377 | max-width: 480px; 378 | margin-bottom: 30px; } 379 | 380 | .index-nav a { 381 | display: inline-block; 382 | margin: 0 3px; } 383 | 384 | body { 385 | margin: 0; 386 | font-family: "Lato", sans-serif; 387 | color: #333; } 388 | 389 | a { 390 | color: #00b7ff; 391 | text-decoration: none; } 392 | 393 | .container { 394 | max-width: 1170px; 395 | margin: 0 auto; } 396 | -------------------------------------------------------------------------------- /public/img/cc-voyager-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 67 | 68 | 70 | 73 | 74 | 76 | 78 | 81 | 82 | 84 | 87 | 88 | 90 | 91 | -------------------------------------------------------------------------------- /public/img/cc-voyager.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 68 | 69 | 71 | 74 | 75 | 77 | 79 | 82 | 83 | 85 | 88 | 89 | 91 | 92 | 94 | 95 | 98 | 100 | 101 | 102 | 104 | 109 | 110 | 111 | 115 | 117 | 119 | 121 | 122 | 124 | 126 | 128 | 129 | 130 | 133 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /public/js/global.bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { 40 | /******/ configurable: false, 41 | /******/ enumerable: true, 42 | /******/ get: getter 43 | /******/ }); 44 | /******/ } 45 | /******/ }; 46 | /******/ 47 | /******/ // getDefaultExport function for compatibility with non-harmony modules 48 | /******/ __webpack_require__.n = function(module) { 49 | /******/ var getter = module && module.__esModule ? 50 | /******/ function getDefault() { return module['default']; } : 51 | /******/ function getModuleExports() { return module; }; 52 | /******/ __webpack_require__.d(getter, 'a', getter); 53 | /******/ return getter; 54 | /******/ }; 55 | /******/ 56 | /******/ // Object.prototype.hasOwnProperty.call 57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 | /******/ 59 | /******/ // __webpack_public_path__ 60 | /******/ __webpack_require__.p = ""; 61 | /******/ 62 | /******/ // Load entry module and return exports 63 | /******/ return __webpack_require__(__webpack_require__.s = 0); 64 | /******/ }) 65 | /************************************************************************/ 66 | /******/ ([ 67 | /* 0 */ 68 | /***/ (function(module, exports, __webpack_require__) { 69 | 70 | "use strict"; 71 | eval("\n\n__webpack_require__(1);\n\n__webpack_require__(7);\n\n__webpack_require__(8);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9nbG9iYWwuanM/MWMyMCJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbnJlcXVpcmUoJy4vc2Nzcy9zdHlsZS5zY3NzJyk7XG5cbnJlcXVpcmUoJy4vaW1nL2NjLXZveWFnZXItbG9nby5zdmcnKTtcblxucmVxdWlyZSgnLi9pbWcvY2Mtdm95YWdlci5zdmcnKTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9nbG9iYWwuanNcbi8vIG1vZHVsZSBpZCA9IDBcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///0\n"); 72 | 73 | /***/ }), 74 | /* 1 */ 75 | /***/ (function(module, exports) { 76 | 77 | eval("// removed by extract-text-webpack-plugin//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9zY3NzL3N0eWxlLnNjc3M/Y2Q3NiJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyByZW1vdmVkIGJ5IGV4dHJhY3QtdGV4dC13ZWJwYWNrLXBsdWdpblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL3Njc3Mvc3R5bGUuc2Nzc1xuLy8gbW9kdWxlIGlkID0gMVxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n"); 78 | 79 | /***/ }), 80 | /* 2 */, 81 | /* 3 */, 82 | /* 4 */, 83 | /* 5 */, 84 | /* 6 */, 85 | /* 7 */ 86 | /***/ (function(module, exports, __webpack_require__) { 87 | 88 | eval("module.exports = __webpack_require__.p + \"./img/cc-voyager-logo.svg\";//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9pbWcvY2Mtdm95YWdlci1sb2dvLnN2Zz84YTg3Il0sInNvdXJjZXNDb250ZW50IjpbIm1vZHVsZS5leHBvcnRzID0gX193ZWJwYWNrX3B1YmxpY19wYXRoX18gKyBcIi4vaW1nL2NjLXZveWFnZXItbG9nby5zdmdcIjtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9pbWcvY2Mtdm95YWdlci1sb2dvLnN2Z1xuLy8gbW9kdWxlIGlkID0gN1xuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///7\n"); 89 | 90 | /***/ }), 91 | /* 8 */ 92 | /***/ (function(module, exports, __webpack_require__) { 93 | 94 | eval("module.exports = __webpack_require__.p + \"./img/cc-voyager.svg\";//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiOC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9pbWcvY2Mtdm95YWdlci5zdmc/OTU0YSJdLCJzb3VyY2VzQ29udGVudCI6WyJtb2R1bGUuZXhwb3J0cyA9IF9fd2VicGFja19wdWJsaWNfcGF0aF9fICsgXCIuL2ltZy9jYy12b3lhZ2VyLnN2Z1wiO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL2ltZy9jYy12b3lhZ2VyLnN2Z1xuLy8gbW9kdWxlIGlkID0gOFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///8\n"); 95 | 96 | /***/ }) 97 | /******/ ]); -------------------------------------------------------------------------------- /public/js/home.bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { 40 | /******/ configurable: false, 41 | /******/ enumerable: true, 42 | /******/ get: getter 43 | /******/ }); 44 | /******/ } 45 | /******/ }; 46 | /******/ 47 | /******/ // getDefaultExport function for compatibility with non-harmony modules 48 | /******/ __webpack_require__.n = function(module) { 49 | /******/ var getter = module && module.__esModule ? 50 | /******/ function getDefault() { return module['default']; } : 51 | /******/ function getModuleExports() { return module; }; 52 | /******/ __webpack_require__.d(getter, 'a', getter); 53 | /******/ return getter; 54 | /******/ }; 55 | /******/ 56 | /******/ // Object.prototype.hasOwnProperty.call 57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 | /******/ 59 | /******/ // __webpack_public_path__ 60 | /******/ __webpack_require__.p = ""; 61 | /******/ 62 | /******/ // Load entry module and return exports 63 | /******/ return __webpack_require__(__webpack_require__.s = 2); 64 | /******/ }) 65 | /************************************************************************/ 66 | /******/ ({ 67 | 68 | /***/ 2: 69 | /***/ (function(module, exports, __webpack_require__) { 70 | 71 | "use strict"; 72 | eval("//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///2\n"); 73 | 74 | /***/ }) 75 | 76 | /******/ }); -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /routes/web.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | /* GET home page. */ 5 | router.get('/', async (req, res) => { 6 | res.render('index', { title: 'Voyager' }) 7 | }) 8 | 9 | module.exports = router 10 | -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | import './scss/style.scss' 2 | import './img/cc-voyager-logo.svg' 3 | import './img/cc-voyager.svg' 4 | -------------------------------------------------------------------------------- /src/home.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/home.js -------------------------------------------------------------------------------- /src/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/img/.gitkeep -------------------------------------------------------------------------------- /src/img/cc-voyager-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 67 | 68 | 70 | 73 | 74 | 76 | 78 | 81 | 82 | 84 | 87 | 88 | 90 | 91 | -------------------------------------------------------------------------------- /src/img/cc-voyager.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 68 | 69 | 71 | 74 | 75 | 77 | 79 | 82 | 83 | 85 | 88 | 89 | 91 | 92 | 94 | 95 | 98 | 100 | 101 | 102 | 104 | 109 | 110 | 111 | 115 | 117 | 119 | 121 | 122 | 124 | 126 | 128 | 129 | 130 | 133 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/scss/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/scss/fonts/.gitkeep -------------------------------------------------------------------------------- /src/scss/fonts/lato.scss: -------------------------------------------------------------------------------- 1 | /* latin-ext */ 2 | @font-face { 3 | font-family: 'Lato'; 4 | font-style: normal; 5 | font-weight: 300; 6 | src: local('Lato Light'), local('Lato-Light'), 7 | url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwaPGQ3q5d0N7w.woff2) 8 | format('woff2'); 9 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, 10 | U+2113, U+2c60-2c7f, U+A720-A7FF; 11 | } 12 | /* latin */ 13 | @font-face { 14 | font-family: 'Lato'; 15 | font-style: normal; 16 | font-weight: 300; 17 | src: local('Lato Light'), local('Lato-Light'), 18 | url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2) 19 | format('woff2'); 20 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, 21 | U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, 22 | U+FEFF, U+FFFD; 23 | } 24 | -------------------------------------------------------------------------------- /src/scss/fonts/roboto-mono.scss: -------------------------------------------------------------------------------- 1 | /* cyrillic-ext */ 2 | @font-face { 3 | font-family: 'Roboto Mono'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 7 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2) 8 | format('woff2'); 9 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F, 10 | U+FE2E-FE2F; 11 | } 12 | /* cyrillic */ 13 | @font-face { 14 | font-family: 'Roboto Mono'; 15 | font-style: normal; 16 | font-weight: 400; 17 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 18 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2) 19 | format('woff2'); 20 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; 21 | } 22 | /* greek-ext */ 23 | @font-face { 24 | font-family: 'Roboto Mono'; 25 | font-style: normal; 26 | font-weight: 400; 27 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 28 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhHq3-cXbKDO1w.woff2) 29 | format('woff2'); 30 | unicode-range: U+1f00-1fff; 31 | } 32 | /* greek */ 33 | @font-face { 34 | font-family: 'Roboto Mono'; 35 | font-style: normal; 36 | font-weight: 400; 37 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 38 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2) 39 | format('woff2'); 40 | unicode-range: U+0370-03ff; 41 | } 42 | /* vietnamese */ 43 | @font-face { 44 | font-family: 'Roboto Mono'; 45 | font-style: normal; 46 | font-weight: 400; 47 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 48 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2) 49 | format('woff2'); 50 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab; 51 | } 52 | /* latin-ext */ 53 | @font-face { 54 | font-family: 'Roboto Mono'; 55 | font-style: normal; 56 | font-weight: 400; 57 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 58 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2) 59 | format('woff2'); 60 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, 61 | U+2113, U+2c60-2c7f, U+A720-A7FF; 62 | } 63 | /* latin */ 64 | @font-face { 65 | font-family: 'Roboto Mono'; 66 | font-style: normal; 67 | font-weight: 400; 68 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 69 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2) 70 | format('woff2'); 71 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, 72 | U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, 73 | U+FEFF, U+FFFD; 74 | } 75 | /* cyrillic-ext */ 76 | @font-face { 77 | font-family: 'Roboto Mono'; 78 | font-style: normal; 79 | font-weight: 700; 80 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 81 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq8f7-pAVU_Lrg.woff2) 82 | format('woff2'); 83 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F, 84 | U+FE2E-FE2F; 85 | } 86 | /* cyrillic */ 87 | @font-face { 88 | font-family: 'Roboto Mono'; 89 | font-style: normal; 90 | font-weight: 700; 91 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 92 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq1f7-pAVU_Lrg.woff2) 93 | format('woff2'); 94 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; 95 | } 96 | /* greek-ext */ 97 | @font-face { 98 | font-family: 'Roboto Mono'; 99 | font-style: normal; 100 | font-weight: 700; 101 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 102 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq9f7-pAVU_Lrg.woff2) 103 | format('woff2'); 104 | unicode-range: U+1f00-1fff; 105 | } 106 | /* greek */ 107 | @font-face { 108 | font-family: 'Roboto Mono'; 109 | font-style: normal; 110 | font-weight: 700; 111 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 112 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqyf7-pAVU_Lrg.woff2) 113 | format('woff2'); 114 | unicode-range: U+0370-03ff; 115 | } 116 | /* vietnamese */ 117 | @font-face { 118 | font-family: 'Roboto Mono'; 119 | font-style: normal; 120 | font-weight: 700; 121 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 122 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq-f7-pAVU_Lrg.woff2) 123 | format('woff2'); 124 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab; 125 | } 126 | /* latin-ext */ 127 | @font-face { 128 | font-family: 'Roboto Mono'; 129 | font-style: normal; 130 | font-weight: 700; 131 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 132 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq_f7-pAVU_Lrg.woff2) 133 | format('woff2'); 134 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, 135 | U+2113, U+2c60-2c7f, U+A720-A7FF; 136 | } 137 | /* latin */ 138 | @font-face { 139 | font-family: 'Roboto Mono'; 140 | font-style: normal; 141 | font-weight: 700; 142 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), 143 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqxf7-pAVU_.woff2) 144 | format('woff2'); 145 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, 146 | U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, 147 | U+FEFF, U+FFFD; 148 | } 149 | -------------------------------------------------------------------------------- /src/scss/global/_variables.scss: -------------------------------------------------------------------------------- 1 | $light-gray: #c5c5c5; 2 | $gray: #636363; 3 | $red: #ff0000; 4 | $purple: #635afe; 5 | 6 | $lato: 'Lato', sans-serif; 7 | $roboto: 'Roboto Mono', monospace; 8 | 9 | $screen-sm-min: 576px; 10 | $screen-md-min: 768px; 11 | $screen-lg-min: 992px; 12 | $screen-xl-min: 1200px; 13 | 14 | @mixin breakpoint($point) { 15 | @if $point == $screen-sm-min { 16 | @media screen and (min-width: $screen-sm-min) { 17 | @content; 18 | } 19 | } 20 | @elseif $point == $screen-md-min { 21 | @media screen and (min-width: $screen-md-min) { 22 | @content; 23 | } 24 | } 25 | @elseif $point == $screen-lg-min { 26 | @media screen and (min-width: $screen-lg-min) { 27 | @content; 28 | } 29 | } 30 | @elseif $point == $screen-xl-min { 31 | @media screen and (min-width: $screen-xl-min) { 32 | @content; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/scss/global/util.scss: -------------------------------------------------------------------------------- 1 | .m0 { 2 | margin: 0; 3 | } 4 | 5 | .mt0 { 6 | margin-top: 0; 7 | } 8 | 9 | .mb0 { 10 | margin-bottom: 0; 11 | } 12 | 13 | // Mobile Margin Top 14 | .mmt10 { 15 | margin-top: 10px; 16 | @include breakpoint($screen-sm-min) { 17 | margin-top: 0; 18 | } 19 | } 20 | 21 | .mmt25 { 22 | margin-top: 25px; 23 | @include breakpoint($screen-sm-min) { 24 | margin-top: 0; 25 | } 26 | } 27 | 28 | .mmt50 { 29 | margin-top: 50px; 30 | @include breakpoint($screen-sm-min) { 31 | margin-top: 0; 32 | } 33 | } 34 | 35 | .mmt75 { 36 | margin-top: 75px; 37 | @include breakpoint($screen-sm-min) { 38 | margin-top: 0; 39 | } 40 | } 41 | 42 | .mmt100 { 43 | margin-top: 100px; 44 | @include breakpoint($screen-sm-min) { 45 | margin-top: 0; 46 | } 47 | } 48 | 49 | // Mobile Margin Bottom 50 | .mmb10 { 51 | margin-bottom: 10px; 52 | @include breakpoint($screen-sm-min) { 53 | margin-bottom: 0; 54 | } 55 | } 56 | 57 | .mmb25 { 58 | margin-bottom: 25px; 59 | @include breakpoint($screen-sm-min) { 60 | margin-bottom: 0; 61 | } 62 | } 63 | 64 | .mmb50 { 65 | margin-bottom: 50px; 66 | @include breakpoint($screen-sm-min) { 67 | margin-bottom: 0; 68 | } 69 | } 70 | 71 | .mmb75 { 72 | margin-bottom: 75px; 73 | @include breakpoint($screen-sm-min) { 74 | margin-bottom: 0; 75 | } 76 | } 77 | 78 | .mmb100 { 79 | margin-bottom: 100px; 80 | @include breakpoint($screen-sm-min) { 81 | margin-bottom: 0; 82 | } 83 | } 84 | 85 | // Mobile Padding Top 86 | .mpt10 { 87 | padding-top: 10px; 88 | @include breakpoint($screen-sm-min) { 89 | padding-top: 0; 90 | } 91 | } 92 | 93 | .mpt25 { 94 | padding-top: 25px; 95 | @include breakpoint($screen-sm-min) { 96 | padding-top: 0; 97 | } 98 | } 99 | 100 | .mpt50 { 101 | padding-top: 50px; 102 | @include breakpoint($screen-sm-min) { 103 | padding-top: 0; 104 | } 105 | } 106 | 107 | .mpt75 { 108 | padding-top: 75px; 109 | @include breakpoint($screen-sm-min) { 110 | padding-top: 0; 111 | } 112 | } 113 | 114 | .mpt100 { 115 | padding-top: 100px; 116 | @include breakpoint($screen-sm-min) { 117 | padding-top: 0; 118 | } 119 | } 120 | 121 | // Mobile Padding Bottom 122 | .mpb10 { 123 | padding-bottom: 10px; 124 | @include breakpoint($screen-sm-min) { 125 | padding-bottom: 0; 126 | } 127 | } 128 | 129 | .mpb25 { 130 | padding-bottom: 25px; 131 | @include breakpoint($screen-sm-min) { 132 | padding-bottom: 0; 133 | } 134 | } 135 | 136 | .mpb50 { 137 | padding-bottom: 50px; 138 | @include breakpoint($screen-sm-min) { 139 | padding-bottom: 0; 140 | } 141 | } 142 | 143 | .mpb75 { 144 | padding-bottom: 75px; 145 | @include breakpoint($screen-sm-min) { 146 | padding-bottom: 0; 147 | } 148 | } 149 | 150 | .mpb100 { 151 | padding-bottom: 100px; 152 | @include breakpoint($screen-sm-min) { 153 | padding-bottom: 0; 154 | } 155 | } 156 | 157 | @include breakpoint($screen-sm-min) { 158 | // Margin Top 159 | .mt10 { 160 | margin-top: 10px; 161 | } 162 | 163 | .mt25 { 164 | margin-top: 25px; 165 | } 166 | 167 | .mt50 { 168 | margin-top: 50px; 169 | } 170 | 171 | .mt75 { 172 | margin-top: 75px; 173 | } 174 | 175 | .mt100 { 176 | margin-top: 100px; 177 | } 178 | 179 | .mt150 { 180 | margin-top: 150px; 181 | } 182 | 183 | .mt200 { 184 | margin-top: 200px; 185 | } 186 | 187 | // Margin Bottom 188 | .mb10 { 189 | margin-bottom: 10px; 190 | } 191 | 192 | .mb15 { 193 | margin-bottom: 15px; 194 | } 195 | 196 | .mb25 { 197 | margin-bottom: 25px; 198 | } 199 | 200 | .mb50 { 201 | margin-bottom: 50px; 202 | } 203 | 204 | .mb75 { 205 | margin-bottom: 75px; 206 | } 207 | 208 | .mb100 { 209 | margin-bottom: 100px; 210 | } 211 | 212 | // Padding Top 213 | .pt10 { 214 | padding-top: 10px; 215 | } 216 | 217 | .pt25 { 218 | padding-top: 25px; 219 | } 220 | 221 | .pt50 { 222 | padding-top: 50px; 223 | } 224 | 225 | .pt75 { 226 | padding-top: 75px; 227 | } 228 | 229 | .pt100 { 230 | padding-top: 100px; 231 | } 232 | 233 | .pt125 { 234 | padding-top: 125px; 235 | } 236 | 237 | .pt150 { 238 | padding-top: 150px; 239 | } 240 | 241 | .pt175 { 242 | padding-top: 175px; 243 | } 244 | 245 | .pt200 { 246 | padding-top: 200px; 247 | } 248 | 249 | // Padding Bottom 250 | .pb10 { 251 | padding-bottom: 10px; 252 | } 253 | 254 | .pb25 { 255 | padding-bottom: 25px; 256 | } 257 | 258 | .pb50 { 259 | padding-bottom: 50px; 260 | } 261 | 262 | .pb75 { 263 | padding-bottom: 75px; 264 | } 265 | 266 | .pb100 { 267 | padding-bottom: 100px; 268 | } 269 | 270 | .pb150 { 271 | padding-bottom: 150px; 272 | } 273 | 274 | .pb200 { 275 | padding-bottom: 200px; 276 | } 277 | } 278 | 279 | .hide-desktop { 280 | @include breakpoint($screen-sm-min) { 281 | display: none; 282 | } 283 | } 284 | 285 | .hide-mobile { 286 | display: none; 287 | @include breakpoint($screen-sm-min) { 288 | display: block; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/scss/pages/index.scss: -------------------------------------------------------------------------------- 1 | .index-container { 2 | height: calc(100vh - 75px); 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: center; 7 | 8 | h1 { 9 | margin-bottom: 0; 10 | } 11 | } 12 | 13 | .body-logo { 14 | width: 90%; 15 | max-width: 480px; 16 | margin-bottom: 30px; 17 | } 18 | 19 | .index-nav { 20 | a { 21 | display: inline-block; 22 | margin: 0 5px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/scss/partials/footer.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/scss/partials/footer.scss -------------------------------------------------------------------------------- /src/scss/partials/header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | padding: 12px; 3 | border-bottom: 1px solid #eee; 4 | background: white; 5 | } 6 | 7 | .header-container { 8 | display: flex; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | 13 | .header-logo { 14 | width: 100px; 15 | @include breakpoint($screen-sm-min) { 16 | width: 150px; 17 | } 18 | } 19 | 20 | .header-nav { 21 | a { 22 | display: inline-block; 23 | margin: 0 15px; 24 | font-family: $roboto; 25 | &:nth-last-child(1) { 26 | margin-right: 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import './fonts/lato'; 2 | @import './fonts/roboto-mono'; 3 | 4 | // Global 5 | @import './global/_variables'; 6 | @import './global/util'; 7 | 8 | // Partials 9 | @import './partials/header'; 10 | @import './partials/footer'; 11 | 12 | // Pages 13 | @import './pages/index'; 14 | 15 | body { 16 | margin: 0; 17 | font-family: $lato; 18 | color: #333; 19 | } 20 | 21 | a { 22 | color: #00b7ff; 23 | text-decoration: none; 24 | } 25 | 26 | .container { 27 | max-width: 1170px; 28 | margin: 0 auto; 29 | } 30 | -------------------------------------------------------------------------------- /test/app.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest') 2 | const app = require('../app') 3 | 4 | describe('GET /', function() { 5 | it('should render successfully', function(done) { 6 | request(app) 7 | .get('/') 8 | .expect(200, done) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |

{{ message }}

2 |

{{ error.status }}

3 |
{{ error.stack }}
4 | -------------------------------------------------------------------------------- /views/index.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Resources

5 | 11 | 12 | 20 |
21 | -------------------------------------------------------------------------------- /views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | {{> header }} 18 | {{{ body }}} 19 | {{> footer }} 20 | 21 | 22 | -------------------------------------------------------------------------------- /views/partials/footer.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/views/partials/footer.hbs -------------------------------------------------------------------------------- /views/partials/header.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 7 |
8 |
-------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin') 5 | 6 | module.exports = { 7 | entry: { 8 | global: './src/global.js', 9 | auth: './src/auth.js' 10 | }, 11 | output: { 12 | path: path.resolve(__dirname, 'public'), 13 | filename: './js/[name].bundle.js' 14 | }, 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.scss$/, 19 | use: ExtractTextPlugin.extract({ 20 | fallback: 'style-loader', 21 | use: 'css-loader!postcss-loader!sass-loader' 22 | }) 23 | }, 24 | { 25 | test: /\.js$/, 26 | exclude: /(node_modules|bower_components)/, 27 | use: { 28 | loader: 'babel-loader', 29 | options: { 30 | presets: ['env'] 31 | } 32 | } 33 | }, 34 | { 35 | test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/, 36 | use: [ 37 | { 38 | loader: 'file-loader', 39 | options: { 40 | name: '[name].[ext]', 41 | outputPath: './css/fonts/', 42 | publicPath: '../' 43 | } 44 | } 45 | ] 46 | }, 47 | { 48 | test: /\.(png|jpg|gif|svg)$/, 49 | exclude: /(node_modules|bower_components)/, 50 | use: [ 51 | { 52 | loader: 'file-loader', 53 | options: { 54 | name: '[name].[ext]', 55 | outputPath: './img/', 56 | publicPath: '../' 57 | } 58 | } 59 | ] 60 | } 61 | ] 62 | }, 63 | plugins: [ 64 | // new webpack.HotModuleReplacementPlugin(), // Enable HMR 65 | new ExtractTextPlugin('css/style.css'), 66 | new BrowserSyncPlugin( 67 | { 68 | host: 'localhost', 69 | port: 3001, 70 | proxy: 'http://localhost:3000/', 71 | files: [ 72 | { 73 | match: ['**/*.hbs'], 74 | fn: function(event) { 75 | if (event === 'change') { 76 | const bs = require('browser-sync').get( 77 | 'bs-webpack-plugin' 78 | ) 79 | bs.reload() 80 | } 81 | } 82 | } 83 | ] 84 | }, 85 | { 86 | reload: false 87 | } 88 | ) 89 | ], 90 | resolve: { 91 | alias: { 92 | vue: 'vue/dist/vue.js', 93 | '@': path.join(__dirname, 'src') 94 | } 95 | }, 96 | watch: true, 97 | devtool: 'cheap-eval-source-map' 98 | } 99 | 100 | if (process.env.NODE_ENV === 'production') { 101 | module.exports.devtool = '#source-map' 102 | module.exports.plugins = (module.exports.plugins || []).concat([ 103 | new webpack.DefinePlugin({ 104 | 'process.env': { 105 | NODE_ENV: '"production"' 106 | } 107 | }), 108 | new webpack.optimize.UglifyJsPlugin({ 109 | sourceMap: true, 110 | compress: { 111 | warnings: false 112 | } 113 | }), 114 | new webpack.LoaderOptionsPlugin({ 115 | minimize: true 116 | }) 117 | ]) 118 | } 119 | --------------------------------------------------------------------------------