Vue Project Setup (#8018)

* add files for new client side and reorg gulp tasks

* add deps and script for new client

* fix webpack paths so that building works

* fix static assets not copied into prod build

* fix linting

* add eslint deps and re-enable it in webpack

* add most missing deps for client side and split .babelrc for client

* reorganize .eslintignore

* update client tests paths and .gitignore

* uncomment code

* client: move App component

* client: update oaths in App component

* fix client tests and add more deps

* add client side tests to npm test

* fix typo in depencency name

* update more deps

* fix karma.conf.js and upgrade phantomjs

* fix dep and move karma.conf to subdirectory

* update karma.conf.js position in Gruntfile

* try downgrading phantomjs

* Fixup client tests (#8032)

* Use phantom 2

* fix(tests): Fix refresher test

* gitignore translation mock

* Update karma version

* disable e2e tests for new client from build

* write vue templates with pug

* add basic routing

* remove unnecessary Function.bind shim

* remove unused dependency
This commit is contained in:
Matteo Pagliazzi 2016-09-18 21:51:20 +02:00 committed by GitHub
parent c615af82f8
commit 4f3a9802c1
59 changed files with 1095 additions and 216 deletions

View file

@ -5,23 +5,22 @@ coverage/
database_reports/
website/build/
website/transpiled-babel/
dist/
# Not linted
migrations/*
# The files in website/client-old/js should be moved out and browserified
website/client-old/
# Temporarilly disabled. These should be removed when the linting errors are fixed
website/common/script/content/index.js
website/common/browserify.js
debug-scripts/*
scripts/*
tasks/*.js
gulpfile.js
Gruntfile.js
newrelic.js
test/content/**/*
test/server_side/**/*
test/client-old/spec/**/*
# Temporarilly disabled. These should be removed when the linting errors are fixed TODO
website/common/script/content/index.js
website/common/browserify.js
test/content/**/*
Gruntfile.js
gulpfile.js
gulp
webpack
test/client

View file

@ -1,10 +1,10 @@
{
"root": true,
"env": {
"node": true,
},
"extends": [
"habitrpg/server",
"habitrpg/babel"
"habitrpg/es6",
"habitrpg"
],
"globals": {
"Promise": true,
"Set": false
}
}

6
.gitignore vendored
View file

@ -32,8 +32,10 @@ website/client-old/docs
coverage
coverage.html
common/dist/scripts/*
test/spec/mocks/translations.js
dist
test/client/unit/coverage
test/client/e2e/reports
test/client-old/spec/mocks/translations.js
# Elastic Beanstalk Files
.elasticbeanstalk/*

View file

@ -6,6 +6,7 @@ website/client-old/**
website/client/**
website/views/**
website/build/**
dist/**
test/**
.git/**
Gruntfile.js

View file

@ -9,10 +9,10 @@ module.exports = function(grunt) {
karma: {
unit: {
configFile: 'karma.conf.js'
configFile: 'test/client-old/spec/karma.conf.js'
},
continuous: {
configFile: 'karma.conf.js',
configFile: 'test/client-old/spec/karma.conf.js',
singleRun: true,
autoWatch: false
}

File diff suppressed because one or more lines are too long

10
gulp/.eslintrc Normal file
View file

@ -0,0 +1,10 @@
{
"root": true,
"env": {
"node": true,
},
"extends": [
"habitrpg/server",
"habitrpg/babel"
],
}

View file

@ -15,7 +15,7 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
});
if (result === false) {
done(new Error('There was a problem generating apiDoc documentation.'))
done(new Error('There was a problem generating apiDoc documentation.'));
} else {
done();
}

View file

@ -10,11 +10,11 @@ let improveRepl = (context) => {
// Let "exit" and "quit" terminate the console
['exit', 'quit'].forEach((term) => {
Object.defineProperty(context, term, { get() { process.exit(); }});
Object.defineProperty(context, term, { get () { process.exit(); }});
});
// "clear" clears the screen
Object.defineProperty(context, 'clear', { get() {
Object.defineProperty(context, 'clear', { get () {
process.stdout.write('\u001B[2J\u001B[0;0f');
}});
@ -25,13 +25,13 @@ let improveRepl = (context) => {
var isProd = nconf.get('NODE_ENV') === 'production';
var mongooseOptions = !isProd ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
};
autoinc.init(
mongoose.connect(
nconf.get('NODE_DB_URI'),
mongooseOptions,
function(err) {
function (err) {
if (err) throw err;
logger.info('Connected with Mongoose');
}
@ -42,6 +42,6 @@ let improveRepl = (context) => {
gulp.task('console', (cb) => {
improveRepl(repl.start({
prompt: 'Habitica > '
prompt: 'Habitica > ',
}).context);
});

View file

@ -52,7 +52,7 @@ gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSpri
}
});
function createSpritesStream(name, src) {
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
@ -83,7 +83,7 @@ function createSpritesStream(name, src) {
return stream;
}
function calculateSpritesheetsSrcIndicies(src) {
function calculateSpritesheetsSrcIndicies (src) {
let totalPixels = 0;
let slices = [0];
@ -100,7 +100,7 @@ function calculateSpritesheetsSrcIndicies(src) {
return slices;
}
function calculateImgDimensions(img, addPadding) {
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
let requiresSpecialTreatment = checkForSpecialTreatment(img);
@ -109,7 +109,7 @@ function calculateImgDimensions(img, addPadding) {
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight
height: newHeight,
};
}
@ -119,19 +119,19 @@ function calculateImgDimensions(img, addPadding) {
padding = (dims.width * 8) + (dims.height * 8);
}
if(!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
let totalPixelSize = (dims.width * dims.height) + padding;
return totalPixelSize;
}
function checkForSpecialTreatment(name) {
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
function cssVarMap(sprite) {
function cssVarMap (sprite) {
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
// 60x60 image pointing at the proper part of the 90x90 sprite.
// We set up the custom info here, and the template makes use of it.
@ -142,9 +142,9 @@ function cssVarMap(sprite) {
offset_x: `-${ sprite.x + 25 }px`,
offset_y: `-${ sprite.y + 15 }px`,
width: '60px',
height: '60px'
}
}
height: '60px',
},
};
}
if (~sprite.name.indexOf('shirt'))
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts

View file

@ -12,6 +12,6 @@ gulp.task('nodemon', () => {
'website/client-old/*',
'website/views/*',
'common/dist/script/content/*',
]
],
});
});

View file

@ -16,7 +16,7 @@ import nconf from 'nconf';
// TODO rewrite
const TEST_SERVER_PORT = 3003
const TEST_SERVER_PORT = 3003;
let server;
const TEST_DB_URI = nconf.get('TEST_DB_URI');
@ -33,11 +33,11 @@ let testResults = [];
let testCount = (stdout, regexp) => {
let match = stdout.match(regexp);
return parseInt(match && match[1] || 0);
}
};
let testBin = (string, additionalEnvVariables = '') => {
if(os.platform() === "win32") {
if(additionalEnvVariables != '') {
if (os.platform() === 'win32') {
if (additionalEnvVariables != '') {
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
}
@ -49,7 +49,7 @@ let testBin = (string, additionalEnvVariables = '') => {
gulp.task('test:nodemon', (done) => {
process.env.PORT = TEST_SERVER_PORT;
process.env.NODE_DB_URI=TEST_DB_URI;
process.env.NODE_DB_URI = TEST_DB_URI;
runSequence('nodemon');
});
@ -65,7 +65,7 @@ gulp.task('test:prepare:mongo', (cb) => {
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
if (!server) {
server = exec(testBin(`node ./website/server/index.js`, `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
if (error) { throw `Problem with the server: ${error}`; }
if (stderr) { console.error(stderr); }
});
@ -83,7 +83,7 @@ gulp.task('test:prepare:webdriver', (cb) => {
gulp.task('test:prepare', [
'test:prepare:build',
'test:prepare:mongo',
'test:prepare:webdriver'
'test:prepare:webdriver',
]);
gulp.task('test:sanity', (cb) => {
@ -128,7 +128,7 @@ gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
suite: 'Common Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
pend: testCount(stdout, /(\d+) pending/),
});
cb();
}
@ -167,7 +167,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
suite: 'Content Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
pend: testCount(stdout, /(\d+) pending/),
});
cb();
}
@ -193,7 +193,7 @@ gulp.task('test:server_side:safe', ['test:prepare:build'], (cb) => {
suite: 'Server Side Specs',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
pend: testCount(stdout, /(\d+) pending/),
});
cb();
}
@ -232,7 +232,7 @@ gulp.task('test:karma:safe', ['test:prepare:build'], (cb) => {
suite: 'Karma Specs\t',
pass: testCount(stdout, /(\d+) tests? completed/),
fail: testCount(stdout, /(\d+) tests? failed/),
pend: testCount(stdout, /(\d+) tests? skipped/)
pend: testCount(stdout, /(\d+) tests? skipped/),
});
cb();
}
@ -249,7 +249,7 @@ gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => {
Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444)
awaitPort(4444),
]).then(() => {
let runner = exec(
'npm run test:e2e',
@ -273,7 +273,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444)
awaitPort(4444),
]).then(() => {
let runner = exec(
'npm run test:e2e',
@ -284,7 +284,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
suite: 'End-to-End Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
pend: testCount(stdout, /(\d+) pending/),
});
support.forEach(kill);
cb();
@ -303,7 +303,7 @@ gulp.task('test:api-v3:unit', (done) => {
}
done();
}
)
);
pipe(runner);
});
@ -315,14 +315,14 @@ gulp.task('test:api-v3:unit:watch', () => {
gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('mocha test/api/v3/integration --recursive'),
{maxBuffer: 500*1024},
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => {
if (err) {
process.exit(1);
}
done();
}
)
);
pipe(runner);
});
@ -335,9 +335,9 @@ gulp.task('test:api-v3:integration:watch', () => {
gulp.task('test:api-v3:integration:separate-server', (done) => {
let runner = exec(
testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
{maxBuffer: 500*1024},
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => done(err)
)
);
pipe(runner);
});

View file

@ -7,8 +7,8 @@ import { postToSlack, conf } from './taskHelper';
const SLACK_CONFIG = {
channel: conf.get('TRANSIFEX_SLACK_CHANNEL'),
username: 'Transifex',
emoji: 'transifex'
}
emoji: 'transifex',
};
const LOCALES = './website/common/locales/';
const ENGLISH_LOCALE = `${LOCALES}en/`;
@ -17,8 +17,8 @@ const ALL_LANGUAGES = getArrayOfLanguages();
const malformedStringExceptions = {
messageDropFood: true,
armoireFood: true,
feedPet: true
}
feedPet: true,
};
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
@ -27,7 +27,7 @@ gulp.task('transifex:missingFiles', () => {
let missingStrings = [];
eachTranslationFile(ALL_LANGUAGES, (error) => {
if(error) {
if (error) {
missingStrings.push(error.path);
}
});
@ -67,13 +67,13 @@ gulp.task('transifex:malformedStrings', () => {
let stringsWithIncorrectNumberOfInterpolations = [];
let count = 0;
_(ALL_LANGUAGES).each(function(lang) {
_(ALL_LANGUAGES).each(function (lang) {
_.each(stringsToLookFor, function(strings, file) {
_.each(stringsToLookFor, function (strings, file) {
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
let parsedTranslationFile = JSON.parse(translationFile);
_.each(strings, function(value, key) {
_.each(strings, function (value, key) {
let translationString = parsedTranslationFile[key];
if (!translationString) return;
@ -104,14 +104,14 @@ gulp.task('transifex:malformedStrings', () => {
}
});
function getArrayOfLanguages() {
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
function eachTranslationFile(languages, cb) {
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_(languages).each((lang) => {
@ -126,12 +126,12 @@ function eachTranslationFile(languages, cb) {
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile)
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
}).value();
}
function eachTranslationString(languages, cb) {
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
@ -141,7 +141,7 @@ function eachTranslationString(languages, cb) {
});
}
function formatMessageForPosting(msg, items) {
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
@ -150,24 +150,24 @@ function formatMessageForPosting(msg, items) {
return body;
}
function getStringsWith(json, interpolationRegex) {
function getStringsWith (json, interpolationRegex) {
var strings = {};
_(json).each(function(file_name) {
_(json).each(function (file_name) {
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
var parsed_json = JSON.parse(raw_file);
strings[file_name] = {};
_.each(parsed_json, function(value, key) {
_.each(parsed_json, function (value, key) {
var match = value.match(interpolationRegex);
if(match) strings[file_name][key] = match;
if (match) strings[file_name][key] = match;
});
}).value();
return strings;
}
function stripOutNonJsonFiles(collection) {
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});

View file

@ -19,23 +19,23 @@ export var conf = nconf;
* This is necessary to ensure that Gulp will terminate when it has completed
* its tasks.
*/
export function kill(proc) {
export function kill (proc) {
let killProcess = (pid) => {
psTree(pid, (_, pids) => {
if(pids.length) {
pids.forEach(kill); return
if (pids.length) {
pids.forEach(kill); return;
}
try {
exec(/^win/.test(process.platform)
? `taskkill /PID ${pid} /T /F`
: `kill -9 ${pid}`)
: `kill -9 ${pid}`);
}
catch(e) { console.log(e) }
catch (e) { console.log(e); }
});
}
};
killProcess(proc.PID || proc.pid);
};
}
/*
* Return a promise that will execute when Node is able to connect on a
@ -43,7 +43,7 @@ export function kill(proc) {
* has fully spun up. Optionally provide a maximum number of seconds to wait
* before failing.
*/
export function awaitPort (port, max=60) {
export function awaitPort (port, max = 60) {
return new Bluebird((reject, resolve) => {
let socket, timeout, interval;
@ -58,23 +58,23 @@ export function awaitPort (port, max=60) {
clearTimeout(timeout);
socket.destroy();
resolve();
}).on('error', () => { socket.destroy });
}).on('error', () => { socket.destroy; });
}, 1000);
});
};
}
/*
* Pipe the child's stdin and stderr to the parent process.
*/
export function pipe(child) {
child.stdout.on('data', (data) => { process.stdout.write(data) });
child.stderr.on('data', (data) => { process.stderr.write(data) });
};
export function pipe (child) {
child.stdout.on('data', (data) => { process.stdout.write(data); });
child.stderr.on('data', (data) => { process.stderr.write(data); });
}
/*
* Post request to notify configured slack channel
*/
export function postToSlack(msg, config={}) {
export function postToSlack (msg, config = {}) {
let slackUrl = nconf.get('SLACK_URL');
if (!slackUrl) {
@ -89,14 +89,14 @@ export function postToSlack(msg, config={}) {
channel: `#${config.channel || '#general'}`,
username: config.username || 'gulp task',
text: msg,
icon_emoji: `:${config.emoji || 'gulp'}:`
icon_emoji: `:${config.emoji || 'gulp'}:`,
})
.end((err, res) => {
if (err) console.error('Unable to post to slack', err);
});
}
export function runMochaTests(files, server, cb) {
export function runMochaTests (files, server, cb) {
require('../test/helpers/globals.helper');
let mocha = new Mocha({reporter: 'spec'});

View file

@ -9,11 +9,11 @@
require('babel-register');
if (process.env.NODE_ENV === 'production') {
require('./tasks/gulp-apidoc');
require('./tasks/gulp-newstuff');
require('./tasks/gulp-build');
require('./tasks/gulp-babelify');
require('./gulp/gulp-apidoc');
require('./gulp/gulp-newstuff');
require('./gulp/gulp-build');
require('./gulp/gulp-babelify');
} else {
require('glob').sync('./tasks/gulp-*').forEach(require);
require('glob').sync('./gulp/gulp-*').forEach(require);
require('gulp').task('default', ['test']);
}

View file

@ -1,95 +0,0 @@
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function karmaConfig (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
// list of files / patterns to load in the browser
files: [
'website/client-old/bower_components/jquery/dist/jquery.js',
'website/client-old/bower_components/pnotify/jquery.pnotify.js',
'website/client-old/bower_components/angular/angular.js',
'website/client-old/bower_components/angular-loading-bar/build/loading-bar.min.js',
'website/client-old/bower_components/angular-resource/angular-resource.min.js',
'website/client-old/bower_components/hello/dist/hello.all.min.js',
'website/client-old/bower_components/angular-sanitize/angular-sanitize.js',
'website/client-old/bower_components/bootstrap/dist/js/bootstrap.js',
'website/client-old/bower_components/angular-bootstrap/ui-bootstrap.js',
'website/client-old/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'website/client-old/bower_components/angular-ui-router/release/angular-ui-router.js',
'website/client-old/bower_components/angular-filter/dist/angular-filter.js',
'website/client-old/bower_components/angular-ui/build/angular-ui.js',
'website/client-old/bower_components/angular-ui-utils/ui-utils.min.js',
'website/client-old/bower_components/Angular-At-Directive/src/at.js',
'website/client-old/bower_components/Angular-At-Directive/src/caret.js',
'website/client-old/bower_components/angular-mocks/angular-mocks.js',
'website/client-old/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
'website/client-old/bower_components/select2/select2.js',
'website/client-old/bower_components/angular-ui-select2/src/select2.js',
'website/client-old/bower_components/habitica-markdown/dist/habitica-markdown.min.js',
'website/client-old/js/habitrpg-shared.js',
'test/client-old/spec/mocks/**/*.js',
'website/client-old/js/env.js',
'website/client-old/js/app.js',
'common/script/public/config.js',
'common/script/public/directives.js',
'website/client-old/js/services/**/*.js',
'website/client-old/js/filters/**/*.js',
'website/client-old/js/directives/**/*.js',
'website/client-old/js/controllers/**/*.js',
'test/client-old/spec/specHelper.js',
'test/client-old/spec/**/*.js',
],
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
preprocessors: {
'website/client-old/js/**/*.js': ['coverage'],
'test/**/*.js': ['babel'],
},
coverageReporter: {
type: 'lcov',
dir: 'coverage/karma',
},
// Enable mocha-style reporting, for better test visibility
reporters: ['mocha', 'coverage'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false,
});
};

7
npm-shrinkwrap.json generated
View file

@ -655,7 +655,7 @@
},
"babel-runtime": {
"version": "6.11.6",
"from": "babel-runtime@>=6.0.0 <7.0.0",
"from": "babel-runtime@>=6.11.6 <7.0.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.11.6.tgz"
},
"babel-template": {
@ -8820,6 +8820,11 @@
"from": "void-elements@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz"
},
"vue": {
"version": "2.0.0-rc.6",
"from": "vue@>=2.0.0-rc.6 <3.0.0",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.0.0-rc.6.tgz"
},
"w3counter": {
"version": "2.0.1",
"from": "w3counter@>=2.0.0 <3.0.0",

View file

@ -16,6 +16,7 @@
"babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"babelify": "^7.2.0",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
@ -93,6 +94,8 @@
"validator": "^4.9.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.0.0-rc.6",
"vue-router": "^2.0.0-rc.5",
"winston": "^2.1.0",
"xml2js": "^0.4.4"
},
@ -102,8 +105,8 @@
"npm": "^3.8.9"
},
"scripts": {
"lint": "eslint .",
"test": "npm run lint && gulp test",
"lint": "eslint --ext .js,.vue .",
"test": "npm run lint && gulp test && npm run client:unit",
"test:api-v3": "gulp test:api-v3",
"test:api-v3:unit": "gulp test:api-v3:unit",
"test:api-v3:integration": "gulp test:api-v3:integration",
@ -111,8 +114,8 @@
"test:sanity": "mocha test/sanity --recursive",
"test:common": "mocha test/common --recursive",
"test:content": "mocha test/content --recursive",
"test:karma": "karma start --single-run",
"test:karma:watch": "karma start",
"test:karma": "karma start test/client-old/spec/karma.conf.js --single-run",
"test:karma:watch": "karma start test/client-old/spec/karma.conf.js",
"test:prepare:webdriver": "webdriver-manager update",
"test:e2e:webdriver": "webdriver-manager start",
"test:e2e": "protractor test/client-old/e2e/protractor.conf.js",
@ -120,43 +123,83 @@
"start": "gulp run:dev",
"sprites": "gulp sprites:compile",
"postinstall": "bower --config.interactive=false install -f; gulp build;",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html"
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
"client:dev": "node webpack/dev-server.js",
"client:build": "node webpack/build.js",
"client:unit": "karma start test/client/unit/karma.conf.js --single-run",
"client:e2e": "node test/client/e2e/runner.js",
"client:test": "npm run client:unit && npm run client:e2e"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^6.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"chai": "^3.4.0",
"chai-as-promised": "^5.1.0",
"chalk": "^1.1.3",
"chromedriver": "^2.21.2",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^2.11.2",
"cross-spawn": "^2.1.5",
"css-loader": "^0.23.0",
"csv": "~0.3.6",
"deep-diff": "~0.1.4",
"eslint": "~2.12.0",
"eslint-config-habitrpg": "^1.0.0",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.3.0",
"eslint-plugin-babel": "^3.0.0",
"eslint-plugin-html": "^1.3.0",
"eslint-plugin-mocha": "^2.1.0",
"event-stream": "^3.2.2",
"eventsource-polyfill": "^0.9.6",
"expect.js": "~0.2.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.4",
"html-webpack-plugin": "^2.8.1",
"http-proxy-middleware": "^0.12.0",
"inject-loader": "^2.0.1",
"isparta-loader": "^2.0.0",
"istanbul": "^0.3.14",
"karma": "~0.13.15",
"json-loader": "^0.5.4",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
"karma-chai-plugins": "~0.6.0",
"karma-coverage": "^0.5.3",
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.1",
"karma-phantomjs-launcher": "~0.2.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sinon-chai": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.24",
"karma-webpack": "^1.7.0",
"lcov-result-merger": "^1.0.2",
"lolex": "^1.4.0",
"mocha": "^2.3.3",
"mongodb": "^2.0.46",
"mongoskin": "~2.1.0",
"phantomjs": "^1.9",
"nightwatch": "^0.8.18",
"ora": "^0.2.0",
"phantomjs-prebuilt": "^2.1.12",
"protractor": "^3.1.1",
"pug": "^2.0.0-beta6",
"require-again": "^2.0.0",
"rewire": "^2.3.3",
"shelljs": "^0.7.0",
"selenium-server": "2.53.0",
"shelljs": "^0.6.0",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0",
"superagent-defaults": "^0.1.13",
"url-loader": "^0.5.7",
"vinyl-source-stream": "^1.0.0",
"vinyl-transform": "^1.0.0"
"vinyl-transform": "^1.0.0",
"vue-hot-reload-api": "^1.2.0",
"vue-loader": "^9.4.0",
"webpack": "^1.12.2",
"webpack-dev-middleware": "^1.4.0",
"webpack-hot-middleware": "^2.6.0",
"webpack-merge": "^0.8.3"
}
}

