{"id":14989629,"url":"https://github.com/idiocc/frontend","last_synced_at":"2026-01-04T13:56:33.406Z","repository":{"id":57117940,"uuid":"167748125","full_name":"idiocc/frontend","owner":"idiocc","description":"The Middleware To Serve Front-End JavaScript With JSX And Hot Reload.","archived":false,"fork":false,"pushed_at":"2020-03-12T08:17:06.000Z","size":1268,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-01T01:27:26.003Z","etag":null,"topics":["backend","export","frontend","import","jsx","modules"],"latest_commit_sha":null,"homepage":"https://www.idio.cc","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/idiocc.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-26T22:57:40.000Z","updated_at":"2020-05-26T21:50:57.000Z","dependencies_parsed_at":"2022-08-23T09:31:04.222Z","dependency_job_id":null,"html_url":"https://github.com/idiocc/frontend","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idiocc%2Ffrontend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idiocc%2Ffrontend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idiocc%2Ffrontend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idiocc%2Ffrontend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/idiocc","download_url":"https://codeload.github.com/idiocc/frontend/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244153325,"owners_count":20406995,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","export","frontend","import","jsx","modules"],"created_at":"2024-09-24T14:18:40.454Z","updated_at":"2026-01-04T13:56:33.352Z","avatar_url":"https://github.com/idiocc.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @idio/frontend\n\n[![npm version](https://badge.fury.io/js/%40idio%2Ffrontend.svg)](https://www.npmjs.com/package/@idio/frontend)\n![Node.js CI](https://github.com/idiocc/frontend/workflows/Node.js%20CI/badge.svg)\n\n`@idio/frontend` is The Middleware To Serve Front-End JavaScript. It allows to set-up the modern front-end development environment where `node_modules` are served alongside compiled JSX code (without using _Babel_, see [`@a-la/jsx`](https://github.com/a-la/jsx)).\n\n\u003ca href=\"https://www.idio.cc\"\u003e\n  \u003cimg src=\"docs/frontend.gif\" alt=\"Idio Frontend Middleware\"\u003e\n\u003c/a\u003e\n\n```sh\nyarn add @idio/frontend\nnpm i @idio/frontend\n```\n\n## Table Of Contents\n\n- [Table Of Contents](#table-of-contents)\n- [API](#api)\n- [`frontEnd(config=: FrontEndConfig): !_goa.Middleware`](#frontendconfig-frontendconfig-_goamiddleware)\n  * [`FrontEndConfig`](#type-frontendconfig)\n- [Hot Reload](#hot-reload)\n  * [`HotReload`](#type-hotreload)\n  * [Enabling Hot Reload](#enabling-hot-reload)\n  * [Status](#status)\n  * [Node-watch](#node-watch)\n- [Copyright \u0026 License](#copyright--license)\n\n\u003cp align=\"center\"\u003e\u003ca href=\"#table-of-contents\"\u003e\n  \u003cimg src=\"/.documentary/section-breaks/0.svg?sanitize=true\"\u003e\n\u003c/a\u003e\u003c/p\u003e\n\n## API\n\nThe package is available by importing its default function:\n\n```js\nimport frontend from '@idio/frontend'\n```\n\n\u003cp align=\"center\"\u003e\u003ca href=\"#table-of-contents\"\u003e\n  \u003cimg src=\"/.documentary/section-breaks/1.svg?sanitize=true\"\u003e\n\u003c/a\u003e\u003c/p\u003e\n\n## \u003ccode\u003e\u003cins\u003efrontEnd\u003c/ins\u003e(\u003c/code\u003e\u003csub\u003e\u003cbr/\u003e\u0026nbsp;\u0026nbsp;`config=: FrontEndConfig,`\u003cbr/\u003e\u003c/sub\u003e\u003ccode\u003e): \u003ci\u003e!_goa.Middleware\u003c/i\u003e\u003c/code\u003e\nCreate a middleware to serve **Front-End** _JavaScript_, including JSX and `node_modules`.\n\n - \u003ckbd\u003econfig\u003c/kbd\u003e \u003cem\u003e\u003ccode\u003e\u003ca href=\"#type-frontendconfig\" title=\"Options for the middleware.\"\u003eFrontEndConfig\u003c/a\u003e\u003c/code\u003e\u003c/em\u003e (optional): Options for the middleware.\n\nThe middleware constructor will initialise the middleware function to serve files from the specified directory or directories (`frontend` by default). The files will be updated on-the-fly to fix their imports to relative paths (e.g., `preact` will be transformed into `/node_modules/preact/dist/preact.mjs`). Any CSS styles will also be served using an injector script.\n\nFiles served with this middleware, either transpiler or plain JS, will be cached using their `mtime`. There is no need to compute `md5` because this middleware is meant for the development purposes, whereas production code can be built with [_Depack_](https://artdecocode.com/depack/).\n\n__\u003ca name=\"type-frontendconfig\"\u003e`FrontEndConfig`\u003c/a\u003e__: Options for the middleware.\n\n\n|     Name      |                                                               Type                                                                |                                                                                                                                       Description                                                                                                                                        |           Default            |\n| ------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |\n| directory     | \u003cem\u003e(string \\| !Array\u0026lt;string\u0026gt;)\u003c/em\u003e                                                                                         | The directory or directories from which to serve files.                                                                                                                                                                                                                                  | `frontend`                   |\n| mount         | \u003cem\u003estring\u003c/em\u003e                                                                                                                   | The directory on which to mount. The dirname must be inside the mount. E.g., to serve `example/src/index.js` from `/src/index.js`, the **mount** is `example/src` and **directory** is `src`.                                                                                            | `.`                          |\n| override      | \u003cem\u003e!Object\u0026lt;string, string\u0026gt;\u003c/em\u003e                                                                                            | Instead of resolving the _package.json_ path for packages and looking up the module and main fields, paths can be passed manually in the override. E.g., `{ preact: '/node_modules/preact/src/preact.js' }` will serve the source code of _Preact_ instead of the resolved dist version. | -                            |\n| pragma        | \u003cem\u003estring\u003c/em\u003e                                                                                                                   | The pragma function to import. This enables to skip writing `h` at the beginning of each file. JSX will be transpiled to have `h` pragma, therefore to use React it's possible to do `import { createElement: h } from 'react'`.                                                         | `import { h } from 'preact'` |\n| log           | \u003cem\u003e(boolean \\| !Function)\u003c/em\u003e                                                                                                   | Log to console when source files were patched.                                                                                                                                                                                                                                           | `false`                      |\n| jsxOptions    | \u003cem\u003e[!_alaJsx.Config](#type-_alajsxconfig)\u003c/em\u003e                                                                                   | Options for the transpiler.                                                                                                                                                                                                                                                              | -                            |\n| exportClasses | \u003cem\u003eboolean\u003c/em\u003e                                                                                                                  | When serving CSS, also export class names.                                                                                                                                                                                                                                               | `true`                       |\n| hotReload     | \u003cem\u003e\u003ca href=\"#type-hotreload\" title=\"Options for hot reload (real-time automatic update of code in browser).\"\u003e!HotReload\u003c/a\u003e\u003c/em\u003e | Enable hot reload for modules. Requires at least to implement `getServer` method so that WebSocket listener can be set up on the HTTP server.                                                                                                                                            | -                            |\n\nThe middleware can be used in any _Koa_ application, or within the [`idio` web server](https://www.idio.cc).\n\n```jsx\nimport idio, { render } from '@idio/idio'\nimport frontend from '@idio/frontend'\n\n/**\n * @param {import('..').FrontEndConfig} options\n */\nexport default async (options = {}, src = 'example/frontend') =\u003e {\n  const { url, app, router, server } = await idio({\n    // logger: { use: true },\n    _frontend: {\n      use: true,\n      middlewareConstructor() {\n        return frontend({\n          directory: ['example/frontend', 'example/reload'],\n          ...options,\n        })\n      },\n    },\n  }, { port: process.env.PORT })\n  router.get('/', async (ctx) =\u003e {\n    ctx.body = render(\u003chtml\u003e\n      \u003chead\u003e\u003ctitle\u003eExample\u003c/title\u003e\u003c/head\u003e\n      \u003cbody\u003e\n        Hello World\n        \u003cdiv id=\"app\" /\u003e\n        \u003cscript type=\"module\" src={src}/\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e, { addDoctype: true })\n  })\n  app.use(router.routes())\n  console.error('Started on %s', url)\n  return { app, url, server }\n}\n```\n\n```m\nexample/frontend\n├── Component.jsx\n├── index.jsx\n└── style.css\n```\n\n*The entry point*\n\n```jsx\nimport { render } from 'preact'\nimport Component from './Component'\n// linked node_modules are also resolved\nimport Form, { Input } from '@depack/form'\n\nconst component = \u003cComponent test=\"Welcome\"/\u003e\nconst form = (\u003cForm\u003e\n  \u003cInput placeholder=\"hello world\"/\u003e\n\u003c/Form\u003e)\n\nlet c = render(component, window['app'])\nlet f = render(form, document.body)\n```\n\n*The component*\n\n```jsx\nimport { Component } from 'preact'\nimport './style.css'\n\nexport default class Example extends Component {\n  constructor() {\n    super()\n    this.state = {\n      count: 0,\n    }\n  }\n  render({ test }) {\n    const { count } = this.state\n    return (\u003cdiv onClick={() =\u003e {\n      this.setState({ count: count + 1 })\n    }}\u003e{test} + updated + {count}\u003c/div\u003e)\n  }\n}\n```\n\n*The style*\n\n```css\nbody {\n  background: lightcyan;\n}\n```\n\n![Chrome Example](docs/Example1.gif)\n\n\u003cp align=\"center\"\u003e\u003ca href=\"#table-of-contents\"\u003e\n  \u003cimg src=\"/.documentary/section-breaks/2.svg?sanitize=true\"\u003e\n\u003c/a\u003e\u003c/p\u003e\n\n## Hot Reload\n\nThis package supports hot reload of modules, mainly for the purpose of developing _Preact_ apps.\n\n__\u003ca name=\"type-hotreload\"\u003e`HotReload`\u003c/a\u003e__: Options for hot reload (real-time automatic update of code in browser).\n\n\n|       Name        |                                                                                                                      Type                                                                                                                       |                                   Description                                    |     Default      |\n| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------- |\n| path              | \u003cem\u003estring\u003c/em\u003e                                                                                                                                                                                                                                 | The path from which to serve the operational module that provides admin methods. | `/hot-reload.js` |\n| module            | \u003cem\u003eboolean\u003c/em\u003e                                                                                                                                                                                                                                | Whether to serve the hot-reload script as a module.                              | `true`           |\n| ignoreNodeModules | \u003cem\u003eboolean\u003c/em\u003e                                                                                                                                                                                                                                | Whether to ignore paths from `node_modules`.                                     | `true`           |\n| watchers          | \u003cem\u003e!Object\u0026lt;string, [!fs.FSWatcher](#type-fsfswatcher)\u0026gt;\u003c/em\u003e                                                                                                                                                                              | Pass an empty object here so that references to _FSWatchers_ can be saved.       | -                |\n| __getServer*__    | \u003cem\u003e() =\u003e \u003ca href=\"https://nodejs.org/api/http.html#http_class_http_server\" title=\"An HTTP server that extends net.Server to handle network requests.\"\u003e\u003cimg src=\".documentary/type-icons/node-odd.png\" alt=\"Node.JS Docs\"\u003e!http.Server\u003c/a\u003e\u003c/em\u003e | The function used to get the server to enable web socket connection.             | -                |\n\nThe middleware will append some code at the end of each original file, and when an update is detected, it will use dynamic import to get references to new methods and classes. When dealing with classes, the prototype of the original class will be changed at run-time. E.g., if a render method is changed, after updating the prototype and rerendering the whole app, a new `render` method will be used.\n\n_For example, there's a simple component:_\n\n```jsx\nimport { Component } from 'preact'\nimport { $Example } from './style.css'\n\nexport default class Example extends Component {\n  constructor() {\n    super()\n    this.example = this.example.bind(this)\n  }\n  example() {\n    console.log('clicked')\n  }\n  render({ test }) {\n    return (\u003cdiv id={test} onClick={this.example}\n      className={$Example}\u003e\n      Hello World\n    \u003c/div\u003e)\n  }\n}\n\nexport const Example2 = () =\u003e {\n  return (\u003cspan\u003eOpen Source!\u003c/span\u003e)\n}\n\nconst Example3 = () =\u003e {\n  return (\u003cpre\u003eArt Deco © {new Date().getFullYear()}\u003c/pre\u003e)\n}\n\nexport { Example3 }\n```\n\n_When hot reload is enabled, it's going to have an additional code at the bottom of the file when served via **front-end** middleware:_\n\n```jsx\nimport { h } from '/node_modules/preact/dist/preact.mjs'\nimport { Component } from '/node_modules/preact/dist/preact.mjs'\nimport { $Example } from './style.css'\n\nexport default class Example extends Component {\n  constructor() {\n    super()\n    this.example = this.example.bind(this)\n  }\n  example() {\n    console.log('clicked')\n  }\n  render({ test }) {\n    return (  h('div',{id:test, onClick:this.example,\n      className:$Example},\n      `Hello World`\n    ))\n  }\n}\n\nexport let Example2 = () =\u003e {\n  return (h('span',{},`Open Source!`))\n}\n\nlet Example3 = () =\u003e {\n  return (h('pre',{},`Art Deco © `,new Date().getFullYear()))\n}\n\nexport { Example3 }\n\n/* IDIO HOT RELOAD */\nimport { idioHotReload } from '/hot-reload'\nif (idioHotReload) {\n  let _idio = 0\n  idioHotReload('example/reload/Example.jsx', async () =\u003e {\n    _idio++\n    const module = await import(`./Example?ihr=${_idio}`)\n    Example2 = module['Example2']\n    Example3 = module['Example3']\n    return {\n      module,\n      classes: {\n        'default': Example,\n      },\n    }\n  })\n}\n```\n\nSuch code registers a listener to messages from the websocket connection.\n\n### Enabling Hot Reload\n\nThe API provided for the reload is implemented in a JS file served from [`/hot-reload.js`](/src/listener.mjs) path. It has 2 functions:\n\n- `idioAddHotReload`: the function to execute to rerender the app, which needs to be implemented by the developer. This should be imported in the application main entry point.\n- `idioHotReload`: the function to register that a module can be hot-reloaded. Called by auto-generated code from the `frontend` middleware.\n\nAfter an update is done, the app needs to be re-rendered. This is why we have to update the entry file to our application a little bit:\n\n```jsx\nimport { render, Component } from 'preact'\nimport Example, { Example2, Example3 } from './Example'\n\nclass App extends Component {\n  render() {\n    return (\u003chtml\u003e\n      \u003cbody\u003e\n        \u003cExample test=\"example\"/\u003e\n        \u003cExample2 /\u003e\n        \u003cExample3 /\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e)\n  }\n}\n\nconst app = (\u003cApp /\u003e)\nconst a = render(app, window.app)\n\n/* IDIO HOT RELOAD */\nimport addHotReload from '@idio/hot-reload'\naddHotReload(() =\u003e {\n  render(app, document.body, a)\n})\n```\n\nIn this case, we created a component and passed it for initial render into a container. The return of this function can then be used inside the `idioAddHotReload` listener to rerender the component [without](https://github.com/developit/preact/issues/1259) it loosing its state.\n\nTherefore, to enable the reload, add the default export from `@idio/hot-reload`.\n\n```js\nimport addHotReload from '@idio/hot-reload'\naddHotReload(() =\u003e {\n  render(app, document.body, a)\n})\n```\n\nThe front-end middleware will rename it into `/hot-reload` path automatically, but when you're building the code with _Depack_, you should install [`@idio/hot-reload`](https://github.com/idiocc/hot-reload) package, which exports a default dummy function that doesn't do anything, and will be dropped by the compiler so that there's no additional bytes added to the bundle because of the hot reload.\n\n```js\n// @idio/hot-reload main:\nexport default (cb) =\u003e {}\n```\n\n### Status\n\nAt the moment, the following works:\n\n- Updating classes, exported like the following:\n    ```js\n    export default class A {}\n\n    class B {}\n    class C {}\n    export default B\n    export { C }\n    ```\n- Updating all other exports. When exporting a _const_, it will be transpiled into a _let_, because otherwise it's impossible to update the binding.\n    ```js\n    export const A = () =\u003e {} // will become `let`\n    export let B = () =\u003e {}\n\n    const C = () =\u003e {} // will become let\n    let D = 'hello'\n\n    export { C, D }\n    ```\n- Update styles dynamically upon changes to CSS files.\n\nWhat doesn't work:\n\n- If you're binding a method in a constructor, it won't be updated, since we don't have access to instances and only update prototypes. This actually doesn't work even in `react-hot-reload`.\n    ```js\n    class E extends Component {\n      constructor() {\n        super()\n        this.example = this.example.bind(this)\n      }\n      example() {\n        // callback\n      }\n      render() {\n        // example won't be reloaded since it was bound.\n        return (\u003cdiv onClick={this.example}\u003e)\n      }\n    }\n    ```\n\n### Node-watch\n\nIt's recommended to install `node-watch` which is an improvement to the standard watcher, that deals with bouncing multiple instant refreshes that happen in some IDEs like _VS Code_.\n\n\u003cp align=\"center\"\u003e\u003ca href=\"#table-of-contents\"\u003e\n  \u003cimg src=\"/.documentary/section-breaks/3.svg?sanitize=true\"\u003e\n\u003c/a\u003e\u003c/p\u003e\n\n## Copyright \u0026 License\n\nGNU Affero General Public License v3.0\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003e\n      \u003ca href=\"https://www.artd.eco\"\u003e\n        \u003cimg width=\"100\" src=\"https://raw.githubusercontent.com/wrote/wrote/master/images/artdeco.png\"\n          alt=\"Art Deco\"\u003e\n      \u003c/a\u003e\n    \u003c/th\u003e\n    \u003cth\u003e© \u003ca href=\"https://www.artd.eco\"\u003eArt Deco™\u003c/a\u003e for \u003ca href=\"https://idio.cc\"\u003eIdio\u003c/a\u003e 2020\u003c/th\u003e\n    \u003cth\u003e\n      \u003ca href=\"https://idio.cc\"\u003e\n        \u003cimg src=\"https://avatars3.githubusercontent.com/u/40834161?s=100\" width=\"100\" alt=\"Idio\"\u003e\n      \u003c/a\u003e\n    \u003c/th\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cp align=\"center\"\u003e\u003ca href=\"#table-of-contents\"\u003e\n  \u003cimg src=\"/.documentary/section-breaks/-1.svg?sanitize=true\"\u003e\n\u003c/a\u003e\u003c/p\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidiocc%2Ffrontend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fidiocc%2Ffrontend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidiocc%2Ffrontend/lists"}