ff-stream-web

git clone git://archive.git.mtrnord.blog/MTRNord/ff-stream-web.git
Log | Files | Refs | README | LICENSE

commit af7bea9da2bf42d3b5984293a5dc1efa06850ea8
parent 889a24bba2ae5d810dc21d79be666d51f1aac961
Author: Konstantin Tarkus <koistya@gmail.com>
Date:   Fri, 18 Dec 2015 10:22:34 +0300

Merge pull request #356 from koistya/start-script

Improve start.js script that starts dev server with Browsersync and HMR
Diffstat:
MCHANGELOG.md | 1+
MREADME.md | 8++++----
Mpackage.json | 5++---
Atools/README.md | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mtools/build.js | 9++++++---
Mtools/bundle.js | 22++++------------------
Mtools/copy.js | 10++++++----
Dtools/lib/watch.js | 14--------------
Atools/runServer.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtools/serve.js | 56--------------------------------------------------------
Mtools/start.js | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtools/webpack.config.js | 33+--------------------------------
12 files changed, 203 insertions(+), 171 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ### [Unreleased][unreleased] +- Optimize the `tools/start.js` script that launches dev server with Browsersync and HMR - Replace Superagent with WHATWG Fetch library - Rename `app.js` to `client.js` (aka client-side code) - Integrate [CSS Modules](https://github.com/css-modules/css-modules) and diff --git a/README.md b/README.md @@ -18,7 +18,7 @@ See [demo](http://demo.reactstarterkit.com) &nbsp;|&nbsp; join [#react-starter-kit](https://gitter.im/kriasoft/react-starter-kit) chatroom to stay up to date [![Rollbar - Full-stack error tracking for all apps in any language](https://dl.dropboxusercontent.com/u/16006521/react-starter-kit/rollbar.png)](https://rollbar.com/?utm_source=reactstartkit(github)&utm_medium=link&utm_campaign=reactstartkit(github)) &nbsp;&nbsp; -[![Localize - Translate your web app in minutes](https://dl.dropboxusercontent.com/u/16006521/react-starter-kit/localize.png)](https://localizejs.com/) +[![Localize - Translate your web app in minutes](https://dl.dropboxusercontent.com/u/16006521/react-starter-kit/localize.png)](https://localizejs.com/?cid=802&utm_source=rsk) ### Directory Layout @@ -46,12 +46,12 @@ join [#react-starter-kit](https://gitter.im/kriasoft/react-starter-kit) chatroom │ ├── /build.js # Builds the project from source to output (build) folder │ ├── /bundle.js # Bundles the web resources into package(s) through Webpack │ ├── /clean.js # Cleans up the output (build) folder -│ ├── /webpack.config.js # Configurations for client-side and server-side bundles │ ├── /copy.js # Copies static files to output (build) folder │ ├── /deploy.js # Deploys your web application │ ├── /run.js # Helper function for running build automation tasks -│ ├── /serve.js # Launches the Node.js/Express web server -│ └── /start.js # Launches the development web server with "live reload" +│ ├── /runServer.js # Launches (or restarts) Node.js server +│ ├── /start.js # Launches the development web server with "live reload" +│ └── /webpack.config.js # Configurations for client-side and server-side bundles │── package.json # The list of 3rd party libraries and utilities └── preprocessor.js # ES6 transpiler settings for Jest ``` diff --git a/package.json b/package.json @@ -59,8 +59,8 @@ "replace": "^0.3.0", "url-loader": "^0.5.7", "webpack": "^1.12.9", - "webpack-dev-middleware": "^1.4.0", - "webpack-hot-middleware": "^2.6.0" + "webpack-hot-middleware": "^2.6.0", + "webpack-middleware": "^1.4.0" }, "jest": { "rootDir": "./src", @@ -80,7 +80,6 @@ "bundle": "babel-node tools/run bundle", "build": "babel-node tools/run build", "deploy": "babel-node tools/run deploy", - "serve": "babel-node tools/run serve", "start": "babel-node tools/run start" } } diff --git a/tools/README.md b/tools/README.md @@ -0,0 +1,47 @@ +## Build Automation Tools + +##### `npm start` (`start.js`) + +* Cleans up the output `/build` directory (`clean.js`) +* Copies static files to the output folder (`copy.js`) +* Launches [Webpack](https://webpack.github.io/) compiler in a watch mode (via [webpack-middleware](https://github.com/kriasoft/webpack-middleware)) +* Launches Node.js server from the compiled output folder (`runServer.js`) +* Launches [Browsersync](https://browsersync.io/), + [HMR](https://webpack.github.io/docs/hot-module-replacement), and + [React Transform](https://github.com/gaearon/babel-plugin-react-transform) + +##### `npm run build` (`build.js`) + +* Cleans up the output `/build` folder (`clean.js`) +* Copies static files to the output folder (`copy.js`) +* Creates application bundles with Webpack (`bundle.js`, `webpack.config.js`) + +##### `npm run deploy` (`deploy.js`) + +* Builds the project from source files (`build.js`) +* Pushes the contents of the `/build` folder to a remote server with Git + +##### Options + +Flag | Description +----------- | -------------------------------------------------- +`--release` | Minimizes and optimizes the compiled output +`--verbose` | Prints detailed information to the console + +For example: + +```sh +$ npm run build -- --release --verbose # Build the app in production mode +``` + +or + +```sh +$ npm start -- --release # Launch dev server in production mode +``` + +#### Misc + +* `webpack.config.js` - Webpack configuration for both client-side and server-side bundles +* `run.js` - Helps to launch other scripts with `babel-node` (e.g. `babel-node tools/run build`) +* `.eslintrc` - ESLint overrides for built automation scripts diff --git a/tools/build.js b/tools/build.js @@ -8,15 +8,18 @@ */ import run from './run'; +import clean from './clean'; +import copy from './copy'; +import bundle from './bundle'; /** * Compiles the project from source files into a distributable * format and copies it to the output (build) folder. */ async function build() { - await run(require('./clean')); - await run(require('./copy')); - await run(require('./bundle')); + await run(clean); + await run(copy); + await run(bundle); } export default build; diff --git a/tools/bundle.js b/tools/bundle.js @@ -11,31 +11,17 @@ import webpack from 'webpack'; import webpackConfig from './webpack.config'; /** - * Bundles JavaScript, CSS and images into one or more packages - * ready to be used in a browser. + * Creates application bundles from the source files. */ function bundle() { return new Promise((resolve, reject) => { - const bundler = webpack(webpackConfig); - let bundlerRunCount = 0; - - function onComplete(err, stats) { + webpack(webpackConfig).run((err, stats) => { if (err) { return reject(err); } - console.log(stats.toString(webpackConfig[0].stats)); - - if (++bundlerRunCount === (global.WATCH ? webpackConfig.length : 1)) { - return resolve(); - } - } - - if (global.WATCH) { - bundler.watch(200, onComplete); - } else { - bundler.run(onComplete); - } + resolve(); + }); }); } diff --git a/tools/copy.js b/tools/copy.js @@ -8,15 +8,15 @@ */ import path from 'path'; +import gaze from 'gaze'; import replace from 'replace'; import Promise from 'bluebird'; -import watch from './lib/watch'; /** * Copies static files such as robots.txt, favicon.ico to the * output (build) folder. */ -async function copy() { +async function copy({ watch } = {}) { const ncp = Promise.promisify(require('ncp')); await Promise.all([ @@ -33,8 +33,10 @@ async function copy() { silent: false, }); - if (global.WATCH) { - const watcher = await watch('src/content/**/*.*'); + if (watch) { + const watcher = await new Promise((resolve, reject) => { + gaze('src/content/**/*.*', (err, val) => err ? reject(err) : resolve(val)); + }); watcher.on('changed', async (file) => { const relPath = file.substr(path.join(__dirname, '../src/content/').length); await ncp(`src/content/${relPath}`, `build/content/${relPath}`); diff --git a/tools/lib/watch.js b/tools/lib/watch.js @@ -1,14 +0,0 @@ -/** - * React Starter Kit (http://www.reactstarterkit.com/) - * - * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE.txt file in the root directory of this source tree. - */ - -import gaze from 'gaze'; - -export default (pattern) => new Promise((resolve, reject) => { - gaze(pattern, (err, watcher) => err ? reject(err) : resolve(watcher)); -}); diff --git a/tools/runServer.js b/tools/runServer.js @@ -0,0 +1,58 @@ +/** + * React Starter Kit (http://www.reactstarterkit.com/) + * + * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import path from 'path'; +import cp from 'child_process'; +import webpackConfig from './webpack.config'; + +// Should match the text string used in `src/server.js/server.listen(...)` +const RUNNING_REGEXP = /The server is running at http:\/\/(.*?)\//; + +let server; +const { output } = webpackConfig.find(x => x.target === 'node'); +const serverPath = path.join(output.path, output.filename); + +// Launch or restart the Node.js server +function runServer(cb) { + function onStdOut(data) { + const time = new Date().toTimeString(); + const match = data.toString('utf8').match(RUNNING_REGEXP); + + process.stdout.write(time.replace(/.*(\d{2}:\d{2}:\d{2}).*/, '[$1] ')); + process.stdout.write(data); + + if (match) { + server.stdout.removeListener('data', onStdOut); + server.stdout.on('data', x => process.stdout.write(x)); + if (cb) { + cb(null, match[1]); + } + } + } + + if (server) { + server.kill('SIGTERM'); + } + + server = cp.spawn('node', [serverPath], { + env: Object.assign({ NODE_ENV: 'development' }, process.env), + silent: false, + }); + + server.stdout.on('data', onStdOut); + server.stderr.on('data', x => process.stderr.write(x)); +} + +process.on('exit', () => { + if (server) { + server.kill('SIGTERM'); + } +}); + +export default runServer; diff --git a/tools/serve.js b/tools/serve.js @@ -1,56 +0,0 @@ -/** - * React Starter Kit (http://www.reactstarterkit.com/) - * - * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE.txt file in the root directory of this source tree. - */ - -import path from 'path'; -import cp from 'child_process'; -import watch from './lib/watch'; - -/** - * Launches Node.js/Express web server in a separate (forked) process. - */ -function serve() { - return new Promise((resolve, reject) => { - function start() { - const server = cp.spawn( - 'node', - [path.join(__dirname, '../build/server.js')], - { - env: Object.assign({ NODE_ENV: 'development' }, process.env), - silent: false, - } - ); - server.stdout.on('data', data => { - let time = new Date().toTimeString(); - time = time.replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1'); - process.stdout.write(`[${time}] `); - process.stdout.write(data); - if (data.toString('utf8').includes('The server is running at')) { - resolve(); - } - }); - server.stderr.on('data', data => process.stderr.write(data)); - server.on('error', err => reject(err)); - process.on('exit', () => server.kill('SIGTERM')); - return server; - } - - let server = start(); - - if (global.WATCH) { - watch('build/server.js').then(watcher => { - watcher.on('changed', () => { - server.kill('SIGTERM'); - server = start(); - }); - }); - } - }); -} - -export default serve; diff --git a/tools/start.js b/tools/start.js @@ -7,55 +7,92 @@ * LICENSE.txt file in the root directory of this source tree. */ -import browserSync from 'browser-sync'; +import Browsersync from 'browser-sync'; import webpack from 'webpack'; -import webpackDevMiddleware from 'webpack-dev-middleware'; +import webpackMiddleware from 'webpack-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; import run from './run'; - -global.WATCH = true; -const webpackConfig = require('./webpack.config')[0]; // Client-side bundle configuration -const bundler = webpack(webpackConfig); +import runServer from './runServer'; +import webpackConfig from './webpack.config'; +import clean from './clean'; +import copy from './copy'; /** * Launches a development web server with "live reload" functionality - * synchronizing URLs, interactions and code changes across multiple devices. */ async function start() { - await run(require('./build')); - await run(require('./serve')); - - browserSync({ - proxy: { - - target: 'localhost:5000', - - middleware: [ - webpackDevMiddleware(bundler, { - // IMPORTANT: dev middleware can't access config, so we should - // provide publicPath by ourselves - publicPath: webpackConfig.output.publicPath, - - // Pretty colored output - stats: webpackConfig.stats, + await run(clean); + await run(copy.bind(undefined, { watch: true })); + await new Promise(resolve => { + // Patch the client-side bundle configurations + // to enable Hot Module Replacement (HMR) and React Transform + webpackConfig.filter(x => x.target !== 'node').forEach(config => { + if (Array.isArray(config.entry)) { + config.entry.unshift('webpack-hot-middleware/client'); + } else { + config.entry = ['webpack-hot-middleware/client', config.entry]; + } + config.plugins.push(new webpack.HotModuleReplacementPlugin()); + config.plugins.push(new webpack.NoErrorsPlugin()); + config.module.loaders + .filter(x => x.loader === 'babel-loader') + .forEach(x => x.query = { + // Wraps all React components into arbitrary transforms + // https://github.com/gaearon/babel-plugin-react-transform + plugins: ['react-transform'], + extra: { + 'react-transform': { + transforms: [ + { + transform: 'react-transform-hmr', + imports: ['react'], + locals: ['module'], + }, { + transform: 'react-transform-catch-errors', + imports: ['react', 'redbox-react'], + }, + ], + }, + }, + }); + }); - // For other settings see - // http://webpack.github.io/docs/webpack-dev-middleware.html - }), + const bundler = webpack(webpackConfig); + const wpMiddleware = webpackMiddleware(bundler, { + // IMPORTANT: webpack middleware can't access config, + // so we should provide publicPath by ourselves + publicPath: webpackConfig[0].output.publicPath, + // Pretty colored output + stats: webpackConfig[0].stats, + // For other settings see + // https://webpack.github.io/docs/webpack-dev-middleware + }); + const hotMiddlewares = bundler.compilers + .filter(compiler => compiler.options.target !== 'node') + .map(compiler => webpackHotMiddleware(compiler)); - // bundler should be the same as above - webpackHotMiddleware(bundler), - ], - }, + let handleServerBundleComplete = () => { + runServer((err, host) => { + if (!err) { + const bs = Browsersync.create(); + bs.init({ + proxy: { + target: host, + middleware: [wpMiddleware, ...hotMiddlewares], + }, + // no need to watch '*.js' here, webpack will take care of it for us, + // including full page reloads if HMR won't work + files: ['build/content/**/*.*'], + }, resolve); + handleServerBundleComplete = () => runServer(); + } + }); + }; - // no need to watch '*.js' here, webpack will take care of it for us, - // including full page reloads if HMR won't work - files: [ - 'build/public/**/*.css', - 'build/public/**/*.html', - 'build/content/**/*.*', - 'build/templates/**/*.*', - ], + bundler.compilers + .find(x => x.options.target === 'node') + .plugin('done', () => handleServerBundleComplete()); }); } diff --git a/tools/webpack.config.js b/tools/webpack.config.js @@ -14,7 +14,6 @@ import AssetsPlugin from 'assets-webpack-plugin'; const DEBUG = !process.argv.includes('--release'); const VERBOSE = process.argv.includes('--verbose'); -const WATCH = global.WATCH === undefined ? false : global.WATCH; const AUTOPREFIXER_BROWSERS = [ 'Android 2.3', 'Android >= 4', @@ -111,10 +110,7 @@ const config = { // ----------------------------------------------------------------------------- const clientConfig = merge({}, config, { - entry: [ - ...(WATCH ? ['webpack-hot-middleware/client'] : []), - './src/client.js', - ], + entry: './src/client.js', output: { path: path.join(__dirname, '../build/public'), filename: DEBUG ? '[name].js?[hash]' : '[name].[hash].js', @@ -138,36 +134,9 @@ const clientConfig = merge({}, config, { }), new webpack.optimize.AggressiveMergingPlugin(), ] : []), - ...(WATCH ? [ - new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin(), - ] : []), ], }); -// Enable React Transform in the "watch" mode -clientConfig.module.loaders - .filter(x => WATCH && x.loader === 'babel-loader') - .forEach(x => x.query = { - // Wraps all React components into arbitrary transforms - // https://github.com/gaearon/babel-plugin-react-transform - plugins: ['react-transform'], - extra: { - 'react-transform': { - transforms: [ - { - transform: 'react-transform-hmr', - imports: ['react'], - locals: ['module'], - }, { - transform: 'react-transform-catch-errors', - imports: ['react', 'redbox-react'], - }, - ], - }, - }, - }); - // // Configuration for the server-side bundle (server.js) // -----------------------------------------------------------------------------