View file

@ -3,8 +3,10 @@
"habitrpg/mocha",
"habitrpg/babel"
],
"env": {
"node": true,
},
"globals": {
"_": true,
"Promise": true
}
}

View file

@ -1,5 +1,5 @@
import fs from 'fs';
import { resetHabiticaDB } from '../../helpers/api-integration/mongo';
import { resetHabiticaDB } from '../../helpers/mongo';
before(async () => {
await resetHabiticaDB();

View file

@ -24,7 +24,7 @@ describe('AppJS', function() {
it('should call refresher if idle time is 6 hours or greater', function() {
window.awaitIdle();
clock.tick(21600000);
clock.tick(21900000);
expect(window.refresher).to.be.called;
});
});

View file

@ -0,0 +1,94 @@
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function karmaConfig (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
// list of files / patterns to load in the browser
files: [
'../../../website/client-old/bower_components/jquery/dist/jquery.js',
'../../../website/client-old/bower_components/pnotify/jquery.pnotify.js',
'../../../website/client-old/bower_components/angular/angular.js',
'../../../website/client-old/bower_components/angular-loading-bar/build/loading-bar.min.js',
'../../../website/client-old/bower_components/angular-resource/angular-resource.min.js',
'../../../website/client-old/bower_components/hello/dist/hello.all.min.js',
'../../../website/client-old/bower_components/angular-sanitize/angular-sanitize.js',
'../../../website/client-old/bower_components/bootstrap/dist/js/bootstrap.js',
'../../../website/client-old/bower_components/angular-bootstrap/ui-bootstrap.js',
'../../../website/client-old/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'../../../website/client-old/bower_components/angular-ui-router/release/angular-ui-router.js',
'../../../website/client-old/bower_components/angular-filter/dist/angular-filter.js',
'../../../website/client-old/bower_components/angular-ui/build/angular-ui.js',
'../../../website/client-old/bower_components/angular-ui-utils/ui-utils.min.js',
'../../../website/client-old/bower_components/Angular-At-Directive/src/at.js',
'../../../website/client-old/bower_components/Angular-At-Directive/src/caret.js',
'../../../website/client-old/bower_components/angular-mocks/angular-mocks.js',
'../../../website/client-old/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
'../../../website/client-old/bower_components/select2/select2.js',
'../../../website/client-old/bower_components/angular-ui-select2/src/select2.js',
'../../../website/client-old/bower_components/habitica-markdown/dist/habitica-markdown.min.js',
'../../../website/client-old/js/habitrpg-shared.js',
'../../../test/client-old/spec/mocks/**/*.js',
'../../../website/client-old/js/env.js',
'../../../website/client-old/js/app.js',
'../../../website/client-old/js/config.js',
'../../../website/client-old/js/services/**/*.js',
'../../../website/client-old/js/filters/**/*.js',
'../../../website/client-old/js/directives/**/*.js',
'../../../website/client-old/js/controllers/**/*.js',
'../../../test/client-old/spec/specHelper.js',
'../../../test/client-old/spec/**/*.js',
],
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
preprocessors: {
'../../../website/client-old/js/**/*.js': ['coverage'],
'../../../test/**/*.js': ['babel'],
},
coverageReporter: {
type: 'lcov',
dir: 'coverage/karma',
},
// Enable mocha-style reporting, for better test visibility
reporters: ['mocha', 'coverage'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false,
});
};

File diff suppressed because one or more lines are too long

3
test/client/README.md Normal file
View file

@ -0,0 +1,3 @@
This folder contains the test files for the new client side that is being developed.
The old client side tests can be found in /test/client-old.

View file

@ -0,0 +1,26 @@
// A custom Nightwatch assertion.
// the name of the method is the filename.
// can be used in tests like this:
//
// browser.assert.elementCount(selector, count)
//
// for how to write custom assertions see
// http://nightwatchjs.org/guide#writing-custom-assertions
exports.assertion = function (selector, count) {
this.message = 'Testing if element <' + selector + '> has count: ' + count;
this.expected = count;
this.pass = function (val) {
return val === this.expected;
};
this.value = function (res) {
return res.value;
};
this.command = function (cb) {
var self = this;
return this.api.execute(function (selector) {
return document.querySelectorAll(selector).length;
}, [selector], function (res) {
cb.call(self, res);
});
};
};

View file

@ -0,0 +1,46 @@
require('babel-register');
var config = require('../../../webpack/config');
// http://nightwatchjs.org/guide#settings-file
module.exports = {
'src_folders': ['test/client/e2e/specs'],
'output_folder': 'test/client/e2e/reports',
'custom_assertions_path': ['test/client/e2e/custom-assertions'],
'selenium': {
'start_process': true,
'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.0.jar',
'host': '127.0.0.1',
'port': 4444,
'cli_args': {
'webdriver.chrome.driver': require('chromedriver').path,
},
},
'test_settings': {
'default': {
'selenium_port': 4444,
'selenium_host': 'localhost',
'silent': true,
'globals': {
'devServerURL': 'http://localhost:' + (process.env.PORT || config.dev.port),
},
},
'chrome': {
'desiredCapabilities': {
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
},
},
'firefox': {
'desiredCapabilities': {
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
},
},
},
};

31
test/client/e2e/runner.js Normal file
View file

@ -0,0 +1,31 @@
// 1. start the dev server using production config
process.env.NODE_ENV = 'testing';
var server = require('../../../webpack/dev-server.js');
// 2. run the nightwatch test suite against it
// to run in additional browsers:
// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
// 2. add it to the --env flag below
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
// For more information on Nightwatch's config file, see
// http://nightwatchjs.org/guide#settings-file
var opts = process.argv.slice(2);
if (opts.indexOf('--config') === -1) {
opts = opts.concat(['--config', 'test/client/e2e/nightwatch.conf.js']);
}
if (opts.indexOf('--env') === -1) {
opts = opts.concat(['--env', 'chrome']);
}
var spawn = require('cross-spawn');
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' });
runner.on('exit', function (code) {
server.close();
process.exit(code);
});
runner.on('error', function (err) {
server.close();
throw err;
});

View file

@ -0,0 +1,20 @@
// For authoring Nightwatch tests, see
// http://nightwatchjs.org/guide#usage
module.exports = {
'default e2e tests': function (browser) {
// automatically uses dev Server port from /config.index.js
// default: http://localhost:8080
// see nightwatch.conf.js
var devServer = browser.globals.devServerURL;
browser
.url(devServer)
.waitForElementVisible('#app', 5000)
.assert.elementPresent('.logo')
.assert.containsText('h1', 'Hello Vue!')
.assert.elementCount('p', 3)
.end();
},
};

View file

@ -0,0 +1,7 @@
// require all test files (files that ends with .spec.js)
var testsContext = require.context('./specs', true, /\.spec$/);
testsContext.keys().forEach(testsContext);
// require all src files except main.js/ README.md / index.html for coverage.
var srcContext = require.context('../../../website/client', true, /^\.\/(?!(main\.js|README\.md|index\.html)?$)/);
srcContext.keys().forEach(srcContext);

View file

@ -0,0 +1,75 @@
// This is a karma config file. For more details see
// http://karma-runner.github.io/0.13/config/configuration-file.html
// we are also using it with karma-webpack
// https://github.com/webpack/karma-webpack
var path = require('path');
var merge = require('webpack-merge');
var baseConfig = require('../../../webpack/webpack.base.conf');
var utils = require('../../../webpack/utils');
var webpack = require('webpack');
var projectRoot = path.resolve(__dirname, '../../../');
var webpackConfig = merge(baseConfig, {
// use inline sourcemap for karma-sourcemap-loader
module: {
loaders: utils.styleLoaders(),
},
devtool: '#inline-source-map',
vue: {
loaders: {
js: 'isparta',
},
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../../../webpack/config/test.env'),
}),
],
});
// no need for app entry during tests
delete webpackConfig.entry;
// make sure isparta loader is applied before eslint
webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || [];
webpackConfig.module.preLoaders.unshift({
test: /\.js$/,
loader: 'isparta',
include: path.resolve(projectRoot, 'website/client'),
});
// only apply babel for test files when using isparta
webpackConfig.module.loaders.some(function (loader, i) {
if (loader.loader === 'babel') {
loader.include = path.resolve(projectRoot, 'test/client/unit');
return true;
}
});
module.exports = function (config) {
config.set({
// to run in additional browsers:
// 1. install corresponding karma launcher
// http://karma-runner.github.io/0.13/config/browsers.html
// 2. add it to the `browsers` array below.
browsers: ['PhantomJS'],
frameworks: ['mocha', 'sinon-chai'],
reporters: ['spec', 'coverage'],
files: ['./index.js'],
preprocessors: {
'./index.js': ['webpack', 'sourcemap'],
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true,
},
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' },
],
},
});
};

