diff --git a/Procfile b/Procfile index 14ff2c2..e449d3d 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: ./node_modules/.bin/rollup -c rollup-build.config.js -o dist/index.js -f iife -n UiZoo -g underscore:_,react:React,react-dom:ReactDOM,react-router-dom:ReactRouterDOM,doctrine:doctrine-standalone,babel-standalone:Babel && node server/main.js \ No newline at end of file +web: ./node_modules/.bin/rollup -c rollup.config.js -o dist/index.js -f iife -n UiZoo && node lib/server/main.js \ No newline at end of file diff --git a/README.md b/README.md index bab04f0..c62b6b8 100644 --- a/README.md +++ b/README.md @@ -17,32 +17,43 @@ This tool can be used for developing, for Product Managers to know what is possi ![React UiZoo 3](https://imgur.com/f3B2TDj.gif) ## How To UiZoo? -Git clone by: + +Just use our zero-configuration CLI! it's easy as pie! 🍽 + ``` -git clone git@github.com:myheritage/uizoo.js.git +npm i -g uizoo ``` -then + +In a directory, do: ``` -cd uizoo.js && npm i -gulp +uizoo ``` -This will start a server on http://localhost:5000 with the UiZoo -you can change the [components file](https://github.com/myheritage/uizoo.js/blob/master/client/components.js) and the [documentation file](https://github.com/myheritage/uizoo.js/blob/master/client/documentation.js) to start rapidly. -We recommend updating those files by a script automatically when files are changing (we plan to create plugins to help with this in the next future). -*or* npm install by: +It will create a [webpack development server](https://webpack.js.org/configuration/dev-server/) fully configured with [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) to watch your files while you develop! + +For example: + +![React UiZoo CLI](https://imgur.com/v3PbP8U.gif) + +Start the server with the newly added script: ``` -npm i -S uizoo +npm start uizoo ``` -then in your code, add: + +### Customization +The CLI creates a directory called `uizoo-app`, in it there is a file called `config.js` that determine basic stuff like the server's port, glob to find your components and more. There is also a very simple webpack configuration called `webpack.uizoo.js`. + + +### Local installation +*If you don't want to install UiZoo globally, you can instead do:* ``` -import 'uizoo/dist/index.css'; -import UiZoo from 'uizoo'; -UiZoo.init(documentation, components, rootElement); +npm i -D uizoo && ./node_modules/.bin/uizoo ``` -### init +### API ``` +import UiZoo from 'uizoo'; + UiZoo.init(documentation: Object, components: Object, rootElement: HTMLElement?, baseRoute: String?) ``` @@ -50,7 +61,7 @@ UiZoo.init(documentation: Object, components: Object, rootElement: HTMLElement?, **components** - Object, mapping of components name to components. See [example](https://github.com/myheritage/uizoo.js/blob/master/client/components.js). -**rootElement** - HTMLElement, will bootstrap UiZoo on that Element. Default is an element with the id 'library-_-root' +**rootElement** - HTMLElement, will bootstrap UiZoo on that Element. Default is a new element on the body. **baseRoute** Route to be the base before the UiZoo routes. Default to '/'. for example if the UiZoo is on your site like so: 'www.mysite.com/my/zoo/', the base route should be '/my/zoo/'. @@ -108,7 +119,7 @@ To add tests, use the following steps - First, make sure the app is up and running: ``` -gulp +npm start ``` The first time tests are run, install the npm dependencies: ``` diff --git a/bin/uizoo.js b/bin/uizoo.js new file mode 100644 index 0000000..207ce01 --- /dev/null +++ b/bin/uizoo.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +const chalk = require('chalk'); +const logo = require('../lib/logo'); +const log = console.log.bind(console); + +log(` +Welcome to the ~ + ${chalk.bold(chalk.cyan(logo))} +`); + +require('../lib/generate')(); \ No newline at end of file diff --git a/client/Components/UI/Tooltip/UiTooltip/index.js b/client/Components/UI/Tooltip/UiTooltip/index.js index b1e68ac..7894f3b 100644 --- a/client/Components/UI/Tooltip/UiTooltip/index.js +++ b/client/Components/UI/Tooltip/UiTooltip/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import _ from 'underscore'; import {SIDES, SIDE_TOP, ALIGNMENTS, ALIGNMENT_CENTER, TRIGGER_EVENTS, TRIGGER_EVENT_HOVER} from './constants'; import './index.scss'; diff --git a/client/Components/UI/Tooltip/autoLocationDetector/autoLocationDetector.js b/client/Components/UI/Tooltip/autoLocationDetector/autoLocationDetector.js index 416e859..bd80331 100644 --- a/client/Components/UI/Tooltip/autoLocationDetector/autoLocationDetector.js +++ b/client/Components/UI/Tooltip/autoLocationDetector/autoLocationDetector.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import { SIDE_TOP, SIDE_BOTTOM, diff --git a/client/index.js b/client/index.js index 4362f2e..cb7a55a 100644 --- a/client/index.js +++ b/client/index.js @@ -1,6 +1,7 @@ import './index.scss'; import React from 'react'; import ReactDOM from 'react-dom'; +import _ from 'underscore'; import { BrowserRouter, Route } from 'react-router-dom'; import libraryData from './components'; @@ -9,22 +10,26 @@ import { checkDependencies } from './services/checkHealth'; import { createCompiler } from './services/compileWithContext'; import { parseDocumentation } from './services/parseDocumentation'; import App from './Components/App'; -import mapComponentsByModule from "./services/componentByModuleMapper"; +import mapComponentsByModule from './services/componentByModuleMapper'; -const defaultRoot = document.getElementById('library-_-root'); +window._extend = _.extend; // to be used instead of Object.assign /** - * Init - * @param {Object} documentation - * @param {Object} components - * @param {HTMLElement} rootElement + * Init - render UiZoo with documentation and components mappings + * @param {Object} [documentation] + * @param {Object} [components] + * @param {HTMLElement} [rootElement] will default to a new element on the body */ function init( documentation = libraryDocs, components = libraryData, - rootElement = defaultRoot, + rootElement, baseRoute = '/' ) { + if (!rootElement) { + rootElement = document.createElement('div'); + document.body.appendChild(rootElement); + } checkDependencies(documentation, components); const compiler = createCompiler(components); // JSX compiler diff --git a/client/services/compileWithContext.js b/client/services/compileWithContext.js index 5dd0c3c..5dd9b9a 100644 --- a/client/services/compileWithContext.js +++ b/client/services/compileWithContext.js @@ -1,6 +1,6 @@ import React from 'react'; import _ from 'underscore'; -import Babel from 'babel-standalone'; +import * as Babel from 'babel-standalone'; export function createCompiler(context) { const iframe = createIframe(); diff --git a/gulpfile.js b/gulpfile.js index 8df9afe..f55e4bd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,8 +1,8 @@ let gulp = require('gulp'), rollup = require('rollup'), - getRollupConfig = require('./rollup.config'), + rConfig = require('./rollup.config'), chalk = require('chalk'), - nodemon = require("gulp-nodemon"), + nodemon = require('gulp-nodemon'), livereload = require('gulp-livereload'), execSync = require("child_process").execSync; @@ -31,20 +31,13 @@ gulp.task("watch", () => { function bundleClient() { updateDocumentation(); - return rollup.rollup(getRollupConfig({external: ['underscore', 'react', 'react-dom', 'react-router-dom', 'doctrine-standalone', 'babel-standalone']})) + return rollup.rollup(rConfig) .then(bundle => { bundle.write({ format: 'iife', file: 'dist/index.js', - globals: { - 'underscore': '_', - 'react': 'React', - 'react-dom': 'ReactDOM', - 'react-router-dom':'ReactRouterDOM', - 'doctrine-standalone': 'doctrine', - 'babel-standalone': 'Babel', - }, name: 'UiZoo', + globals: rConfig.globals }); }) .catch(handleError); @@ -52,7 +45,7 @@ function bundleClient() { function startNodemonServer() { nodemonStream = nodemon({ - script: './server/main.js', + script: './lib/server/main.js', ext: 'js html', watch: false, }) @@ -78,7 +71,7 @@ function handleError(error) { function updateDocumentation() { try { - execSync(`node documentationMapper.js "./client/Components/UI/*/index.js" "./client/Components/UI/(.+)/index.js" "./client/documentation.js"`); + execSync(`node lib/scripts/documentationMapper.js "./client/Components/UI/*/index.js" "./client/Components/UI/(.+)/index.js" "./client/documentation.js"`); } catch (err) { console.error(err.message); diff --git a/index.html b/index.html index f928fcd..1a9f3e3 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,6 @@ -
diff --git a/lib/generate/config.js b/lib/generate/config.js new file mode 100644 index 0000000..99b1e26 --- /dev/null +++ b/lib/generate/config.js @@ -0,0 +1,39 @@ +const path = require('path'); + +const appFolderPath = path.resolve('uizoo-app'); +const uiZooScript = `node ${path.join('uizoo-app', 'webpack.uizoo.js')}`; + +const neededPackages = [ + 'uizoo', + 'webpack', + 'webpack-dev-server', + 'babel-loader', + 'babel-preset-env', + 'babel-preset-react', + 'babel-plugin-transform-object-rest-spread', + 'babel-plugin-syntax-dynamic-import', + 'style-loader', + 'css-loader', + 'fs-extra', + 'glob-stream' +]; + +const templatesToCopy = [ + 'index.html', + 'index.js', + 'webpack.uizoo.js', + 'createConfigsScript.js', + 'componentsContainer.js', + 'documentationContainer.js', + 'config.js', +]; + +const log = console.log.bind(console); + +module.exports = { + appFolderPath, + uiZooScript, + neededPackages, + templatesToCopy, + log +}; \ No newline at end of file diff --git a/lib/generate/index.js b/lib/generate/index.js new file mode 100644 index 0000000..9a7214c --- /dev/null +++ b/lib/generate/index.js @@ -0,0 +1,23 @@ +const fs = require('fs-extra'); +const opn = require('opn'); + +const {updatePackageJson} = require('./packageJsonService'); +const {copyTemplate} = require('./templatesService'); +const {executeCommand} = require('./processService'); +const {log, templatesToCopy, appFolderPath} = require('./config'); + +function generate() { + log(' ~ Copying templates...\n'); + updatePackageJson() + .then(() => fs.ensureDir(appFolderPath)) + .then(() => Promise.all(templatesToCopy.map(copyTemplate))) + .then(() => log(' ~ Executing commands...\n')) + .then(() => executeCommand('npm i')) + .then(() => {setTimeout(() => opn('http://localhost:5005/uizoo'), 2500)}) + .then(() => log(' ~ Done! Executing `uizoo` script:\n')) + .then(() => executeCommand('npm run uizoo')) + .then(() => process.exit(0)) + .catch(e => console.error(e)); +} + +module.exports = generate; \ No newline at end of file diff --git a/lib/generate/packageJsonService.js b/lib/generate/packageJsonService.js new file mode 100644 index 0000000..781c7f1 --- /dev/null +++ b/lib/generate/packageJsonService.js @@ -0,0 +1,55 @@ +const path = require('path'); +const latestVersion = require('latest-version'); +const fs = require('fs-extra'); +const {resolveTemplatePath} = require('./templatesService'); +const {uiZooScript, neededPackages} = require('./config'); + +module.exports = { + updatePackageJson +}; + +/** + * Update the folder's package.json with needed dependencies or use a fresh one from templates + * if there is none + * @return {Promise} + */ +function updatePackageJson() { + return new Promise((resolve, reject) => { + const pkgPath = path.resolve('package.json'); + const defaultPkgPath = resolveTemplatePath('package.json'); + + fs.exists(pkgPath) + .then(exists => fs.readFile(exists ? pkgPath : defaultPkgPath)) + .then(modifyPackage) + .then(pkg => fs.writeJSON(pkgPath, pkg, {spaces: 2})) + .then(resolve) + .catch(reject); + }); +} + +function modifyPackage(packageBuffer) { + return new Promise((resolve, reject) => { + Promise.all(neededPackages.map(latestVersion)) + .then(neededPackagesVersions => { + let pkg = JSON.parse(packageBuffer.toString()); + + // Add dependencies + neededPackages.forEach((neededPackage, i) => { + if((!pkg.dependencies || !pkg.dependencies[neededPackage]) && + (!pkg.devDependencies || !pkg.devDependencies[neededPackage])) { + + pkg.devDependencies = Object.assign({}, pkg.devDependencies, { + [neededPackage]: `~${neededPackagesVersions[i]}` + }); + } + }); + + // Add scripts + pkg.scripts = Object.assign({}, pkg.scripts, { + "uizoo": uiZooScript + }); + resolve(pkg); + }) + .catch(reject); + }); +} \ No newline at end of file diff --git a/lib/generate/processService.js b/lib/generate/processService.js new file mode 100644 index 0000000..8ba6658 --- /dev/null +++ b/lib/generate/processService.js @@ -0,0 +1,21 @@ +const {spawn} = require('child_process'); +const chalk = require('chalk'); +const {log} = require('./config'); + +module.exports = { + executeCommand +}; + +/** + * Execute a command to terminal with inherit io, resolve when it is done + * @param {String} cmd + * @return {Promise} + */ +function executeCommand(cmd) { + return new Promise((resolve, reject) => { + log(chalk.grey(` ${cmd}\n`)); + let cmdParts = cmd.split(' '); + const ls = spawn(cmdParts.shift(), [].concat(cmdParts), {stdio: "inherit"}); + ls.on('close', resolve); + }); +} \ No newline at end of file diff --git a/lib/generate/templates/componentsContainer.js b/lib/generate/templates/componentsContainer.js new file mode 100644 index 0000000..4b0f9fb --- /dev/null +++ b/lib/generate/templates/componentsContainer.js @@ -0,0 +1 @@ +export default null; \ No newline at end of file diff --git a/lib/generate/templates/config.js b/lib/generate/templates/config.js new file mode 100644 index 0000000..0af62b7 --- /dev/null +++ b/lib/generate/templates/config.js @@ -0,0 +1,58 @@ +const path = require('path'); + +/** + * Dev Server config + */ +const serverPort = 5005; +const serverProtocol = process.env.HTTPS === 'true' ? 'https' : 'http'; +const serverHost = process.env.HOST || '0.0.0.0'; + +/** + * The dir where the original command to create UiZoo was originated from, + * it is where the node_modules are and where to look for components from + */ +const componentsRootDir = path.dirname(__dirname); + +/** + * TIL that glob use '/' separators everywhere! + */ +const componentsRootsDirGlob = path.sep === '\\' ? componentsRootDir.split(path.sep).join('/') : componentsRootDir; + +/** + * Glob to fetch all components for UiZoo + * It exclude node_modules & uizoo-app directories + * If you have a specific convention to your Components names, or specific sub-libraries, you should add it + * + * You can provide either a single glob or an array that will be aggregated + * (using 'glob-stream', so you can negate and stuff, see: https://github.com/gulpjs/glob-stream) + */ +const componentsGlob = [ + `${componentsRootsDirGlob}/**/*.js`, + `!${componentsRootsDirGlob}/node_modules/**/*`, + `!${componentsRootsDirGlob}/uizoo-app/**/*`, +]; + +/** + * Add this tag to a component JSDoc will exclude it from the config files of UiZoo + * @example + * This will exclude this component: + * @componentLibraryIgnore + */ +const ignoreTag = 'componentLibraryIgnore'; + +/** + * Strategy to decide on the file's JSDoc by a regex + * It will take the first JSDoc comment answering this regex + * It should have either a @description or @example tag in it + */ +const componentMainCommentRegex = /\/\*\*(\s*\*\s*.*?)*@(description|example).*(\s*\*.*)*/g; + +module.exports = { + serverPort, + serverProtocol, + serverHost, + componentsRootDir, + componentsGlob, + ignoreTag, + componentMainCommentRegex, +}; \ No newline at end of file diff --git a/lib/generate/templates/createConfigsScript.js b/lib/generate/templates/createConfigsScript.js new file mode 100644 index 0000000..40224a2 --- /dev/null +++ b/lib/generate/templates/createConfigsScript.js @@ -0,0 +1,198 @@ +const gs = require('glob-stream'); +const path = require('path'); +const fs = require('fs-extra'); +const doctrine = require('doctrine'); + +const { + ignoreTag, + componentMainCommentRegex, + componentsGlob, + componentsRootDir +} = require('./config'); + +module.exports = createConfigs; + +/** + * Create 2 config files + * One is a mapping between components names to actual components + * and another is a mapping between components names to their documentation + * @return {Promise} + */ +function createConfigs() { + return promiseGlob(componentsGlob) + .then(filePaths => readFiles(filePaths) + .then(filesData => processFiles(filesData, filePaths)) + ) + .then(writeFiles); +} + +/** + * @param {Array} filePaths + * @return {Promise} + */ +function readFiles(filePaths) { + return Promise.all(filePaths.map(filePath => fs.readFile(filePath))); +} + +/** + * Process components files and output a map that we can write to files + * @param {Array} filesData + * @param {Array} filePaths + * @return {Map} of component name to an object in the form {filePath, comment} + */ +function processFiles(filesData = [], filePaths = []) { + let componentsMap = new Map(); + + filesData.forEach((fileDataBuffer, i) => { + const fileData = fileDataBuffer.toString(); + // Get component main comment + const matches = fileData.match(componentMainCommentRegex); + if (matches && matches.length) { + // Choosing the first comment that matched the regex + let comment = matches[0], + filePath = filePaths[i]; + + const parsedComment = parseCommentToObject(comment); + // skip this components if it have an ignore tag + if (!parsedComment[ignoreTag]) { + const componentName = getComponentName(parsedComment, filePath); + componentsMap.set(componentName, {filePath, comment}); + } + } + }); + + return componentsMap; +} + +/** + * Write the map data to files + * It will sort the component names + * It will skip the writing if there is nothing to write or nothing had changed + * + * @param {Map} componentsMap + * @return {Promise} + */ +function writeFiles(componentsMap) { + let componentsKeys = [...componentsMap.keys()]; + if (!componentsKeys.length) return null; + let docMap = new Map(), + comMap = new Map(); + + componentsKeys.sort(); + componentsKeys.forEach(componentName => { + let componentConf = componentsMap.get(componentName); + docMap.set(componentName, componentConf.comment); + comMap.set(componentName, componentConf.filePath); + }); + + return Promise.all([ + writeIfDifferent('documentationContainer.js', createDocumentationFile(docMap)), + writeIfDifferent('componentsContainer.js', createComponentsFile(comMap)) + ]); +} + +/** + * Strategy to figure out the component name + * + * @param {Object} parsedComment + * @param {String} filePath + */ +function getComponentName(parsedComment, filePath) { + let name; + if (parsedComment.name && parsedComment.name[0] && parsedComment.name[0].name) { + // Option 1: name in JSDoc @name tag + name = parsedComment.name[0].name; + } else { + let fileParts = path.parse(filePath) || {}; + if (fileParts.name && !(/^index/.test(fileParts.name))) { + // Option 2: try to get name from file name, but make sure it's not index.~ something + name = fileParts.name; + } else { + // Option 3: nothing left - take dir name + name = path.basename(fileParts.dir); // parent's directory + } + } + return name; +} + +/** + * Create the documentation file from the map + * @param {Map} docMap + * @return {String} + */ +function createDocumentationFile(docMap) { + let docFile = 'export default {\n'; + for (let [componentName, comment] of docMap) { + // escaping ` and ${} by \` and \${} + docFile += `${componentName}: \`${comment.replace(/\`/g, '\\`').replace(/\$\{/g, '\\${')}\`,\n`; + } + docFile += '}'; + return docFile; +} + +/** + * Create the components file from the map + * @param {Map} comMap + * @return {String} + */ +function createComponentsFile(comMap) { + let comFile = '', + exportLine = 'export default {\n'; + for (let [componentName, filePath] of comMap) { + comFile += `import ${componentName} from '${filePath.replace(componentsRootDir, '..')}';\n`; + exportLine += ` ${componentName},\n`; + } + comFile += `${exportLine}};`; + return comFile; +} + +/** + * Write only if current content is different to prevent + * @param {String} fileName + * @param {String} fileContent + * @return {Promise} + */ +function writeIfDifferent(fileName, fileContent) { + const filePath = path.join(componentsRootDir, 'uizoo-app', fileName); + return new Promise((resolve, reject) => { + fs.readFile(filePath) + .then(fileBuffer => fileContent !== fileBuffer.toString()) + .then(shouldWrite => shouldWrite ? fs.writeFile(filePath, fileContent) : true) + .then(resolve) + .catch(reject); + }); +} + +/** + * Us doctrine to transform String comment to an Object + * @param {String} comment + * @return {Object} + */ +function parseCommentToObject(comment) { + const {tags = []} = doctrine.parse(comment, {unwrap: true, recoverable: true, sloppy: true}); + let doc = {}; + tags.forEach(tag => { + doc[tag.title] = doc[tag.title] || []; + doc[tag.title].push(tag); + }); + return doc; +} + +/** + * Turn the glob stream to a promise that reslove the files + * @param {Array|String} globs + * @return {Promise} + */ +function promiseGlob(globs) { + return new Promise((resolve, reject) => { + let files = []; + let ls = gs(componentsGlob); + ls.on('data', (f = {}) => { + if (f.path) files.push(f.path); + }); + ls.on('error', reject); + ls.on('end', () => { + resolve(files); + }); + }); +} \ No newline at end of file diff --git a/lib/generate/templates/documentationContainer.js b/lib/generate/templates/documentationContainer.js new file mode 100644 index 0000000..4b0f9fb --- /dev/null +++ b/lib/generate/templates/documentationContainer.js @@ -0,0 +1 @@ +export default null; \ No newline at end of file diff --git a/lib/generate/templates/index.html b/lib/generate/templates/index.html new file mode 100644 index 0000000..7110a54 --- /dev/null +++ b/lib/generate/templates/index.html @@ -0,0 +1,13 @@ + + + + + UiZoo + + + + + + + + \ No newline at end of file diff --git a/lib/generate/templates/index.js b/lib/generate/templates/index.js new file mode 100644 index 0000000..24db903 --- /dev/null +++ b/lib/generate/templates/index.js @@ -0,0 +1,8 @@ +import UiZoo from 'uizoo'; +import documentation from './documentationContainer'; +import components from './componentsContainer'; + +let root = document.createElement('div'); +document.body.appendChild(root); + +UiZoo.init(documentation, components, root, '/uizoo/'); \ No newline at end of file diff --git a/lib/generate/templates/package.json b/lib/generate/templates/package.json new file mode 100644 index 0000000..713c267 --- /dev/null +++ b/lib/generate/templates/package.json @@ -0,0 +1,9 @@ +{ + "name": "uizoo-app", + "version": "1.0.0", + "description": "UiZoo - Dynamic React components library", + "repository": { + "url": "https://github.com/myheritage/UiZoo.js" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/lib/generate/templates/webpack.uizoo.js b/lib/generate/templates/webpack.uizoo.js new file mode 100644 index 0000000..af8f0f3 --- /dev/null +++ b/lib/generate/templates/webpack.uizoo.js @@ -0,0 +1,128 @@ +const path = require('path'); +const WebpackDevServer = require('webpack-dev-server'); +const webpack = require('webpack'); +const express = require('express'); +const chalk = require('chalk'); +const createConfigs = require('./createConfigsScript'); +const { + serverPort, + serverHost, + serverProtocol, + componentsRootDir +} = require('./config'); + +/** + * Just a simple webpack config, you can replace or extend it + */ +const webpackConfig = { + devtool: 'cheap-module-source-map', + entry: { + app: [path.join(__dirname, 'index.js')] + }, + output: { + filename: 'components.js', + path: __dirname, + publicPath: path.sep + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules.*/, + use: [{ + loader: require.resolve('babel-loader'), + options: { + babelrc: false, + presets: [ + require.resolve('babel-preset-env'), + require.resolve('babel-preset-react') + ], + plugins: [ + require.resolve('babel-plugin-transform-object-rest-spread'), + require.resolve('babel-plugin-syntax-dynamic-import') + ], + cacheDirectory: true + } + }] + }, + { + test: /\.css$/, + use: [ + require.resolve('style-loader'), + require.resolve('css-loader'), + + /** + * To support scss/sass files - + * 1. un comment line below + * 2. run `npm i -D sass-loader node-sass-chokidar` + * 3. change the 'test' to `/\.scss$/` instead of the current `/\.css$/` + */ + // require.resolve('sass-loader') + ] + } + ] + }, + // Mock node libs in case some npm pkg depend on them + node: { + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + }, + performance: { + hints: false + } +}; + +/** + * Plugin to create the UiZoo config on each file change while using the webpack dev server + * @class GenerateUiZooConfigsPlugin + */ +class GenerateUiZooConfigsPlugin { + apply(compiler) { + compiler.plugin('watch-run', (c, cb) => { + createConfigs() + .then(() => cb()) + .catch(e => console.error(e)); + }); + } +} + +// Addition to the config for the web dev server +webpackConfig.entry.app.push(`webpack-dev-server/client?http://localhost:${serverPort}/`, "webpack/hot/dev-server"); +webpackConfig.plugins = [].concat(webpackConfig.plugins || [], [ + new webpack.HotModuleReplacementPlugin(), + new GenerateUiZooConfigsPlugin(), + new webpack.NamedModulesPlugin() +]); +webpackConfig.devServer = { + inline: true, + hot: true +}; + +// The Web Dev server +const server = new WebpackDevServer(webpack(webpackConfig), { + contentBase: 'uizoo-app/', + hot: true, + noInfo: true, + compress: true, + https: serverProtocol === 'https', + host: serverHost, + before: (app) => { + app.use('[/]', (req, res) => { // redirect on exact '/' url to /uizoo + res.redirect('/uizoo'); + }); + app.use(express.static(componentsRootDir)); + app.all('/uizoo*', (req, res) => { // this is needed for the client router to work + res.sendFile('index.html', { + root: __dirname + }); + }); + } +}); + +let {cyan, grey} = chalk; +server.listen(serverPort, 'localhost', () => { + console.log(` ${grey(`+*+*+`)} ${chalk.cyan(`UiZoo up on localhost:${serverPort}`)} ${grey(`+*+*+`)}`); +}); \ No newline at end of file diff --git a/lib/generate/templatesService.js b/lib/generate/templatesService.js new file mode 100644 index 0000000..110c14d --- /dev/null +++ b/lib/generate/templatesService.js @@ -0,0 +1,20 @@ +const path = require('path'); +const fs = require('fs-extra'); +const {appFolderPath} = require('./config'); + +module.exports = { + copyTemplate, + resolveTemplatePath +}; + +/** + * Copy template from template folders to the new uizoo-app folder + * @param {String} templateName + * @return {Promise} + */ +function copyTemplate(templateName) { + return fs.copy(resolveTemplatePath(templateName), path.join(appFolderPath, templateName)); +} +function resolveTemplatePath(fileName) { + return path.join(__dirname, 'templates', fileName); +} \ No newline at end of file diff --git a/lib/logo.js b/lib/logo.js new file mode 100644 index 0000000..241834c --- /dev/null +++ b/lib/logo.js @@ -0,0 +1,11 @@ +module.exports = ` +___/\\\\\\________/\\\\\\________/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_____________________________ +___\\/\\\\\\_______\\/\\\\\\_______\\////////////\\\\\\______________________________ +____\\/\\\\\\_______\\/\\\\\\__/\\\\\\___________/\\\\\\/_______________________________ +_____\\/\\\\\\_______\\/\\\\\\_\\///__________/\\\\\\/_________/\\\\\\\\\\________/\\\\\\\\\\____ +______\\/\\\\\\_______\\/\\\\\\__/\\\\\\_______/\\\\\\/_________/\\\\\\///\\\\\\____/\\\\\\///\\\\\\__ +_______\\/\\\\\\_______\\/\\\\\\_\\/\\\\\\_____/\\\\\\/__________/\\\\\\__\\//\\\\\\__/\\\\\\__\\//\\\\\\_ +________\\//\\\\\\______/\\\\\\__\\/\\\\\\___/\\\\\\/___________\\//\\\\\\__/\\\\\\__\\//\\\\\\__/\\\\\\__ +__________\\///\\\\\\\\\\\\\\\\\\/___\\/\\\\\\__/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\__\\///\\\\\\\\\\/____\\///\\\\\\\\\\/___ +_____________\\/////////_____\\///__\\///////////////_____\\/////________\\/////_____ +`; \ No newline at end of file diff --git a/documentationMapper.js b/lib/scripts/documentationMapper.js similarity index 97% rename from documentationMapper.js rename to lib/scripts/documentationMapper.js index 1025057..714cafb 100644 --- a/documentationMapper.js +++ b/lib/scripts/documentationMapper.js @@ -1,4 +1,4 @@ -// In next versions this service should be an optional plugin +// Internal script to generate UiZoo component's mapping to documentation function howToUse() { explanation = 'How to use: Run `node documentationMapper.js "./client/Components/UI/*/index.js" "./client/Components/UI/(.+)/index.js" "./client/documentation.js"`'; diff --git a/server/main.js b/lib/server/main.js similarity index 100% rename from server/main.js rename to lib/server/main.js diff --git a/server/server.js b/lib/server/server.js similarity index 85% rename from server/server.js rename to lib/server/server.js index af2b0bb..6af2fdf 100644 --- a/server/server.js +++ b/lib/server/server.js @@ -1,7 +1,6 @@ let compression = require('compression'); let cors = require('cors'); let express = require('express'); -let helmet = require('helmet'); let http = require('http'); let {json} = require('body-parser'); let path = require('path'); @@ -22,7 +21,6 @@ module.exports = function (app) { */ function useDependencies(app) { app.use(json()); - app.use(helmet()); app.use(compression({ level: 1 })); @@ -34,7 +32,6 @@ function useDependencies(app) { */ function start(app) { let rootDir = getRootDir(__dirname); - // app.set('view engine', 'jade'); app.set('port', (process.env.PORT || 5000)); app.use('/dist', express.static(path.join(rootDir, 'dist'))); @@ -57,8 +54,5 @@ function start(app) { * @returns string */ function getRootDir(dirname) { - let paths = dirname.split(path.sep); - paths.splice(paths.length - 1); - dirname = paths.join(path.sep); - return dirname; + return path.dirname(path.dirname(__dirname)); } \ No newline at end of file diff --git a/package.json b/package.json index 4dfa028..2ea2e35 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "uizoo", - "version": "1.1.1", + "version": "1.2.0", "description": "Dynamic React Component Library", "scripts": { "start": "npm run gulp:w", - "build": "rollup -c rollup-build.config.js -o dist/index.js -f iife -n UiZoo -g underscore:_,react:React,react-dom:ReactDOM,react-router-dom:ReactRouterDOM,doctrine-standalone:doctrine,babel-standalone:Babel && rollup -c rollup-build.config.js -o dist/index.cjs.js -f cjs -n UiZoo -g underscore:_,react:React,react-dom:ReactDOM,react-router-dom:ReactRouterDOM,doctrine-standalone:doctrine,babel-standalone:Babel && rollup -c rollup-build.config.js -o dist/index.es.js -f es -n UiZoo -g underscore:_,react:React,react-dom:ReactDOM,react-router-dom:ReactRouterDOM,doctrine-standalone:doctrine,babel-standalone:Babel", - "gulp:w": "gulp", + "build": "npm run build:iife && npm run build:cjs && npm run build:es", + "build:iife": "./node_modules/.bin/rollup -c rollup.config.js -o dist/index.js -f iife -n UiZoo", + "build:cjs": "./node_modules/.bin/rollup -c rollup.config.js -o dist/index.cjs.js -f cjs -n UiZoo", + "build:es": "./node_modules/.bin/rollup -c rollup.config.js -o dist/index.es.js -f es -n UiZoo", + "gulp:w": "./node_modules/.bin/gulp", "test": "npm start & SERVER_PID=$!; (cd tests; npm i && node run-tests.js); kill $SERVER_PID" }, "main": "dist/index.cjs.js", @@ -25,16 +28,16 @@ "url": "https://github.com/myheritage/uizoo.js/issues" }, "homepage": "https://github.com/myheritage/uizoo.js#readme", + "bin": { + "uizoo": "./bin/uizoo.js" + }, "dependencies": { "babel-standalone": "~6.25.0", - "body-parser": "~1.15.2", - "compression": "~1.6.2", - "cors": "~2.8.3", + "chalk": "~1.1.3", "doctrine-standalone": "~1.0.0", - "express": "~4.15.2", - "glob": "7.0.6", - "helmet": "~3.1.0", - "jsx-to-string": "~1.1.0", + "fs-extra": "~4.0.2", + "latest-version": "~3.1.0", + "opn": "~5.1.0", "react": "~16.0.0", "react-dom": "~16.0.0", "react-router-dom": "~4.2.2", @@ -45,7 +48,11 @@ }, "license": "MIT", "devDependencies": { - "chalk": "~1.1.3", + "body-parser": "~1.15.2", + "compression": "~1.6.2", + "cors": "~2.8.3", + "express": "~4.15.2", + "glob": "7.0.6", "gulp": "3.9.1", "gulp-livereload": "~3.8.1", "gulp-nodemon": "~2.2.1", diff --git a/rollup-build.config.js b/rollup-build.config.js deleted file mode 100644 index fb96285..0000000 --- a/rollup-build.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const getRollupConfig = require('./rollup.config'); - -module.exports = getRollupConfig({external: ['underscore', 'react', 'react-dom', 'react-router-dom', 'doctrine-standalone', 'babel-standalone']}); \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index acfeb23..d2242ee 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -11,9 +11,7 @@ let fs = require('fs'), comments = require('postcss-discard-comments'), dupes = require('postcss-discard-duplicates'), cssnext = require('postcss-cssnext'), - replace = require("rollup-plugin-replace"); - -const defaultExternal = []; + replace = require('rollup-plugin-replace'); function getPlugins({ external @@ -30,18 +28,9 @@ function getPlugins({ const defaultPlugins = [ json(), buble({ - objectAssign: 'Object.assign', - exclude: ["./node_modules/**/*"] + objectAssign: '_extend', + exclude: ['./node_modules/**/*'] }), - // babel({ - // presets: [ - // ["env", {modules: false}], - // "es2015-rollup", - // "react" - // ], - // exclude: "./node_modules/**/*" - // }), - nodeResolve({ browser: true, jsnext: true, @@ -49,7 +38,7 @@ function getPlugins({ }), commonjs(), replace({ - 'process.env.NODE_ENV': JSON.stringify('development') + 'process.env.NODE_ENV': JSON.stringify('production') }) ]; @@ -60,13 +49,32 @@ function getPlugins({ function getConfig({ external = [], + globals = {}, input = 'client/index.js' }, withScss = true) { return { input, - external: defaultExternal.concat(external), + external, + globals, plugins: getPlugins({external}, withScss), }; } -module.exports = getConfig; \ No newline at end of file +module.exports = getConfig({ + external: [ + 'underscore', + 'react', + 'react-dom', + 'react-router-dom', + 'doctrine-standalone', + 'babel-standalone' + ], + globals: { + 'underscore': '_', + 'react': 'React', + 'react-dom': 'ReactDOM', + 'react-router-dom':'ReactRouterDOM', + 'doctrine-standalone': 'doctrine', + 'babel-standalone': 'Babel' + } +}); \ No newline at end of file