The module was compiled with Depack: the website bundler that runs Google Closure Compiler. The command for creating the bundle is:
{
"depack": "depack example/App.jsx -o splendid/js/main.js -I 2018 -a -H -p -w"
}
where the advanced compilation is set, as well as Preact's h pragma added to the beginning of each file. -p is for pretty printing, and -w for no warnings. Depack then performs static-analysis on the entered files, and creates a temp directory where it transpiles JSX files.
import core from '@idio/core'
import render from '@depack/render'
(async () => {
const { url } = await core({
frontend: { directory: ['example', 'src'] },
async api(ctx, next) {
if (ctx.path == '/form') {
ctx.body = { data: 'ok', error: null }
} else await next()
},
serve(ctx) {
ctx.body = '<!doctype html>' + render(<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossOrigin="anonymous"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<title>Form Example</title>
<style>
{`body {
background: #dfffdf;
}`}
</style>
</head>
<body id="preact">
<script type="module" src="/example/App.jsx"/>
</body>
</html>)
},
}, { port: null })
console.log('%s', url)
})()
The server code itself is written in JSX thanks to the ÀLaMode's require hook. The JSX's VNode tree is then transformed into HTML with preact-render-to-string.
require('alamode')()
require(`../${process.argv[2]}`)
The example can be started with yarn example/ command:
MacBook:form zavr$ yarn example/
yarn run v1.13.0
$ yarn e example/example
$ node example example/example
http://localhost:5000
import { h } from 'preact'
import { Component } from 'preact'
export default class Form extends Component {
constructor() {
super()
this.state = {
values: {},
}
/**
* @type {FormProps}
*/
this.props = this.props
}
getChildContext() {
return {
values: this.state.values,
onChange: this.onChange.bind(this),
}
}
onChange(name, value) {
this.setState({
values: {
...this.state.values,
[name]: value,
},
})
if (this.props.onChange)
this.props.onChange(this.state.values)
}
/**
* @param {FormProps} props Options for the Form component.
* @param {function} [props.onChange] The callback to call when a change is made to any of the inputs inside of the form.
* @param {function} [props.formRef] The function to call with the reference to the form HTML.
* @param {function} [props.onSubmit] The function to call on form submit.
*/
render({ children, formRef, onSubmit, onChange, ...props }) {
return h('form',{...props,'ref':formRef, 'onSubmit':onSubmit},
children,
)
}
}
/**
* The div with `form-group` class to hold the label, input, help and validation message.
*/
export class FormGroup extends Component {
constructor() {
super()
this.id = `i${Math.floor(Math.random() * 100000)}`
this.hid = `h${this.id}`
/**
* @type {FormGroupProps}
*/
this.props = this.props
}
getChildContext() {
return {
id: this.id,
hid: this.hid,
}
}
render() {
const { children, label, help } = this.props
return h('div',{'className':"form-group"},
label && h('label',{'htmlFor':this.id},label),
children,
help && h('small',{'id':this.hid,'dangerouslySetInnerHTML':{ __html: help },'className':"form-text text-muted"}),
)
}
}
export { default as Select } from './Select'
export { default as TextArea } from './TextArea'
export { default as Input } from './Input'
export { default as SubmitForm } from './SubmitForm'
/**
* The button with `type="submit"` which can be loading with a spinner indicator.
* @param {SubmitButtonProps} props Options for the SubmitButton component.
* @param {boolean} [props.loading=false] Whether the button should display as loading. Default `false`.
* @param {string} [props.loadingText] The text to show during the loading progress.
* @param {string} props.confirmText The text for the normal state.
* @param {string} [props.className] The class name, such as `btn-lg`.
* @param {('primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark')} [props.type="primary"] The type of the button to add to the class as `btn-{type}`. Default `primary`.
* @param {boolean} [props.outline=false] Display the outline style of the button via setting the `btn-outline-{type}` class. Default `false`.
*/
export const SubmitButton = (props) => {
const { loading, confirmText, loadingText = confirmText, className, type = 'primary', outline = false } = props
const classes = ['btn', `btn-${outline ? 'outline-' : ''}${type}`, className].filter(Boolean)
return (h('button',{ 'className':classes.join(' '),'type':"submit", 'disabled':loading},
loading && h('span',{'className':`spinner-border spinner-border-sm${loadingText ? ' mr-2' : ''}`,'role':"status",'aria-hidden':"true"}),
loading ? loadingText : confirmText,
))
}
/* documentary types/index.xml */
/**
* @typedef {Object} FormProps Options for the Form component.
* @prop {function} [onChange] The callback to call when a change is made to any of the inputs inside of the form.
* @prop {function} [formRef] The function to call with the reference to the form HTML.
* @prop {function} [onSubmit] The function to call on form submit.
*
* @typedef {Object} FormGroupProps Options for the FormGroup component.
* @prop {string} [label] The label to display for the group.
* @prop {string} [help] The help text to show in `<small className="form-text text-muted">{help}</small>`
*
* @typedef {Object} SubmitButtonProps Options for the SubmitButton component.
* @prop {boolean} [loading=false] Whether the button should display as loading. Default `false`.
* @prop {string} [loadingText] The text to show during the loading progress.
* @prop {string} confirmText The text for the normal state.
* @prop {string} [className] The class name, such as `btn-lg`.
* @prop {('primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark')} [type="primary"] The type of the button to add to the class as `btn-{type}`. Default `primary`.
* @prop {boolean} [outline=false] Display the outline style of the button via setting the `btn-outline-{type}` class. Default `false`.
*/
The program as modules can be viewed on the following page. Modules Demo. The aim was to discover the use case for building JSX files into JS for serving as modules on CDNs like GitHub pages where the addition of .jsx extension is required for serving <script type="module"></script>.