View file

@ -0,0 +1,12 @@
import Vue from 'vue';
import Hello from 'src/components/Hello';
describe('Hello.vue', () => {
it('should render correct contents', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(Hello),
});
expect(vm.$el.querySelector('.hello h1').textContent).to.equal('Hello Vue!');
});
});

35
webpack/build.js Normal file
View file

@ -0,0 +1,35 @@
// https://github.com/shelljs/shelljs
require('shelljs/global');
env.NODE_ENV = 'production';
var path = require('path');
var config = require('./config');
var ora = require('ora');
var webpack = require('webpack');
var webpackConfig = require('./webpack.prod.conf');
console.log(
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
);
var spinner = ora('building for production...');
spinner.start();
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory);
rm('-rf', assetsPath);
mkdir('-p', assetsPath);
cp('-R', config.build.staticAssetsDirectory, assetsPath);
webpack(webpackConfig, function (err, stats) {
spinner.stop();
if (err) throw err;
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
}) + '\n');
});

View file

@ -0,0 +1,6 @@
var merge = require('webpack-merge');
var prodEnv = require('./prod.env');
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
});

35
webpack/config/index.js Normal file
View file

@ -0,0 +1,35 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path');
var staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
staticAssetsDirectory: staticAssetsDirectory,
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
},
dev: {
env: require('./dev.env'),
port: 8080,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
staticAssetsDirectory: staticAssetsDirectory,
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false,
},
};

