├── .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 |
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 |
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 |
4 |
5 |
6 |
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 | 
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 |
--------------------------------------------------------------------------------