From 4d027fef192ceecd42362cabf0d661c776928e85 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 29 May 2015 11:48:55 +0200 Subject: [PATCH 1/3] changed tooling from browserify to webpack * using browsersync instead of livereload * `gulp serve` works as before * `gulp sync` starts browserSync and serves UI on port 3000 * everything is built in `build/` * build release with `gulp build --release` --- Makefile | 14 +- client/.gitignore | 6 +- client/app/index.html | 12 +- client/app/scripts/main.js | 3 + client/app/styles/main.less | 5 +- client/gulpfile.js | 241 +++++++++++++++++++---------------- client/package.json | 44 ++++--- client/test/.bowerrc | 3 - client/test/karma.conf.js | 30 +++-- client/test/tests.webpack.js | 2 + client/webpack.config.js | 129 +++++++++++++++++++ 11 files changed, 322 insertions(+), 167 deletions(-) delete mode 100644 client/test/.bowerrc create mode 100644 client/test/tests.webpack.js create mode 100644 client/webpack.config.js diff --git a/Makefile b/Makefile index a3d17c5016..02dda82a38 100644 --- a/Makefile +++ b/Makefile @@ -37,14 +37,14 @@ $(APP_EXE) $(PROBE_EXE): go get -tags netgo ./$(@D) go build -ldflags "-extldflags \"-static\" -X main.version $(GIT_REVISION)" -tags netgo -o $@ ./$(@D) -static: client/dist/scripts/bundle.js - esc -o app/static.go -prefix client/dist client/dist +static: client/build/app.js + esc -o app/static.go -prefix client/build client/build -client/dist/scripts/bundle.js: client/app/scripts/* - mkdir -p client/dist +client/build/app.js: client/app/scripts/* + mkdir -p client/build docker run -ti -v $(shell pwd)/client/app:/home/weave/app \ - -v $(shell pwd)/client/dist:/home/weave/dist \ - $(SCOPE_UI_BUILD_IMAGE) gulp build + -v $(shell pwd)/client/build:/home/weave/build \ + $(SCOPE_UI_BUILD_IMAGE) gulp build --release client-test: client/test/* docker run -ti -v $(shell pwd)/client/app:/home/weave/app \ @@ -62,7 +62,7 @@ $(SCOPE_UI_BUILD_EXPORT): client/Dockerfile client/gulpfile.js client/package.js clean: go clean ./... - rm -rf $(SCOPE_EXPORT) $(SCOPE_UI_BUILD_EXPORT) client/dist + rm -rf $(SCOPE_EXPORT) $(SCOPE_UI_BUILD_EXPORT) client/build deps: go get \ diff --git a/client/.gitignore b/client/.gitignore index 2f4628a1ad..dd87e2d73f 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,6 +1,2 @@ node_modules -dist -.tmp -.sass-cache -bower_components -test/bower_components +build diff --git a/client/app/index.html b/client/app/index.html index 3688c79140..981cd91dbd 100644 --- a/client/app/index.html +++ b/client/app/index.html @@ -5,14 +5,6 @@ Weave Scope - - - - - - - - - - - + diff --git a/client/app/scripts/main.js b/client/app/scripts/main.js index 330003e053..5be55cab5f 100644 --- a/client/app/scripts/main.js +++ b/client/app/scripts/main.js @@ -1,3 +1,6 @@ +require('font-awesome-webpack'); +require('../styles/main.less'); + const React = require('react'); const App = require('./components/app.js'); diff --git a/client/app/styles/main.less b/client/app/styles/main.less index 4a442f3e5e..1e46251c25 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -1,6 +1,5 @@ -@import "../../node_modules/material-ui/src/less/scaffolding"; -@import "../../node_modules/material-ui/src/less/components"; -@import "../../node_modules/font-awesome/less/font-awesome.less"; +@import "~material-ui/src/less/scaffolding.less"; +@import "~material-ui/src/less/components.less"; @import url(http://fonts.googleapis.com/css?family=Roboto:300,400); diff --git a/client/gulpfile.js b/client/gulpfile.js index c8a192958a..3126eec477 100644 --- a/client/gulpfile.js +++ b/client/gulpfile.js @@ -1,118 +1,110 @@ -const gulp = require('gulp'); -const connect = require('gulp-connect'); -const livereload = require('gulp-livereload'); -const babelify = require('babelify'); -const browserify = require('browserify'); -const del = require('del'); -const source = require('vinyl-source-stream'); -const vbuffer = require('vinyl-buffer'); -const reactify = require('reactify'); - -// load plugins -const $ = require('gulp-load-plugins')(); - -var isDev = true; -var isProd = false; - -gulp.task('styles', function() { - return gulp.src('app/styles/main.less') - .pipe($.if(isDev, $.sourcemaps.init())) - .pipe($.less()) - .pipe($.autoprefixer('last 1 version')) - .pipe($.if(isDev, $.sourcemaps.write())) - .pipe($.if(isDev, gulp.dest('.tmp/styles'))) - .pipe($.if(isProd, $.csso())) - .pipe($.if(isProd, gulp.dest('dist/styles'))) - .pipe($.size()) - .pipe(livereload()); -}); - -gulp.task('scripts', function() { - const bundler = browserify('./app/scripts/main.js', {debug: isDev}); - bundler.transform(reactify); - bundler.transform(babelify); - - const stream = bundler.bundle(); - - return stream - .pipe(source('bundle.js')) - .pipe($.if(isDev, gulp.dest('.tmp/scripts'))) - .pipe($.if(isProd, vbuffer())) - .pipe($.if(isProd, $.uglify())) - .on('error', $.util.log) - .pipe($.if(isProd, gulp.dest('dist/scripts'))) - .pipe(livereload()); -}); - -gulp.task('html', ['styles', 'scripts'], function() { - return gulp.src('app/*.html') - .pipe($.preprocess()) - .pipe(gulp.dest('dist')) - .pipe($.size()) - .pipe(livereload()); -}); - -gulp.task('images', function() { - return gulp.src('app/images/**/*') - .pipe(gulp.dest('dist/images')) - .pipe($.size()); -}); - -gulp.task('fonts', function() { - return gulp.src('node_modules/font-awesome/fonts/*') +'use strict'; + +var gulp = require('gulp'); +var $ = require('gulp-load-plugins')(); +var del = require('del'); +var path = require('path'); +var runSequence = require('run-sequence'); +var webpack = require('webpack'); +var argv = require('minimist')(process.argv.slice(2)); + +var watch = false; +var browserSync; + +// The default task +gulp.task('default', ['build']); + +// Clean output directory +gulp.task('clean', del.bind( + null, ['.tmp', 'build/*'], {dot: true} +)); + +// 3rd party libraries +gulp.task('vendor', function() { + return gulp.src('node_modules/font-awesome/fonts/**') .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}')) - .pipe($.flatten()) - .pipe($.if(isDev, gulp.dest('.tmp/fonts'))) - .pipe($.if(isProd, gulp.dest('dist/fonts'))) - .pipe($.size()); + .pipe(gulp.dest('build/fonts')); }); -gulp.task('extras', function() { - return gulp.src(['app/*.*', '!app/*.html'], { dot: true }) - .pipe(gulp.dest('dist')); +// Favicon +gulp.task('favicon', function() { + return gulp.src(['app/favicon.ico']) + .pipe(gulp.dest('build')); }); -gulp.task('clean', function() { - return del(['.tmp', 'dist']); -}); +// Static files +gulp.task('html', function() { + var release = !!argv.release; -gulp.task('lint', function() { - return gulp.src(['app/**/*.js']) - // eslint() attaches the lint output to the eslint property - // of the file object so it can be used by other modules. - .pipe($.eslint()) - // eslint.format() outputs the lint results to the console. - // Alternatively use eslint.formatEach() (see Docs). - .pipe($.eslint.format()) - // To have the process exit with an error code (1) on - // lint error, return the stream and pipe to failOnError last. - .pipe($.eslint.failOnError()); + return gulp.src('app/*.html') + .pipe($.changed('build')) + .pipe($.if(release, $.preprocess())) + .pipe(gulp.dest('build')) + .pipe($.size({title: 'html'})); }); -gulp.task('production', ['html', 'images', 'fonts', 'extras']); +// Bundle +gulp.task('bundle', function(cb) { + var started = false; + var config = require('./webpack.config.js'); + var bundler = webpack(config); + var verbose = !!argv.verbose; + + function bundle(err, stats) { + if (err) { + throw new $.util.PluginError('webpack', err); + } + + console.log(stats.toString({ + colors: $.util.colors.supportsColor, + hash: verbose, + version: verbose, + timings: verbose, + chunks: verbose, + chunkModules: verbose, + cached: verbose, + cachedAssets: verbose + })); + + if (!started) { + started = true; + return cb(); + } + } + + if (watch) { + bundler.watch(200, bundle); + } else { + bundler.run(bundle); + } +}); -gulp.task('build', function() { - isDev = false; - isProd = true; - gulp.start('production'); +// Build the app from source code +gulp.task('build', ['clean'], function(cb) { + runSequence(['vendor', 'html', 'favicon', 'bundle'], cb); }); -gulp.task('default', ['clean'], function() { - gulp.start('build'); +// Build and start watching for modifications +gulp.task('build:watch', function(cb) { + watch = true; + runSequence('build', function() { + gulp.watch('app/*.html', ['html']); + cb(); + }); }); -gulp.task('connect', function() { - const root = isProd ? ['dist'] : ['.tmp', 'app']; - connect.server({ - root: root, +// Launch a Node.js/Express server +gulp.task('serve', ['build:watch'], function() { + $.connect.server({ + root: ['build'], port: 4041, middleware: function() { return [(function() { - const url = require('url'); - const proxy = require('proxy-middleware'); - const options = url.parse('http://localhost:4040/api'); + var url = require('url'); + var proxy = require('proxy-middleware'); + var options = url.parse('http://localhost:4040/api'); options.route = '/api'; return proxy(options); })()]; @@ -121,23 +113,48 @@ gulp.task('connect', function() { }); }); -gulp.task('serve', ['connect', 'styles', 'scripts', 'fonts']); - -gulp.task('serve-build', function() { - isDev = false; - isProd = true; +// Launch BrowserSync development server +gulp.task('sync', ['serve'], function(cb) { + browserSync = require('browser-sync'); + + browserSync({ + logPrefix: 'RSK', + // Stop the browser from automatically opening + open: false, + notify: false, + // Run as an https by setting 'https: true' + // Note: this uses an unsigned certificate which on first access + // will present a certificate warning in the browser. + https: false, + // Informs browser-sync to proxy our Express app which would run + // at the following location + proxy: 'localhost:4041' + }, cb); + + process.on('exit', function() { + browserSync.exit(); + }); - // use local WS api - gulp.src('app/*.html') - .pipe($.preprocess({context: {DEBUG: true} })) - .pipe(gulp.dest('dist')); + gulp.watch('build/**/*.*', browserSync.reload); - gulp.start('connect'); + // FIX this part to only reload styles parts that changed + // gulp.watch(['build/**/*.*'].concat( + // src.server.map(function(file) { return '!' + file; }) + // ), function(file) { + // browserSync.reload(path.relative(__dirname, file.path)); + // }); }); -gulp.task('watch', ['serve'], function() { - livereload.listen(); - gulp.watch('app/styles/**/*.less', ['styles']); - gulp.watch('app/scripts/**/*.js', ['scripts']); - gulp.watch('app/images/**/*', ['images']); +// Lint +gulp.task('lint', function() { + return gulp.src(['app/**/*.js']) + // eslint() attaches the lint output to the eslint property + // of the file object so it can be used by other modules. + .pipe($.eslint()) + // eslint.format() outputs the lint results to the console. + // Alternatively use eslint.formatEach() (see Docs). + .pipe($.eslint.format()) + // To have the process exit with an error code (1) on + // lint error, return the stream and pipe to failOnError last. + .pipe($.eslint.failOnError()); }); diff --git a/client/package.json b/client/package.json index e45f17f09a..5c7d0207c5 100644 --- a/client/package.json +++ b/client/package.json @@ -11,53 +11,61 @@ "flux": "^2.0.3", "font-awesome": "^4.3.0", "keymirror": "^0.1.1", - "lodash": "~3.8.0", - "material-ui": "^0.7.5", + "lodash": "~3.9.3", + "material-ui": "~0.7.5", "object-assign": "^2.0.0", "page": "^1.6.3", "react": "^0.13.3", - "react-tap-event-plugin": "^0.1.6", + "react-tap-event-plugin": "^0.1.7", "react-tween-state": "0.0.5", "reqwest": "~1.1.5" }, "devDependencies": { + "autoprefixer-core": "^5.2.0", + "babel-core": "^5.4.7", "babel-eslint": "^3.1.9", + "babel-loader": "^5.1.3", "babelify": "^6.1.2", - "browserify": "^10.2.0", - "del": "^1.1.1", + "browser-sync": "^2.7.6", + "css-loader": "^0.14.4", + "del": "^1.2.0", "eslint": "^0.21.2", + "eslint-loader": "^0.11.2", "eslint-plugin-jasmine": "^1.0.0", "eslint-plugin-react": "^2.3.0", + "file-loader": "^0.8.4", + "font-awesome-webpack": "0.0.3", "gulp": "^3.8.11", "gulp-autoprefixer": "^2.3.0", + "gulp-changed": "^1.2.1", "gulp-connect": "^2.2.0", - "gulp-csso": "^1.0.0", "gulp-eslint": "^0.12.0", "gulp-filter": "^2.0.2", - "gulp-flatten": "^0.0.4", "gulp-if": "^1.2.5", - "gulp-less": "^3.0.3", - "gulp-livereload": "^3.8.0", "gulp-load-plugins": "^0.10.0", "gulp-preprocess": "^1.2.0", "gulp-size": "^1.2.1", - "gulp-sourcemaps": "^1.5.2", - "gulp-uglify": "^1.2.0", - "gulp-useref": "^1.1.1", "gulp-util": "^3.0.4", "jasmine-core": "^2.3.4", - "jshint-stylish": "^1.0.2", - "karma": "^0.12.32", - "karma-browserify": "^4.2.1", + "karma": "^0.12.33", "karma-cli": "0.0.4", "karma-jasmine": "^0.3.5", "karma-phantomjs-launcher": "^0.1.4", - "opn": "^1.0.1", - "proxy-middleware": "^0.11.1", + "karma-webpack": "^1.5.1", + "less": "^2.5.1", + "less-loader": "^2.2.0", + "minimist": "^1.1.1", + "node-libs-browser": "^0.5.2", + "postcss-loader": "^0.4.3", + "proxy-middleware": "^0.12.0", "react-tools": "^0.13.3", "reactify": "^1.1.1", + "run-sequence": "^1.1.0", + "style-loader": "^0.12.3", + "url-loader": "^0.5.6", "vinyl-buffer": "^1.0.0", - "vinyl-source-stream": "^1.1.0" + "vinyl-source-stream": "^1.1.0", + "webpack": "^1.9.10" }, "scripts": { "lint": "gulp lint", diff --git a/client/test/.bowerrc b/client/test/.bowerrc deleted file mode 100644 index 44491d3df5..0000000000 --- a/client/test/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "bower_components" -} diff --git a/client/test/karma.conf.js b/client/test/karma.conf.js index 068b09e991..004e03a5ad 100644 --- a/client/test/karma.conf.js +++ b/client/test/karma.conf.js @@ -4,20 +4,34 @@ module.exports = function(config) { 'PhantomJS' ], files: [ - '../app/**/__tests__/*.js' + { + pattern: 'tests.webpack.js', + watched: false + } ], frameworks: [ - 'jasmine', 'browserify' + 'jasmine' ], preprocessors: { - '../app/**/__tests__/*.js': ['browserify'] - }, - browserify: { - debug: true, - transform: ['reactify', 'babelify'] + 'tests.webpack.js': ['webpack'] }, reporters: [ 'dots' - ] + ], + webpack: { + module: { + loaders: [ + { + test: /\.js?$/, + exclude: /node_modules/, + loader: 'babel-loader' + } + ] + }, + watch: true + }, + webpackServer: { + noInfo: true + } }); }; diff --git a/client/test/tests.webpack.js b/client/test/tests.webpack.js new file mode 100644 index 0000000000..e0bf705c25 --- /dev/null +++ b/client/test/tests.webpack.js @@ -0,0 +1,2 @@ +var context = require.context('../app/scripts', true, /-test\.js$/); +context.keys().forEach(context); diff --git a/client/webpack.config.js b/client/webpack.config.js new file mode 100644 index 0000000000..6c869f8496 --- /dev/null +++ b/client/webpack.config.js @@ -0,0 +1,129 @@ +/* + * React.js Starter Kit + * Copyright (c) Konstantin Tarkus (@koistya), KriaSoft LLC + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +'use strict'; + +var autoprefixer = require('autoprefixer-core'); +var _ = require('lodash'); +var webpack = require('webpack'); +var argv = require('minimist')(process.argv.slice(2)); + +var DEBUG = !argv.release; +var STYLE_LOADER = 'style-loader'; +var CSS_LOADER = DEBUG ? 'css-loader' : 'css-loader?minimize'; +var AUTOPREFIXER_LOADER = 'postcss-loader'; +var GLOBALS = { + 'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"', + '__DEV__': DEBUG +}; + +// +// Common configuration chunk to be used for both +// client-side (app.js) and server-side (server.js) bundles +// ----------------------------------------------------------------------------- + +var config = { + output: { + path: './build/', + publicPath: './', + sourcePrefix: ' ' + }, + + cache: DEBUG, + debug: DEBUG, + devtool: DEBUG ? '#inline-source-map' : false, + + stats: { + colors: true, + reasons: DEBUG + }, + + plugins: [ + new webpack.optimize.OccurenceOrderPlugin() + ], + + resolve: { + extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx'] + }, + + module: { + preLoaders: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'eslint-loader' + } + ], + + loaders: [ + { + test: /\.css$/, + loader: STYLE_LOADER + '!' + CSS_LOADER + '!' + AUTOPREFIXER_LOADER + }, + { + test: /\.less$/, + loader: STYLE_LOADER + '!' + CSS_LOADER + '!' + AUTOPREFIXER_LOADER + + '!less-loader' + }, + { + test: /\.gif/, + loader: 'url-loader?limit=10000&mimetype=image/gif' + }, + { + test: /\.jpg/, + loader: 'url-loader?limit=10000&mimetype=image/jpg' + }, + { + test: /\.png/, + loader: 'url-loader?limit=10000&mimetype=image/png' + }, + { + test: /\.svg/, + loader: 'url-loader?limit=10000&mimetype=image/svg+xml' + }, + { + test: /\.jsx?$/, + exclude: /node_modules/, + loader: 'babel-loader' + }, + { + test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'url-loader?limit=10000&minetype=application/font-woff' + }, + { + test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file-loader' + } + ] + }, + + postcss: [ + autoprefixer({ + browsers: ['last 2 version'] + }) + ] +}; + +// +// Configuration for the client-side bundle (app.js) +// ----------------------------------------------------------------------------- + +var appConfig = _.merge({}, config, { + entry: './app/scripts/main.js', + output: { + filename: 'app.js' + }, + plugins: config.plugins.concat(DEBUG ? [] : [ + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.AggressiveMergingPlugin() + ] + ) +}); + +module.exports = [appConfig]; From a2d9f60f66c01ea2bbcf3e465fb21f6759e62251 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 29 May 2015 12:18:46 +0200 Subject: [PATCH 2/3] added gulpfile to Docker image, added config src --- client/Dockerfile | 2 +- client/gulpfile.js | 3 +++ client/webpack.config.js | 6 +----- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/Dockerfile b/client/Dockerfile index 7eeb476ce1..deac749578 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -14,6 +14,6 @@ RUN npm install -g gulp ADD package.json /home/weave/ RUN npm install -ADD gulpfile.js /home/weave/ +ADD gulpfile.js webpack.config.js /home/weave/ # For instructions on running this container, consult the toplevel Makefile diff --git a/client/gulpfile.js b/client/gulpfile.js index 3126eec477..2b0a72566c 100644 --- a/client/gulpfile.js +++ b/client/gulpfile.js @@ -1,3 +1,6 @@ +/* + * Gulpfile based on https://github.com/kriasoft/react-starter-kit + */ 'use strict'; diff --git a/client/webpack.config.js b/client/webpack.config.js index 6c869f8496..08cd379231 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -1,9 +1,5 @@ /* - * React.js Starter Kit - * Copyright (c) Konstantin Tarkus (@koistya), KriaSoft LLC - * - * This source code is licensed under the MIT license found in the - * LICENSE.txt file in the root directory of this source tree. + * Webpack config based on https://github.com/kriasoft/react-starter-kit */ 'use strict'; From 3a79dbb02aa1e15e31f63170bdf3251f89a76f2c Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 29 May 2015 13:55:07 +0200 Subject: [PATCH 3/3] added linter config to build image, linter README --- client/Dockerfile | 2 +- client/README.md | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/Dockerfile b/client/Dockerfile index deac749578..8efe53933d 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -14,6 +14,6 @@ RUN npm install -g gulp ADD package.json /home/weave/ RUN npm install -ADD gulpfile.js webpack.config.js /home/weave/ +ADD gulpfile.js webpack.config.js .eslintrc /home/weave/ # For instructions on running this container, consult the toplevel Makefile diff --git a/client/README.md b/client/README.md index c4ed8e4c63..6b1bd9b096 100644 --- a/client/README.md +++ b/client/README.md @@ -2,7 +2,14 @@ ## Getting Started -- Install: `npm install` -- Run `gulp` for building to the `dist` directory or `gulp serve` to serve the UI via a webserver -- Develop using `gulp watch` for automatic code reload +- Setup: `npm install` +- Build: `gulp build --release`, output will be in `build/` +- Develop: `gulp sync` and then open `http://localhost:3000/` + +To see a topology, `../app/app` needs to be running, as well as a probe. + +## Coding + +This directory has a `.eslintrc`, make sure your editor supports linter hints. +To run a linter, you also run `gulp lint`.