├── .gitignore ├── .npmignore ├── .npmrc ├── app ├── components │ ├── inspector.js │ └── sidebar.js ├── index.js ├── stores │ └── components.js ├── styles │ └── style.css └── views │ └── main.js ├── bin.js ├── demo.gif ├── examples ├── alert.js ├── components │ ├── alert.js │ └── simple.js └── index.js ├── index.js ├── package.json ├── readme.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo.gif 2 | tmp 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /app/components/inspector.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | var { emit } = require('choo-shortemit') 3 | 4 | module.exports = inspector 5 | 6 | function inspector (componentRenderer, selected, events) { 7 | return html` 8 |
9 | ${selected ? renderComponent() : message('← Select a component to inspect.')} 10 |
11 | ` 12 | 13 | function renderComponent () { 14 | // Use render as the default method of the wrapper 15 | if (typeof componentRenderer !== 'function') { 16 | if (typeof componentRenderer.render === 'function') { 17 | componentRenderer = componentRenderer.render 18 | } else { 19 | return message('The component or the wrapper is not exported correctly.') 20 | } 21 | } 22 | 23 | var el = html`${componentRenderer()}` 24 | 25 | return el 26 | } 27 | 28 | function message (string) { 29 | return html` 30 |
31 |
${string}
32 |
33 | ` 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/components/sidebar.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | 3 | module.exports = sidebar 4 | 5 | function sidebar (components, selected) { 6 | return html` 7 |
8 | ${components.map(componentLink)} 9 |
10 | ` 11 | 12 | function componentLink (name) { 13 | return html` 14 | ${name} 15 | ` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | var choo = require('choo') 2 | var devtools = require('choo-devtools') 3 | var css = require('sheetify') 4 | 5 | css('./styles/style.css') 6 | 7 | var app = choo() 8 | 9 | // In case anyone needs it (disable logging) 10 | app.use(devtools({ 11 | filter: () => false 12 | })) 13 | 14 | app.use(require('choo-shortemit')) 15 | app.use(require('./stores/components')) 16 | 17 | app.route('/:component', require('./views/main')) 18 | 19 | app.mount('body') 20 | -------------------------------------------------------------------------------- /app/stores/components.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | var components = require('../../tmp') 3 | 4 | // Handle single components 5 | if (typeof components !== 'object' || (typeof components.render === 'function')) { 6 | components = { default: components } 7 | } 8 | 9 | module.exports = function (state, emitter) { 10 | state.nanoconstruct = { 11 | components: components 12 | } 13 | 14 | Object.keys(state.nanoconstruct.components).map(name => { 15 | var component = state.nanoconstruct.components[name] 16 | // If the component wasn't wrapped, we wrap it 17 | if (component.prototype && typeof component.prototype.createElement === 'function') { 18 | state.nanoconstruct.components[name] = wrap(component) 19 | } 20 | }) 21 | 22 | state.nanoconstruct.componentsCount = Object.keys(components).length 23 | } 24 | 25 | function wrap (Component) { 26 | return () => html` 27 | ${(new Component()).render()} 28 | ` 29 | } 30 | -------------------------------------------------------------------------------- /app/styles/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | html {font-size: 16px} 8 | 9 | body { 10 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 11 | font-size: 1rem; 12 | background: white; 13 | } 14 | 15 | .nano a { 16 | text-decoration: none; 17 | } 18 | 19 | .nano a:not(.black):hover { background: #f5f5f5 } 20 | 21 | .black {background:black} 22 | .tcwhite {color:white} 23 | .tcblack {color:black} 24 | 25 | .lh {line-height: 1.8} 26 | 27 | .f1 { font-size: 2rem } 28 | 29 | .h100vh {height: 100vh} 30 | .h100 {height: 100%} 31 | 32 | .w250px {width: 250px} 33 | .\31{width:100.000%} 34 | 35 | .db{display:block} 36 | .dib{display:inline-block} 37 | .dn{display:none} 38 | .dx{display:flex} 39 | 40 | .p1 {padding: 1.5rem} 41 | .px1 {padding-left: 1.5rem;padding-right:1.5rem} 42 | .py1 {padding-top:1.5rem;padding-bottom:1.5rem} 43 | 44 | .br {border-right-style: solid; border-right-width: 1px} 45 | 46 | .center {align-items: center; justify-content: center} 47 | -------------------------------------------------------------------------------- /app/views/main.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | var sidebar = require('../components/sidebar') 3 | var inspector = require('../components/inspector') 4 | 5 | module.exports = view 6 | 7 | function view (state, emit) { 8 | var selected = null 9 | // get the current selected component, fallback to default 10 | if (state.nanoconstruct.componentsCount > 1) { 11 | if (state.nanoconstruct.components[state.params.component] !== undefined) { 12 | selected = state.params.component 13 | } 14 | } else { 15 | selected = 'default' 16 | } 17 | 18 | return html` 19 | 20 |
21 | ${renderSidebar()} 22 | ${inspector(state.nanoconstruct.components[selected], selected, state.events)} 23 |
24 | 25 | ` 26 | 27 | function renderSidebar() { 28 | return state.nanoconstruct.componentsCount > 1 ? 29 | sidebar(Object.keys(state.nanoconstruct.components), selected) : 30 | null 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var minimist = require('minimist') 4 | var chalk = require('chalk') 5 | var dedent = require('dedent') 6 | var assert = require('assert') 7 | var app = require('./server') 8 | 9 | var argv = minimist(process.argv.slice(2), { 10 | alias: { 11 | 'help': 'h', 12 | 'library': 'l', 13 | 'open': 'o', 14 | 'port': 'p', 15 | 'version': 'v' 16 | }, 17 | default: { 18 | port: process.env.PORT || 8080 19 | }, 20 | boolean: [ 21 | 'help', 22 | 'library', 23 | 'open', 24 | 'version' 25 | ] 26 | }) 27 | 28 | if (argv.help) { 29 | console.log(dedent` 30 | \n${chalk.dim('usage')} 31 | ${chalk.yellow.bold('nanoconstruct')} [opts] 32 | ${chalk.dim('options')} 33 | --help, -h show this help text 34 | --library, -l use all the files from a library 35 | --open, -o open the page in the browser 36 | --port, -p server port 37 | --version, -v print version 38 | ${chalk.dim('examples')} 39 | ${chalk.bold('start server')} 40 | nanoconstruct example.js 41 | 42 | ${chalk.bold('start server on port 3000 and open it')} 43 | nanoconstruct example.js -p 3000 -o 44 | 45 | ${chalk.bold('start server with library mode')} 46 | nanoconstruct components --library 47 | `, '\n') 48 | process.exit(0) 49 | } 50 | 51 | if (argv.version) { 52 | console.log(require('./package.json').version) 53 | process.exit(0) 54 | } 55 | 56 | var entry = argv._[0] 57 | assert(entry, 'nanoconstruct: entry path should be supplied') 58 | 59 | app(entry, { 60 | port: argv.port, 61 | open: argv.open, 62 | library: argv.library 63 | }) 64 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodedninja/nanoconstruct/113604473f4d6d12d7c2816a7ad78a06f9cae61d/demo.gif -------------------------------------------------------------------------------- /examples/alert.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | var Alert = require('./components/alert') 3 | 4 | var component = new Alert('Click me!') 5 | 6 | module.exports = () => html` 7 | ${component.render(':)')} 8 | ` 9 | -------------------------------------------------------------------------------- /examples/components/alert.js: -------------------------------------------------------------------------------- 1 | /* global alert */ 2 | 3 | var Component = require('choo/component') 4 | var html = require('choo/html') 5 | 6 | module.exports = class SimpleComponent extends Component { 7 | constructor (text) { 8 | super() 9 | this.text = text 10 | } 11 | 12 | createElement (message) { 13 | return html` 14 | ${this.text} 15 | ` 16 | 17 | function click (e) { 18 | e.preventDefault() 19 | alert(message) 20 | } 21 | } 22 | 23 | update () { 24 | return false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/components/simple.js: -------------------------------------------------------------------------------- 1 | var Component = require('choo/component') 2 | var html = require('choo/html') 3 | 4 | module.exports = class SimpleComponent extends Component { 5 | constructor () { 6 | super() 7 | this.count = 0 8 | 9 | this.click = this.click.bind(this) 10 | } 11 | 12 | createElement () { 13 | return html` 14 |
15 |
I'm a simple component.
16 |
17 | I can count: ${this.count} 18 |
19 |
20 | ` 21 | } 22 | 23 | click (e) { 24 | e.preventDefault() 25 | this.count++ 26 | this.rerender() 27 | } 28 | 29 | update () { 30 | return false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Alert: require('./alert'), 3 | Example: require('./components/simple') 4 | } 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // utility components will come here 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanoconstruct", 3 | "version": "0.3.0", 4 | "description": "Tiny tool to test and develop nanocomponents", 5 | "main": "index.js", 6 | "bin": "bin.js", 7 | "scripts": { 8 | "start": "node bin.js examples", 9 | "test": "standard && dependency-check ./package.json" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/kodedninja/nanoconstruct.git" 14 | }, 15 | "keywords": [ 16 | "choo", 17 | "nanocomponent" 18 | ], 19 | "author": "kodedninja", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/kodedninja/nanoconstruct/issues" 23 | }, 24 | "homepage": "https://github.com/kodedninja/nanoconstruct#readme", 25 | "dependencies": { 26 | "budo": "^11.6.1", 27 | "chalk": "^2.4.2", 28 | "chokidar": "^2.1.2", 29 | "choo": "^6.13.1", 30 | "choo-devtools": "^2.5.1", 31 | "choo-shortemit": "^0.1.0", 32 | "dedent": "^0.7.0", 33 | "minimist": "^1.2.0", 34 | "sheetify": "^7.3.3", 35 | "tape": "^4.10.1" 36 | }, 37 | "devDependencies": { 38 | "dependency-check": "^3.3.0", 39 | "standard": "^12.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # nanoconstruct 2 | 3 | Stability 4 | 5 | 6 | NPM version 7 | 8 | 9 | Tiny tool to test and develop [nanocomponents](https://github.com/choojs/nanocomponent). 10 | 11 | `nanoconstruct` provides a very simple wrapper around your components and serves them on a simple interface you can access with your browser. It's inspired by [Kit](https://github.com/c8r/kit) and uses [budo](https://github.com/mattdesl/budo) under the hood. 12 | 13 | It includes [`choo-devtools`](https://github.com/choojs/choo-devtools), so you have access to the state and other Choo things, and [`tape`](https://github.com/substack/tape) for [testing](#tests). 14 | 15 | ![Demo of nanoconstruct](demo.gif) 16 | 17 | ## Installation 18 | ``` 19 | npm i nanoconstruct 20 | ``` 21 | 22 | ## Usage 23 | If your component depends on a custom state or parameters, you can wrap it into a simple wrapper function. Otherwise, a neutral wrapper will be used around the component. 24 | 25 | A simple wrapper function looks like this: 26 | ```javascript 27 | var html = require('choo/html') 28 | var Component = require('./components/component') 29 | 30 | var c = new Component() 31 | 32 | module.exports = () => html`${c.render()}` 33 | ``` 34 | Then just point `nanoconstruct` to the file with: 35 | ``` 36 | nanoconstruct example.js 37 | ``` 38 | 39 | ### Multiple Components 40 | It's also possible to use your whole component library at once. Use the `--library` mode and point `nanoconstruct` to a directory of wrapper functions or components. 41 | 42 | However, if the structure of your components isn't that simple, export all the wrappers or components from a `.js` file and use this as input. 43 | 44 | Like this: 45 | ```javascript 46 | module.exports = { 47 | Title: require('./title-wrapper'), 48 | Content: require('./component/content') 49 | } 50 | ``` 51 | 52 | ## CLI 53 | ``` 54 | usage 55 | nanoconstruct [opts] 56 | options 57 | --help, -h show this help text 58 | --library, -l use all the files from a library 59 | --open, -o open the page in the browser 60 | --port, -p server port 61 | --version, -v print version 62 | examples 63 | start server 64 | nanoconstruct example.js 65 | 66 | start server on port 3000 and open it 67 | nanoconstruct example.js -p 3000 -o 68 | 69 | start server with library mode 70 | nanoconstruct components --library 71 | ``` 72 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var budo = require('budo') 2 | var path = require('path') 3 | var dedent = require('dedent') 4 | var chalk = require('chalk') 5 | var fs = require('fs') 6 | var chokidar = require('chokidar') 7 | var sheetify = require('sheetify') 8 | 9 | module.exports = app 10 | 11 | var tmpDir = path.join(__dirname, 'tmp') 12 | 13 | function app (entry, opts) { 14 | var absolute = path.resolve(entry) 15 | 16 | writeForwarder(absolute, opts.library) 17 | 18 | budo(path.join(__dirname, 'app', 'index.js'), { 19 | live: true, 20 | port: opts.port, 21 | open: opts.open, 22 | title: 'nanoconstruct', 23 | pushstate: true, 24 | browserify: { 25 | transform: sheetify 26 | } 27 | }).on('connect', function (ev) { 28 | console.log(`\n${chalk.yellow('nanoconstruct')} is listening on ${chalk.cyan(ev.uri)}\n`) 29 | console.log('Exit by Ctrl + C\n') 30 | }) 31 | } 32 | 33 | function writeForwarder (absolute, libraryMode) { 34 | // Ensure we have the tmp directory 35 | !fs.existsSync(tmpDir) && fs.mkdirSync(tmpDir) 36 | 37 | if (libraryMode) { 38 | // Watch input files for changes 39 | chokidar.watch(absolute, { ignoreInitial: true, depth: 1 }).on('all', (event) => { 40 | writeMultiForwarder(absolute) 41 | }) 42 | } else { 43 | // Do the trick 44 | fs.writeFileSync(path.join(tmpDir, 'index.js'), dedent` 45 | module.exports = require('${absolute}') 46 | `) 47 | } 48 | } 49 | 50 | function writeMultiForwarder (absolute) { 51 | var files = fs.readdirSync(absolute, { withFileTypes: true }) 52 | // Filter out directories 53 | files = files.filter(file => !file.isDirectory()) 54 | fs.writeFileSync(path.join(tmpDir, 'index.js'), dedent` 55 | module.exports = { 56 | ${files.map(renderFile)} 57 | } 58 | `) 59 | 60 | function renderFile (file, id) { 61 | var componentName = file.name.replace('.js', '') 62 | return `"${componentName}": require('${absolute}/${file.name}')\n` 63 | } 64 | } 65 | --------------------------------------------------------------------------------