diff --git a/README.md b/README.md index 3111568..a695d62 100644 --- a/README.md +++ b/README.md @@ -13,27 +13,27 @@ - + - - + + - - + + - +
-Frontend Dev REPL +Frontend Playground
@@ -43,7 +43,7 @@ Frontend Dev REPL ## Install ```sh -npm install aik +npm install -g aik ``` ## Usage diff --git a/cli.js b/cli.js new file mode 100755 index 0000000..7c3ae12 --- /dev/null +++ b/cli.js @@ -0,0 +1,40 @@ +#! /usr/bin/env node +'use strict'; + +var _meow = require('meow'); + +var _meow2 = _interopRequireDefault(_meow); + +var _lib = require('./lib/'); + +var _lib2 = _interopRequireDefault(_lib); + +var _isEmpty = require('lodash/lang/isEmpty'); + +var _isEmpty2 = _interopRequireDefault(_isEmpty); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var cli = (0, _meow2.default)({ + help: ['Usage', ' $ aik filename.js', '', 'Options', ' -p, --port Web server port. [Default: 8080]', ' -h, --host Web server host. [Default: localhost]', ' -n, --ngrok Exposes server to real world by ngrok.', ' -c, --cssmodules Enables css modules.', ' --help Shows help.', '', 'Examples', ' $ aik filename.js --port 3000 -n -cm', ' Runs aik web server on 3000 port with ngrok and css modules support'] +}, { + alias: { + p: 'port', + h: 'host', + n: 'ngrok', + c: 'cssmodules' + }, + default: { + port: 8080, + host: 'localhost' + } +}); + +var input = cli.input || []; +var flags = cli.flags || {}; + +if ((0, _isEmpty2.default)(input) || flags.help) { + console.log(cli.help); // eslint-disable-line +} else { + (0, _lib2.default)(input, flags); + } \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..1ff74ac --- /dev/null +++ b/lib/index.js @@ -0,0 +1,60 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + +exports.banner = banner; +exports.default = aikDevServer; + +var _chalk = require('chalk'); + +var _chalk2 = _interopRequireDefault(_chalk); + +var _webpack = require('./webpack'); + +var _webpack2 = _interopRequireDefault(_webpack); + +var _ngrok = require('./ngrok'); + +var _ngrok2 = _interopRequireDefault(_ngrok); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function banner(filename, flags, ngrokUrl) { + return '\n /$$$$$$ /$$$$$$ /$$ /$$\n /$$__ $$|_ $$_/| $$ /$$/\n| $$ $$ | $$ | $$ /$$/\n| $$$$$$$$ | $$ | $$$$$/\n| $$__ $$ | $$ | $$ $$\n| $$ | $$ | $$ | $$ $$\n| $$ | $$ /$$$$$$| $$ $$\n|__/ |__/|______/|__/ __/\n\n ' + _chalk2.default.yellow('Frontend Playground') + '\n\n' + _chalk2.default.magenta('Server:') + ' ' + _chalk2.default.cyan('http://' + flags.host + ':' + flags.port) + '\n' + _chalk2.default.magenta('CSS Modules:') + ' ' + (flags.cssmodules ? _chalk2.default.green('enabled') : _chalk2.default.dim('disabled')) + '\n' + _chalk2.default.magenta('Ngrok:') + ' ' + (flags.ngrok ? _chalk2.default.green(ngrokUrl) : _chalk2.default.dim('disabled')) + '\n'; +} + +/** + * Aik dev server + * + * @param {String[]} input + * @param {Object} flags + * @param {String} flags.port + * @param {String} flags.host + * @param {Boolean} flags.ngrok + * @param {Boolean} flags.cssmodules + * + * @return {Type} + */ +function aikDevServer(input, flags) { + var _input = _slicedToArray(input, 1); + + var filename = _input[0]; + + var promiseList = [(0, _webpack2.default)(filename, flags), flags.ngrok && (0, _ngrok2.default)(flags)]; + + return Promise.all(promiseList).then(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2); + + var ngrokUrl = _ref2[1]; + + console.log(banner(filename, flags, ngrokUrl)); // eslint-disable-line + }).catch(function (err) { + console.error(_chalk2.default.red(err)); // eslint-disable-line + + throw err; + }); +} \ No newline at end of file diff --git a/lib/ngrok.js b/lib/ngrok.js new file mode 100644 index 0000000..b964e68 --- /dev/null +++ b/lib/ngrok.js @@ -0,0 +1,24 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = createNgrokTunnel; + +var _ngrok = require('ngrok'); + +var _ngrok2 = _interopRequireDefault(_ngrok); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function createNgrokTunnel(flags) { + return new Promise(function (resolve, reject) { + _ngrok2.default.connect(flags.port, function (err, url) { + if (err) { + return reject(err); + } + + resolve(url); + }); + }); +} \ No newline at end of file diff --git a/lib/webpack-config-builder.js b/lib/webpack-config-builder.js new file mode 100644 index 0000000..af0ddb8 --- /dev/null +++ b/lib/webpack-config-builder.js @@ -0,0 +1,81 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.makeAbsolutePathToNodeModules = makeAbsolutePathToNodeModules; +exports.setupEntry = setupEntry; +exports.setupOutput = setupOutput; +exports.setupPlugins = setupPlugins; +exports.setupLoaders = setupLoaders; +exports.default = webpackConfigBuilder; + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _webpack = require('webpack'); + +var _webpack2 = _interopRequireDefault(_webpack); + +var _htmlWebpackPlugin = require('html-webpack-plugin'); + +var _htmlWebpackPlugin2 = _interopRequireDefault(_htmlWebpackPlugin); + +var _npmInstallWebpackPlugin = require('npm-install-webpack-plugin'); + +var _npmInstallWebpackPlugin2 = _interopRequireDefault(_npmInstallWebpackPlugin); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function makeAbsolutePathToNodeModules(relativePath) { + return _path2.default.join(__dirname, '..', 'node_modules', relativePath); +} + +function setupEntry(filename, host, port) { + host = host === '::' ? 'localhost' : host; + + return { + app: [makeAbsolutePathToNodeModules('webpack-dev-server/client') + '?http://' + host + ':' + port + '/', makeAbsolutePathToNodeModules('webpack/hot/only-dev-server'), _path2.default.join(process.cwd(), filename)] + }; +} + +function setupOutput(filename) { + return { + path: _path2.default.join(process.cwd(), _path2.default.dirname(filename)), + filename: 'index.js', + hash: true + }; +} + +function setupPlugins() { + return [new _webpack2.default.HotModuleReplacementPlugin(), new _htmlWebpackPlugin2.default(), new _npmInstallWebpackPlugin2.default({ + save: false, + saveDev: false, + saveExact: false + })]; +} + +function setupLoaders(cssmodules) { + return [{ + test: /\.css$/, + loaders: [makeAbsolutePathToNodeModules('style-loader'), makeAbsolutePathToNodeModules('css-loader') + (cssmodules ? '?modules' : '')] + }, { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loaders: [makeAbsolutePathToNodeModules('react-hot-loader'), makeAbsolutePathToNodeModules('babel-loader') + '?presets[]=react,presets[]=es2015'] + }]; +} + +function webpackConfigBuilder(filename, flags) { + return { + entry: setupEntry(filename, flags.host, flags.port), + output: setupOutput(filename), + debug: true, + devtool: 'source-map', + plugins: setupPlugins(), + module: { + loaders: setupLoaders(flags.cssmodules) + } + }; +} \ No newline at end of file diff --git a/lib/webpack.js b/lib/webpack.js new file mode 100644 index 0000000..87a9c4c --- /dev/null +++ b/lib/webpack.js @@ -0,0 +1,41 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = createWebpackDevServer; + +var _webpack = require('webpack'); + +var _webpack2 = _interopRequireDefault(_webpack); + +var _webpackDevServer = require('webpack-dev-server'); + +var _webpackDevServer2 = _interopRequireDefault(_webpackDevServer); + +var _webpackConfigBuilder = require('./webpack-config-builder'); + +var _webpackConfigBuilder2 = _interopRequireDefault(_webpackConfigBuilder); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function createWebpackDevServer(filename, flags) { + var config = (0, _webpackConfigBuilder2.default)(filename, flags); + var compiler = (0, _webpack2.default)(config); + var server = new _webpackDevServer2.default(compiler, { + hot: true, + colors: true, + noInfo: true, + stats: { colors: true } + }); + + return new Promise(function (resolve, reject) { + server.listen(flags.port, flags.host, function (err) { + if (err) { + return reject(err); + } + + resolve(server); + }); + }); +} \ No newline at end of file diff --git a/package.json b/package.json index 41afbae..9b9366d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "aik", "version": "0.0.0", - "description": "Frontend Dev REPL", + "description": "Frontend Playground", "bin": "cli.js", "main": "lib/index.js", "files": [ @@ -9,7 +9,18 @@ "cli.js" ], "keywords": [ - "node","frontend","repl","react","babel","webpack","playground","experiments","boostrap","boilerplate", "postcss", "cssmodules" + "node", + "frontend", + "repl", + "react", + "babel", + "webpack", + "playground", + "experiments", + "boostrap", + "boilerplate", + "postcss", + "cssmodules" ], "license": "MIT", "repository": { @@ -42,8 +53,20 @@ "validate" ], "dependencies": { + "babel-core": "^6.5.2", + "babel-loader": "^6.2.3", + "babel-preset-es2015": "^6.5.0", + "babel-preset-react": "^6.5.0", + "chalk": "^1.1.1", + "css-loader": "^0.23.1", + "html-webpack-plugin": "^2.9.0", "meow": "^3.7.0", - "chalk": "^1.1.1" + "ngrok": "0.1.99", + "npm-install-webpack-plugin": "git://github.com/d4rkr00t/npm-install-webpack-plugin.git#search-in-nodemodules", + "react-hot-loader": "^1.3.0", + "style-loader": "^0.13.0", + "webpack": "^1.12.14", + "webpack-dev-server": "^1.14.1" }, "devDependencies": { "ava": "^0.11.0", diff --git a/src/cli.js b/src/cli.js index dbd3634..de7ab74 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,27 +1,42 @@ #! /usr/bin/env node - import meow from 'meow'; import aik from './lib/'; +import isEmpty from 'lodash/lang/isEmpty'; const cli = meow({ help: [ 'Usage', - ' $ aik [input]', + ' $ aik filename.js', '', 'Options', - ' --foo Lorem ipsum. [Default: false]', + ' -p, --port Web server port. [Default: 8080]', + ' -h, --host Web server host. [Default: localhost]', + ' -n, --ngrok Exposes server to real world by ngrok.', + ' -c, --cssmodules Enables css modules.', + ' --help Shows help.', '', 'Examples', - ' $ aik', - ' unicorns & rainbows', - ' $ aik ponies', - ' ponies & rainbows' + ' $ aik filename.js --port 3000 -n -cm', + ' Runs aik web server on 3000 port with ngrok and css modules support' ] +}, { + alias: { + p: 'port', + h: 'host', + n: 'ngrok', + c: 'cssmodules' + }, + default: { + port: 8080, + host: 'localhost' + } }); const input = cli.input || []; const flags = cli.flags || {}; -console.log(cli.help); // eslint-disable-line - -aik(input, flags); +if (isEmpty(input) || flags.help) { + console.log(cli.help); // eslint-disable-line +} else { + aik(input, flags); +} diff --git a/src/lib/index.js b/src/lib/index.js index 4cdcf3b..4c12544 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1,8 +1,54 @@ +import chalk from 'chalk'; + +import createWebpackDevServer from './webpack'; +import createNgrokTunnel from './ngrok'; + +export function banner(filename, flags, ngrokUrl) { + return ` + /$$$$$$ /$$$$$$ /$$ /$$ + /$$__ $$|_ $$_/| $$ /$$/ +| $$ \ $$ | $$ | $$ /$$/ +| $$$$$$$$ | $$ | $$$$$/ +| $$__ $$ | $$ | $$ $$ +| $$ | $$ | $$ | $$\ $$ +| $$ | $$ /$$$$$$| $$ \ $$ +|__/ |__/|______/|__/ \__/ + + ${chalk.yellow('Frontend Playground')} + +${chalk.magenta('Server:')} ${chalk.cyan(`http://${flags.host}:${flags.port}`)} +${chalk.magenta('CSS Modules:')} ${flags.cssmodules ? chalk.green('enabled') : chalk.dim('disabled')} +${chalk.magenta('Ngrok:')} ${flags.ngrok ? chalk.green(ngrokUrl) : chalk.dim('disabled')} +`; +} + /** - * Main function + * Aik dev server + * + * @param {String[]} input + * @param {Object} flags + * @param {String} flags.port + * @param {String} flags.host + * @param {Boolean} flags.ngrok + * @param {Boolean} flags.cssmodules * * @return {Type} */ -export default function () { - return true; +export default function aikDevServer(input, flags) { + const [filename] = input; + const promiseList = [ + createWebpackDevServer(filename, flags), + flags.ngrok && createNgrokTunnel(flags) + ]; + + return Promise + .all(promiseList) + .then(([, ngrokUrl]) => { + console.log(banner(filename, flags, ngrokUrl)); // eslint-disable-line + }) + .catch((err) => { + console.error(chalk.red(err)); // eslint-disable-line + + throw err; + }); } diff --git a/src/lib/ngrok.js b/src/lib/ngrok.js new file mode 100644 index 0000000..b7194c1 --- /dev/null +++ b/src/lib/ngrok.js @@ -0,0 +1,13 @@ +import ngrok from 'ngrok'; + +export default function createNgrokTunnel(flags) { + return new Promise((resolve, reject) => { + ngrok.connect(flags.port, (err, url) => { + if (err) { + return reject(err); + } + + resolve(url); + }); + }); +} diff --git a/src/lib/webpack-config-builder.js b/src/lib/webpack-config-builder.js new file mode 100644 index 0000000..1e46a9c --- /dev/null +++ b/src/lib/webpack-config-builder.js @@ -0,0 +1,73 @@ +import path from 'path'; +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import NpmInstallPlugin from 'npm-install-webpack-plugin'; + +export function makeAbsolutePathToNodeModules(relativePath) { + return path.join(__dirname, '..', 'node_modules', relativePath); +} + +export function setupEntry(filename, host, port) { + host = host === '::' ? 'localhost' : host; + + return { + app: [ + `${makeAbsolutePathToNodeModules('webpack-dev-server/client')}?http://${host}:${port}/`, + makeAbsolutePathToNodeModules('webpack/hot/only-dev-server'), + path.join(process.cwd(), filename) + ] + }; +} + +export function setupOutput(filename) { + return { + path: path.join(process.cwd(), path.dirname(filename)), + filename: 'index.js', + hash: true + }; +} + +export function setupPlugins() { + return [ + new webpack.HotModuleReplacementPlugin(), + new HtmlWebpackPlugin(), + new NpmInstallPlugin({ + save: false, + saveDev: false, + saveExact: false + }) + ]; +} + +export function setupLoaders(cssmodules) { + return [ + { + test: /\.css$/, + loaders: [ + makeAbsolutePathToNodeModules('style-loader'), + makeAbsolutePathToNodeModules('css-loader') + (cssmodules ? '?modules' : '') + ] + }, + { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loaders: [ + makeAbsolutePathToNodeModules('react-hot-loader'), + `${makeAbsolutePathToNodeModules('babel-loader')}?presets[]=react,presets[]=es2015` + ] + } + ]; +} + +export default function webpackConfigBuilder(filename, flags) { + return { + entry: setupEntry(filename, flags.host, flags.port), + output: setupOutput(filename), + debug: true, + devtool: 'source-map', + plugins: setupPlugins(), + module: { + loaders: setupLoaders(flags.cssmodules) + } + }; +} diff --git a/src/lib/webpack.js b/src/lib/webpack.js new file mode 100644 index 0000000..d129d95 --- /dev/null +++ b/src/lib/webpack.js @@ -0,0 +1,24 @@ +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; +import webpackConfigBuilder from './webpack-config-builder'; + +export default function createWebpackDevServer(filename, flags) { + const config = webpackConfigBuilder(filename, flags); + const compiler = webpack(config); + const server = new WebpackDevServer(compiler, { + hot: true, + colors: true, + noInfo: true, + stats: { colors: true } + }); + + return new Promise((resolve, reject) => { + server.listen(flags.port, flags.host, (err) => { + if (err) { + return reject(err); + } + + resolve(server); + }); + }); +} diff --git a/test/index.js b/test/index.js index 84faa23..f361a74 100644 --- a/test/index.js +++ b/test/index.js @@ -4,5 +4,5 @@ import 'babel-core/register'; import aik from '../src/lib/'; test('aik', (t) => { - t.is(aik(), true); + t.ok(aik()); });