View file

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"',
};

View file

@ -0,0 +1,6 @@
var merge = require('webpack-merge');
var devEnv = require('./dev.env');
module.exports = merge(devEnv, {
NODE_ENV: '"testing"',
});

9
webpack/dev-client.js Normal file
View file

@ -0,0 +1,9 @@
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})

65
webpack/dev-server.js Normal file
View file

@ -0,0 +1,65 @@
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./config');
var proxyMiddleware = require('http-proxy-middleware');
var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf');
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port;
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable;
var app = express();
var compiler = webpack(webpackConfig);
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false,
},
});
var hotMiddleware = require('webpack-hot-middleware')(compiler);
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' });
cb();
});
});
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context];
if (typeof options === 'string') {
options = { target: options };
}
app.use(proxyMiddleware(context, options));
});
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')());
// serve webpack bundle output
app.use(devMiddleware);
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware);
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
app.use(staticPath, express.static(config.dev.staticAssetsDirectory));
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err);
return;
}
console.log('Listening at http://localhost:' + port + '\n');
});

59
webpack/utils.js Normal file
View file

@ -0,0 +1,59 @@
var path = require('path');
var config = require('./config');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory;
return path.posix.join(assetsSubDirectory, _path);
};
exports.cssLoaders = function (options) {
options = options || {};
// generate loader string to be used with extract text plugin
function generateLoaders (loaders) {
var sourceLoader = loaders.map(function (loader) {
var extraParamChar;
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?');
extraParamChar = '&';
} else {
loader = loader + '-loader';
extraParamChar = '?';
}
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '');
}).join('!');
if (options.extract) {
return ExtractTextPlugin.extract('vue-style-loader', sourceLoader);
} else {
return ['vue-style-loader', sourceLoader].join('!');
}
}
// http://vuejs.github.io/vue-loader/configurations/extract-css.html
return {
css: generateLoaders(['css']),
postcss: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
stylus: generateLoaders(['css', 'stylus']),
styl: generateLoaders(['css', 'stylus']),
};
};
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = [];
var loaders = exports.cssLoaders(options);
for (var extension in loaders) {
var loader = loaders[extension];
output.push({
test: new RegExp('\\.' + extension + '$'),
loader: loader,
});
}
return output;
};

