diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..af0f0c3 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..a493496 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,33 @@ +{ + "extends": "airbnb", + "ecmaFeatures": { + "jsx": true, + "modules": true + }, + "env": { + "browser": true, + "node": true + }, + "parser": "babel-eslint", + "rules": { + "quotes": [2, "single"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2, + "comma-dangle": [2, "never"], + "space-after-keywords": [2, "never"], + "react/jsx-quotes": [2, "single"], + "react/prop-types": 0, + "no-use-before-define": 0, + "padded-blocks": 0, + "id-length": [2, { + "min": 3, + "max": 30, + "properties": "never", + "exceptions": ["x", "y", "vx", "vy", "id", "i", "e", "fn"] + }] + }, + "plugins": [ + "react" + ] +} diff --git a/.gitignore b/.gitignore index 703a672..d28fd52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ +npm-debug.log node_modules -bower_components public .DS_Store -npm-debug.log \ No newline at end of file + diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index a08e606..0000000 --- a/.jshintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "browserify": true -} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3a8ee75 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Riku Rouvila + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index 32c8d16..a045d9c 100644 --- a/README.md +++ b/README.md @@ -5,54 +5,43 @@ **Koodiklinikka.fi lähdekoodi**. [Issueita](https://github.com/koodiklinikka/koodiklinikka.fi/issues) ja [Pull Requestejä](https://github.com/koodiklinikka/koodiklinikka.fi/pulls) otetaan lämpimästi vastaan. Yritämme pitää kynnyksen kontribuoida projektiin alhaisena, jotta mahdollisimman moni pääsisi jättämään siihen jälkensä. Kaikki koodi katselmoidaan läpi ja mergetään projektiin kun näyttää hyvälle. Muutamasta mergetystä Pull Requestista oikeudet ylläpitää projektia. -[Issueita](https://github.com/koodiklinikka/koodiklinikka.fi/issues) voidaan käyttää myös sivun +[Issueita](https://github.com/koodiklinikka/koodiklinikka.fi/issues) voidaan käyttää myös sivun * toiminnallisuuteen -* designiin +* designiin * [HTTP-rajapintaan](https://github.com/koodiklinikka/koodiklinikka.fi-api) * projektin hallintaan liittyviin asioihin Tai koko Koodiklinikkaan yleisesti. - ----------------------------- -# Contributing - -This repository is automatically deployed by [Codeship](https://codeship.com) to a [Digital Ocean](http://digitalocean.com) droplet hosting [http://koodiklinikka.fi](http://koodiklinikka.fi). - ## Getting things up and running - Install [Node.js](http://nodejs.org) ``` - git clone git@github.com:koodiklinikka/koodiklinikka.fi.git + git clone git@github.com:leonidas/gulp-project-template.git cd npm install npm start open http://localhost:9001 in your browser ``` -### Enable LiveReload -Install [LiveReload for Chrome](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei?hl=en) - ## CLI Commands * npm install - * Installs server-side dependencies from NPM and client-side dependencies from Bower + * Installs server-side dependencies from npm * npm start * Compiles your files, starts watching files for changes, serves static files to port 9001 * npm run build * Builds everything +# Production build Minification, uglification and other tasks you're expected to run before deploying your product can be made by running the build command with env variable NODE_ENV set to "production" -``` -NODE_ENV=production npm run build -``` -## API server -API proxy can be defined with **SERVER** environment variable. -``` -SERVER=http://localhost:9000 npm start -``` + + NODE_ENV=production npm run build + ## Development guidelines -* **public** - directory should be dedicated only to compiled/copied files from **src** - directory. +#### Directory structure + +**public** - directory should be dedicated only to compiled/copied files from **src** - directory. It should be possible to delete directory completely and after **npm start** or **npm run build** everything should be as they were before the deletion. -* All backend dependencies should be installed with **npm**. Browser dependencies should be installed with **bower** or with **npm**. diff --git a/bower.json b/bower.json deleted file mode 100644 index 1b64b74..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "koodiklinikka.fi", - "version": "0.0.0", - "authors": [ - "Riku Rouvila " - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "font-awesome": "~4.2.0" - } -} diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 0000000..dda3d1c --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,187 @@ +import browserify from 'browserify'; +import browserSync from 'browser-sync'; +import duration from 'gulp-duration'; +import gulp from 'gulp'; +import gutil from 'gulp-util'; +import jade from 'gulp-jade'; +import notifier from 'node-notifier'; +import path from 'path'; +import prefix from 'gulp-autoprefixer'; +import rev from 'gulp-rev'; +import source from 'vinyl-source-stream'; +import exorcist from 'exorcist'; +import transform from 'vinyl-transform'; +import sourcemaps from 'gulp-sourcemaps'; +import streamify from 'gulp-streamify'; +import stylus from 'gulp-stylus'; +import uglify from 'gulp-uglify'; +import watchify from 'watchify'; +import watch from 'gulp-watch'; +import inject from 'gulp-inject'; + +/*eslint "no-process-env":0 */ +const production = process.env.NODE_ENV === 'production'; + +const config = { + source: './src', + destination: './public', + scripts: { + source: './src/main.js', + destination: './public/js/', + extensions: ['.jsx'], + filename: 'bundle.js' + }, + templates: { + source: './src/*.jade', + watch: './src/*.jade', + destination: './public/', + revision: './public/**/*.html' + }, + styles: { + source: './src/style.styl', + watch: './src/**/*.styl', + destination: './public/css/' + }, + assets: { + source: './src/assets/**/*.*', + watch: './src/assets/**/*.*', + destination: './public/' + }, + inject: { + resources: ['./public/**/*.css', './public/**/*.js'] + } +}; + +const browserifyConfig = { + entries: [config.scripts.source], + extensions: config.scripts.extensions, + debug: !production, + cache: {}, + packageCache: {} +}; + +function handleError(err) { + gutil.log(err.message); + gutil.beep(); + notifier.notify({ + title: 'Compile Error', + message: err.message + }); + return this.emit('end'); +} + +gulp.task('scripts', () => { + let pipeline = browserify(browserifyConfig) + .bundle() + .on('error', handleError) + .pipe(source(config.scripts.filename)); + + if(production) { + pipeline = pipeline + .pipe(streamify(uglify())) + .pipe(streamify(rev())); + } else { + pipeline = pipeline.pipe(transform(() => { + return exorcist(path.join(config.scripts.destination, config.scripts.filename) + '.map'); + })); + } + + return pipeline.pipe(gulp.dest(config.scripts.destination)); +}); + +gulp.task('templates', ['styles', 'scripts'], () => { + const resources = gulp.src(config.inject.resources, {read: false}); + + const pipeline = gulp.src(config.templates.source) + .pipe(jade({ + pretty: !production + })) + .on('error', handleError) + .pipe(inject(resources, {ignorePath: 'public', removeTags: true})) + .pipe(gulp.dest(config.templates.destination)); + + if(production) { + return pipeline; + } + + return pipeline.pipe(browserSync.reload({ + stream: true + })); +}); + +gulp.task('styles', () => { + let pipeline = gulp.src(config.styles.source); + + if(!production) { + pipeline = pipeline.pipe(sourcemaps.init()); + } + + pipeline = pipeline.pipe(stylus({ + 'include css': true, + paths: ['node_modules', path.join(__dirname, config.source)], + compress: production + })) + .on('error', handleError) + .pipe(prefix('last 2 versions', 'Chrome 34', 'Firefox 28', 'iOS 7')); + + if(production) { + pipeline = pipeline.pipe(rev()); + } else { + pipeline = pipeline.pipe(sourcemaps.write('.')); + } + + pipeline = pipeline.pipe(gulp.dest(config.styles.destination)); + + if(production) { + return pipeline; + } + + return pipeline.pipe(browserSync.stream({ + match: '**/*.css' + })); +}); + +gulp.task('assets', () => { + return gulp.src(config.assets.source) + .pipe(gulp.dest(config.assets.destination)); +}); + +gulp.task('server', () => { + return browserSync({ + open: false, + port: 9001, + notify: false, + ghostMode: false, + server: { + baseDir: config.destination + } + }); +}); + +gulp.task('watch', () => { + + ['templates', 'styles', 'assets'].forEach((watched) => { + watch(config[watched].watch, () => { + gulp.start(watched); + }); + }); + + const bundle = watchify(browserify(browserifyConfig)); + + bundle.on('update', () => { + const build = bundle.bundle() + .on('error', handleError) + .pipe(source(config.scripts.filename)); + + build + .pipe(transform(() => { + return exorcist(config.scripts.destination + config.scripts.filename + '.map'); + })) + .pipe(gulp.dest(config.scripts.destination)) + .pipe(duration('Rebundling browserify bundle')) + .pipe(browserSync.reload({stream: true})); + }).emit('update'); +}); + +gulp.task('build', ['styles', 'assets', 'scripts', 'templates']); +gulp.task('default', ['styles', 'assets', 'templates', 'watch', 'server']); diff --git a/package.json b/package.json index c333ed2..34e12c0 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "license": "MIT", "main": "gulpfile.js", "scripts": { - "prepublish": "bower install", - "start": "gulp", - "build": "gulp build", - "test": "karma start test/karma.conf.js" + "start": "rm -rf public && gulp", + "build": "rm -rf public && gulp build", + "lint": "eslint src", + "test": "mocha src/**/__tests__/*.js --compilers js:babel-core/register --require test/test-helper" }, "keywords": [ "gulp", @@ -17,53 +17,55 @@ ], "dependencies": { "axios": "^0.4.2", - "lodash": "^2.4.1", + "font-awesome": "^4.4.0", + "http-server": "^0.8.0", + "lodash": "^3.9.1", "parse-github-event": "^0.2.0", "react": "^0.12.2", "timeago": "^0.2.0", - "twitter-text": "^1.11.0", - "http-server": "^0.8.0" + "twitter-text": "^1.11.0" }, "devDependencies": { - "6to5ify": "^3.1.2", - "bower": "~1.3.5", - "browserify": "~6.1.0", - "coffee-script": "~1.8.0", - "deamdify": "^0.1.1", - "debowerify": "~0.9.1", - "ecstatic": "~0.5.3", + "babel": "^6.1.18", + "babel-core": "^6.2.1", + "babel-eslint": "^4.1.3", + "babel-preset-es2015": "^6.1.18", + "babel-preset-react": "^6.1.18", + "babelify": "^7.2.0", + "browser-sync": "^2.9.4", + "browserify": "^10.2.1", + "chai": "^3.0.0", "envify": "^3.4.0", - "event-stream": "^3.2.1", - "faker": "^2.1.2", - "gulp": "~3.8.1", + "eslint": "^1.5.1", + "eslint-config-airbnb": "0.0.9", + "eslint-plugin-react": "^3.4.2", + "event-stream": "^3.3.2", + "exorcist": "^0.4.0", + "gulp": "3.9.0", "gulp-autoprefixer": "1.0.1", - "gulp-concat": "^2.4.3", + "gulp-duration": "0.0.0", + "gulp-inject": "^3.0.0", "gulp-jade": "~0.9.0", - "gulp-less": "3.0.0", - "gulp-livereload": "~2.1.0", - "gulp-minify-css": "~0.3.5", + "gulp-less": "^3.0.5", + "gulp-replace": "^0.5.3", + "gulp-rev": "^4.0.0", + "gulp-sourcemaps": "^1.3.0", "gulp-streamify": "0.0.5", - "gulp-stylus": "1.3.3", + "gulp-stylus": "~2.0.0", "gulp-uglify": "~1.0.1", "gulp-util": "~3.0.1", - "http-proxy": "^1.8.1", - "jsx-transform": "^0.10.1", - "karma": "~0.12.21", - "karma-chrome-launcher": "~0.1.4", - "karma-cli": "0.0.4", - "karma-coffee-preprocessor": "~0.2.1", - "karma-jasmine": "~0.2.2", - "reactify": "^1.0.0", - "through2": "^0.6.3", + "gulp-watch": "^4.3.4", + "jsdom": "^5.6.0", + "mocha": "^2.2.5", + "node-notifier": "^4.2.1", + "rimraf": "^2.3.4", "vinyl-source-stream": "~1.0.0", - "watchify": "~2.3.0" + "vinyl-transform": "^1.0.0", + "watchify": "^3.2.1" }, "browserify": { "transform": [ - "reactify", - "6to5ify", - "debowerify", - "deamdify", + "babelify", "envify" ] } diff --git a/src/components/repository/__tests__/renderer.js b/src/components/repository/__tests__/renderer.js new file mode 100644 index 0000000..c46aca3 --- /dev/null +++ b/src/components/repository/__tests__/renderer.js @@ -0,0 +1,37 @@ +/* globals beforeEach, describe, it */ +import {render} from '../'; +import {expect} from 'chai'; + +const REPO_DATA = { + html_url: 'http://example.com', + full_name: 'Gulp project template', + description: 'Hello world!' +}; + +const COMMITS = [{ + message: 'initial commit', + committer: { + name: 'Riku' + } +}, { + message: 'final commit', + committer: { + name: 'L.H.Ahne' + } +}]; + +describe('View renderer', function() { + beforeEach(function() { + document.body.innerHTML = render(REPO_DATA, COMMITS); + }); + + it('should render a title with the string "Hello world!"', function() { + const $title = document.getElementsByTagName('h1')[0]; + expect($title.innerHTML).to.equal('Hello world!'); + }); + + it('should render 2 list items', function() { + const $listItems = document.querySelectorAll('li'); + expect($listItems.length).to.equal(2); + }); +}); diff --git a/src/components/repository/index.js b/src/components/repository/index.js new file mode 100644 index 0000000..9317029 --- /dev/null +++ b/src/components/repository/index.js @@ -0,0 +1,28 @@ + +export function render(repository, commits) { + const $commits = commits.map(commit => { + return ` +
  • + + ${commit.message.replace(/\n/g, '
    ')} + +

    + + ${commit.committer.name} + +
  • `; + }); + + return ` +
    +

    ${repository.description}

    +

    + ${repository.full_name} +

    +
      + ${$commits.join('')} +
    +
    + `; +} + diff --git a/src/components/repository/index.styl b/src/components/repository/index.styl new file mode 100644 index 0000000..e733b92 --- /dev/null +++ b/src/components/repository/index.styl @@ -0,0 +1,17 @@ +.repository__title + margin-bottom 0 + +.repository__description + margin-top 0 + +.repository__commits + margin-top 2em + +.commit + margin 1em 0 + padding 1em 0 + border-bottom 1px solid #eee + + small + color #777 + diff --git a/src/index.jade b/src/index.jade new file mode 100644 index 0000000..c5ab11f --- /dev/null +++ b/src/index.jade @@ -0,0 +1,10 @@ +doctype html +html + head + title Gulp template + // inject:css + // endinject + body + h1 Hello world! + // inject:js + // endinject diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..a8af948 --- /dev/null +++ b/src/main.js @@ -0,0 +1,8 @@ +import {getCommits, getRepo} from './services/github'; +import {render} from './components/repository'; + +Promise.all([getRepo(), getCommits()]) +.then(([repository, commits]) => { + document.body.innerHTML = render(repository, commits); +}); + diff --git a/src/services/github.js b/src/services/github.js new file mode 100644 index 0000000..8da54c2 --- /dev/null +++ b/src/services/github.js @@ -0,0 +1,13 @@ +const REPO_URL = 'https://api.github.com/repos/leonidas/gulp-project-template'; + +export function getRepo() { + return fetch(REPO_URL).then(res => res.json()); +} + +export function getCommits() { + return fetch(`${REPO_URL}/commits`) + .then(res => res.json()) + .then(commits => { + return commits.map(({commit}) => commit); + }); +} diff --git a/src/style.styl b/src/style.styl new file mode 100644 index 0000000..3226077 --- /dev/null +++ b/src/style.styl @@ -0,0 +1,8 @@ +body, html + margin 0 + padding 1em + font 14px/1.4 'Helvetica Neue', Helvetica, Arial + color #333 + +@import 'components/repository' +// @import 'bootstrap/dist/css/bootstrap.css' diff --git a/test/karma.conf.js b/test/karma.conf.js deleted file mode 100644 index 718dbc6..0000000 --- a/test/karma.conf.js +++ /dev/null @@ -1,68 +0,0 @@ -// Karma configuration -// Generated on Mon Aug 11 2014 13:43:38 GMT+0300 (EEST) - -'use strict'; - -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '../', - - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine'], - - - // list of files / patterns to load in the browser - files: [ - 'test/unit/**/*.coffee' - ], - - - // list of files to exclude - exclude: [ - ], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - '**/*.coffee': ['coffee'] - }, - - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress'], - - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true - }); -}; diff --git a/test/test-helper.js b/test/test-helper.js new file mode 100644 index 0000000..a14c8f6 --- /dev/null +++ b/test/test-helper.js @@ -0,0 +1,7 @@ +'use strict'; + +import {jsdom} from 'jsdom'; + +const document = global.document = jsdom(''); +const window = global.window = document.defaultView; +global.navigator = window.navigator = {}; diff --git a/test/unit/exampleSpec.coffee b/test/unit/exampleSpec.coffee deleted file mode 100644 index df38553..0000000 --- a/test/unit/exampleSpec.coffee +++ /dev/null @@ -1,3 +0,0 @@ -describe 'Example spec', -> - it 'should ...', -> - expect(true).toBe(true)