├── .eslintignore ├── src ├── templates │ ├── hyperapp │ │ └── App.js │ ├── React │ │ └── App.js │ ├── Vue │ │ └── App.js │ ├── Vue3 │ │ └── App.js │ ├── Cycle │ │ └── App.js │ ├── lit-element │ │ └── App.js │ ├── RxJS │ │ └── App.js │ └── Preact │ │ └── App.js ├── generateImportMap.js ├── generateLitElementMarkup.js ├── generateVue3Markup.js ├── generateVueMarkup.js ├── generatePreactMarkup.js ├── generateCycleMarkup.js ├── generateReactMarkup.js ├── generateRxJSMarkup.js ├── generateHyperappMarkup.js └── packageUrls.js ├── .gitignore ├── .npmignore ├── commitlint.config.js ├── .husky └── commit-msg ├── .prettierrc.js ├── renovate.json ├── .github └── workflows │ ├── pull_requests.yml │ └── main.yml ├── .eslintrc.js ├── CHANGELOG.md ├── LICENSE.md ├── VISION.md ├── test ├── render │ ├── vue3.test.js │ ├── cycle.test.js │ ├── lit-element.test.js │ ├── rxjs.test.js │ ├── react.test.js │ ├── preact.test.js │ └── hyperapp.test.js └── cli │ ├── vue.test.js │ ├── vue3.test.js │ ├── cycle.test.js │ ├── rxjs.test.js │ ├── preact.test.js │ ├── hyperapp.test.js │ ├── lit-element.test.js │ └── react.test.js ├── bin └── cli.js ├── package.json ├── index.js ├── README.md └── pnpm-lock.yaml /.eslintignore: -------------------------------------------------------------------------------- 1 | src/templates 2 | -------------------------------------------------------------------------------- /src/templates/hyperapp/App.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/*.png 3 | .log/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | tests/ 3 | VISION.md 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | }; 7 | -------------------------------------------------------------------------------- /src/templates/React/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import htm from 'htm'; 3 | const html = htm.bind(React.createElement); 4 | 5 | export default function App(props) { 6 | return html`

Hello React from ${props.cdn}

`; 7 | } 8 | -------------------------------------------------------------------------------- /src/templates/Vue/App.js: -------------------------------------------------------------------------------- 1 | export default { 2 | template: ` 3 |
4 |

Hello Vue

5 |

{{ message }}

6 |
7 | `, 8 | data() { 9 | return { 10 | message: 'Oh hi from the component', 11 | }; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/templates/Vue3/App.js: -------------------------------------------------------------------------------- 1 | export default { 2 | template: ` 3 |
4 |

Hello Vue3

5 |

{{ message }}

6 |
7 | `, 8 | data() { 9 | return { 10 | message: 'Oh hi from the component', 11 | }; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/generateImportMap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const packageUrls = require('./packageUrls'); 4 | module.exports = function (framework, cdn) { 5 | const imap = { 6 | imports: {}, 7 | }; 8 | 9 | imap.imports = packageUrls[cdn][framework]; 10 | 11 | return JSON.stringify(imap, null, 2); 12 | }; 13 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "packageRules": [ 6 | { 7 | "depTypeList": ["devDependencies", "dependencies"], 8 | "matchUpdateTypes": ["minor","patch"], 9 | "automerge": true 10 | } 11 | ], 12 | "prHourlyLimit": 2, 13 | "prConcurrentLimit": 5 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/pull_requests.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | lint: 7 | name: Lint 8 | runs-on: ubuntu-latest 9 | if: "!contains(github.event.head_commit.message, 'skip ci')" 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: rwjblue/setup-volta@v1 13 | - name: Install 14 | run: yarn install 15 | - name: ESLint 16 | run: | 17 | yarn lint 18 | yarn test:cli 19 | 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | ecmaVersion: 2017, 7 | sourceType: 'module', 8 | }, 9 | plugins: ['prettier', 'node'], 10 | extends: ['eslint:recommended', 'plugin:node/recommended', 'prettier'], 11 | env: { 12 | node: true, 13 | }, 14 | rules: { 15 | 'prettier/prettier': 'error', 16 | // 'node/no-unsupported-features': ['error', { ignores: ['modules'] }], 17 | 'node/no-unpublished-require': 0, 18 | 'node/no-missing-require': 0, 19 | }, 20 | overrides: [ 21 | { 22 | files: ['test/**'], 23 | }, 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | lint: 8 | name: Lint 9 | runs-on: ubuntu-latest 10 | if: "!contains(github.event.head_commit.message, 'skip ci')" 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: rwjblue/setup-volta@v1 14 | - name: Install 15 | run: yarn install 16 | - name: ESLint 17 | run: | 18 | yarn lint 19 | yarn test:cli 20 | - name: Release 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GH_PAT }} 23 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 24 | run: npm run semantic-release 25 | -------------------------------------------------------------------------------- /src/generateLitElementMarkup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - lit-element with ${cdn} 13 | 14 | 15 | 16 | 17 | 20 | 26 | 27 | 28 | `; 29 | 30 | return markup; 31 | }; 32 | -------------------------------------------------------------------------------- /src/templates/Cycle/App.js: -------------------------------------------------------------------------------- 1 | import _xs from 'xstream'; 2 | import { div, h1, button, p, img } from '@cycle/dom'; 3 | 4 | // Until xstream is ESM compatible we need this 5 | const xs = _xs.default || _xs; 6 | 7 | export default function main(sources) { 8 | const increment$ = sources.DOM.select('.increment') 9 | .events('click') 10 | .mapTo(+1); 11 | 12 | const decrement$ = sources.DOM.select('.decrement').events('click').mapTo(-1); 13 | 14 | const action$ = xs.merge(decrement$, increment$); 15 | const count$ = action$.fold((x, y) => x + y, 0); 16 | 17 | const vdom$ = count$.map((count) => { 18 | return div([ 19 | h1('Counter'), 20 | button('.increment', {}, '+'), 21 | p(count), 22 | button('.decrement', {}, '-'), 23 | ]); 24 | }); 25 | 26 | return { 27 | DOM: vdom$, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/generateVue3Markup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - Vue3 with ${cdn} 13 | 14 | 15 |
16 | 17 | 20 | 27 | 28 | 29 | `; 30 | 31 | return markup; 32 | }; 33 | -------------------------------------------------------------------------------- /src/templates/lit-element/App.js: -------------------------------------------------------------------------------- 1 | import { html, css, LitElement } from 'lit-element'; 2 | 3 | export default class MyElement extends LitElement { 4 | static get styles() { 5 | return css` 6 | :host { 7 | display: block; 8 | padding: 25px; 9 | color: var(--my-element-text-color, #000); 10 | } 11 | `; 12 | } 13 | 14 | static get properties() { 15 | return { 16 | title: { type: String }, 17 | counter: { type: Number }, 18 | }; 19 | } 20 | 21 | constructor() { 22 | super(); 23 | this.title = 'Hey there'; 24 | this.counter = 5; 25 | } 26 | 27 | __increment() { 28 | this.counter += 1; 29 | } 30 | 31 | render() { 32 | return html` 33 |

${this.title} Nr. ${this.counter}!

34 | 35 | `; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/generateVueMarkup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - Vue with ${cdn} 13 | 14 | 15 |
16 | 17 | 20 | 32 | 33 | 34 | `; 35 | 36 | return markup; 37 | }; 38 | -------------------------------------------------------------------------------- /src/templates/RxJS/App.js: -------------------------------------------------------------------------------- 1 | import { fromEvent, merge, interval } from 'rxjs'; 2 | import { scan, map, startWith } from 'rxjs/operators'; 3 | 4 | export default function App() { 5 | const btnIncrement = document.getElementById('btnIncrement'); 6 | const btnDecrement = document.getElementById('btnDecrement'); 7 | const txt = document.getElementById('txtCount'); 8 | const increment$ = fromEvent(btnIncrement, 'click').pipe(map((ev) => +1)); 9 | const decrement$ = fromEvent(btnDecrement, 'click').pipe(map((ev) => -1)); 10 | 11 | merge(increment$, decrement$) 12 | .pipe(scan((inc, dec) => inc + dec, 0)) 13 | .subscribe((count) => (txt.value = count)); 14 | 15 | const span = document.createElement('span'); 16 | span.setAttribute('id', 'timer'); 17 | document.querySelector('#timer-wrapper').appendChild(span); 18 | interval(1000).subscribe((x) => { 19 | span.textContent = `${x} seconds elapsed.`; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/generatePreactMarkup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - Preact with ${cdn} 13 | 14 | 15 |
16 | 17 | 20 | 28 | 29 | 30 | `; 31 | 32 | return markup; 33 | }; 34 | -------------------------------------------------------------------------------- /src/generateCycleMarkup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - Cycle.js with ${cdn} 13 | 14 | 15 |
16 |
Cycle.js bootstrapped with unpack using ${cdn}
17 | 18 | 21 | 31 | 32 | 33 | `; 34 | 35 | return markup; 36 | }; 37 | -------------------------------------------------------------------------------- /src/generateReactMarkup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - React with ${cdn} 13 | 14 | 15 |
16 | 17 | 20 | 29 | 30 | 31 | `; 32 | 33 | return markup; 34 | }; 35 | -------------------------------------------------------------------------------- /src/generateRxJSMarkup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateImportMap = require('./generateImportMap'); 4 | module.exports = function (options) { 5 | const { projectName, cdn, framework } = options; 6 | 7 | const markup = ` 8 | 9 | 10 | 11 | 12 | ${projectName} - RxJS with ${cdn} 13 | 14 | 15 |
16 |

17 | 18 | 19 | 20 |

21 |
RxJS bootstrapped with unpack using ${cdn}
22 | 23 | 26 | 32 | 33 | 34 | `; 35 | 36 | return markup; 37 | }; 38 | -------------------------------------------------------------------------------- /src/templates/Preact/App.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import htm from 'htm'; 3 | 4 | const html = htm.bind(h); 5 | 6 | export default class App extends Component { 7 | addTodo() { 8 | const { 9 | todos = [] 10 | } = this.state; 11 | this.setState({ 12 | todos: todos.concat(`Item ${todos.length}`) 13 | }); 14 | } 15 | render({ 16 | page 17 | }, { 18 | todos = [] 19 | }) { 20 | return html ` 21 |
22 | <${Header} name="ToDo's (${page})" /> 23 |
    24 | ${todos.map(todo => html` 25 |
  • ${todo}
  • 26 | `)} 27 |
28 | 29 | <${Footer}>footer content here 30 |
31 | `; 32 | } 33 | } 34 | 35 | const Header = ({ 36 | name 37 | }) => html `

${name} List

` 38 | 39 | const Footer = props => html `