View file

@ -0,0 +1,86 @@
var path = require('path');
var config = require('./config');
var utils = require('./utils');
var projectRoot = path.resolve(__dirname, '../');
module.exports = {
entry: {
app: './website/client/main.js',
},
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
filename: '[name].js',
},
resolve: {
extensions: ['', '.js', '.vue'],
fallback: [path.join(__dirname, '../node_modules')],
alias: {
'src': path.resolve(__dirname, '../website/client'),
'assets': path.resolve(__dirname, '../website/client/assets'),
'components': path.resolve(__dirname, '../website/client/components'),
},
},
resolveLoader: {
fallback: [path.join(__dirname, '../node_modules')],
},
module: {
preLoaders: [
{
test: /\.vue$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
},
{
test: /\.js$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}
],
loaders: [
{
test: /\.vue$/,
loader: 'vue',
},
{
test: /\.js$/,
loader: 'babel',
include: projectRoot,
exclude: /node_modules/,
},
{
test: /\.json$/,
loader: 'json',
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]'),
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]'),
},
},
],
},
eslint: {
formatter: require('eslint-friendly-formatter'),
},
vue: {
loaders: utils.cssLoaders(),
postcss: [
require('autoprefixer')({
browsers: ['last 2 versions'],
}),
],
},
};

View file

@ -0,0 +1,34 @@
var config = require('./config');
var webpack = require('webpack');
var merge = require('webpack-merge');
var utils = require('./utils');
var baseWebpackConfig = require('./webpack.base.conf');
var HtmlWebpackPlugin = require('html-webpack-plugin');
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./webpack/dev-client'].concat(baseWebpackConfig.entry[name]);
});
module.exports = merge(baseWebpackConfig, {
module: {
loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }),
},
// eval-source-map is faster for development
devtool: '#eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env,
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: './website/client/index.html',
inject: true,
}),
],
});

View file

@ -0,0 +1,102 @@
var path = require('path');
var config = require('./config');
var utils = require('./utils');
var webpack = require('webpack');
var merge = require('webpack-merge');
var baseWebpackConfig = require('./webpack.base.conf');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var env = process.env.NODE_ENV === 'testing'
? require('./config/test.env')
: config.build.env;
var webpackConfig = merge(baseWebpackConfig, {
module: {
loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }),
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
},
vue: {
loaders: utils.cssLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
}),
},
plugins: [
// http://vuejs.github.io/vue-loader/workflow/production.html
new webpack.DefinePlugin({
'process.env': env,
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
}),
new webpack.optimize.OccurenceOrderPlugin(),
// extract css into its own file
new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: './website/client/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency',
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
);
},
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor'],
}),
],
});
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin');
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8,
})
);
}
module.exports = webpackConfig;

View file

@ -3,13 +3,13 @@
/* Refresh page if idle > 6h */
var REFRESH_FREQUENCY = 21600000;
var refresh;
var refresher = function() {
window.refresher = function() {
window.location.reload(true);
};
var awaitIdle = function() {
if(refresh) clearTimeout(refresh);
refresh = setTimeout(refresher, REFRESH_FREQUENCY);
refresh = setTimeout(window.refresher, REFRESH_FREQUENCY);
};
awaitIdle();

5
website/client/.babelrc Normal file
View file

@ -0,0 +1,5 @@
{
"presets": ["es2015"],
"plugins": ["transform-runtime"],
"comments": false
}

View file

@ -1,4 +1,10 @@
{
"extends": "habitrpg/browser"
"extends": [
"habitrpg/browser",
"habitrpg/babel"
],
"plugins": [
"html"
]
}

View file

@ -1,3 +1,3 @@
This folder contains the source files for the new client side of that is being developed.
This folder contains the source files for the new client side that is being developed.
The old client side files can be found in /website/client-old, they are still used on Habitica.com while the redesign is in progress.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,64 @@
<template lang="pug">
#app
img.logo(src='../assets/logo.png')
ul
li
router-link(to='/') Home
li
router-link(to='/sub') Sub
router-view.view
p Welcome to your Vue.js app!
p
| To get a better understanding of how this boilerplate works, check out
| <a href="http://vuejs-templates.github.io/webpack" target="_blank">its documentation</a>.
| It is also recommended to go through the docs for
| <a href="http://webpack.github.io/" target="_blank">Webpack</a> and
| <a href="http://vuejs.github.io/vue-loader/" target="_blank">vue-loader</a>.
| If you have any issues with the setup, please file an issue at this boilerplate's
| <a href="https://github.com/vuejs-templates/webpack" target="_blank">repository</a>.
p
| You may also want to checkout
| <a href="https://github.com/vuejs/vue-router/" target="_blank">vue-router</a> for routing and
| <a href="https://github.com/vuejs/vuex/" target="_blank">vuex</a> for state management.
</template>
<script>
import Hello from './Hello';
export default {
components: {
Hello,
},
};
</script>
<style>
html {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
#app {
color: #2c3e50;
margin-top: -100px;
max-width: 600px;
font-family: Source Sans Pro, Helvetica, sans-serif;
text-align: center;
}
#app a {
color: #42b983;
text-decoration: none;
}
.logo {
width: 100px;
height: 100px
}
</style>

View file

@ -0,0 +1,21 @@
<template lang="pug">
.hello
h1 {{ msg }}
</template>
<script>
export default {
data () {
return {
msg: 'Hello Vue!',
};
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
color: #42b983;
}
</style>

View file

@ -0,0 +1,21 @@
<template lang="pug">
.hello
h1 {{ msg }}
</template>
<script>
export default {
data () {
return {
msg: 'Hello Sub Route!',
};
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
color: #42b983;
}
</style>

View file

@ -1 +1,11 @@
Hello World!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Habitica</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

9
website/client/main.js Normal file
View file

@ -0,0 +1,9 @@
import Vue from 'vue';
import App from './components/App';
import router from './router';
new Vue({ // eslint-disable-line no-new
router,
el: '#app',
render: h => h(App),
});

15
website/client/router.js Normal file
View file

@ -0,0 +1,15 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import Hello from './components/Hello';
import Sub from './components/Sub';
Vue.use(VueRouter);
export default new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', component: Hello },
{ path: '/sub', component: Sub },
],
});

6
website/server/.eslintrc Normal file
View file

@ -0,0 +1,6 @@
{
"extends": [
"habitrpg/server",
"habitrpg/babel"
]
}

3
website/static/README.md Normal file
View file

@ -0,0 +1,3 @@
This folder contains the source files for static assets used in the new client side that is being developed.
These files are served without passing through any preprocessor.

1
website/static/test.txt Normal file
View file

@ -0,0 +1 @@
Hello World!