{"id":13878417,"url":"https://github.com/mayu-live/framework","last_synced_at":"2025-07-16T14:32:02.657Z","repository":{"id":55005798,"uuid":"522786803","full_name":"mayu-live/framework","owner":"mayu-live","description":"Mayu is a live updating server-side component-based VDOM rendering framework written in Ruby","archived":false,"fork":false,"pushed_at":"2025-06-05T05:25:37.000Z","size":9486,"stargazers_count":145,"open_issues_count":34,"forks_count":5,"subscribers_count":4,"default_branch":"rewrite","last_synced_at":"2025-07-07T12:38:15.972Z","etag":null,"topics":["components","http2","reactive","ruby","server-side-rendering","vdom","virtual-dom"],"latest_commit_sha":null,"homepage":"https://mayu.live","language":"Ruby","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/mayu-live.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-08-09T03:19:47.000Z","updated_at":"2025-05-26T17:55:46.000Z","dependencies_parsed_at":"2023-12-25T21:45:24.863Z","dependency_job_id":"403c0ee9-066a-485d-8e6b-b3ed65f46fed","html_url":"https://github.com/mayu-live/framework","commit_stats":{"total_commits":892,"total_committers":4,"mean_commits":223.0,"dds":0.01905829596412556,"last_synced_commit":"cd11327afa5bf1c38464d2aead7bf5d2dbbfcbd3"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/mayu-live/framework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayu-live%2Fframework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayu-live%2Fframework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayu-live%2Fframework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayu-live%2Fframework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mayu-live","download_url":"https://codeload.github.com/mayu-live/framework/tar.gz/refs/heads/rewrite","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayu-live%2Fframework/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265502936,"owners_count":23777956,"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":["components","http2","reactive","ruby","server-side-rendering","vdom","virtual-dom"],"created_at":"2024-08-06T08:01:49.037Z","updated_at":"2025-07-16T14:32:02.645Z","avatar_url":"https://github.com/mayu-live.png","language":"Ruby","readme":"# ![Mayu Live](https://raw.githubusercontent.com/mayu-live/logo/main/logo-with-text.svg)\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/mayu-live/framework/.github/workflows/test.yml?branch=main\u0026label=Tests\u0026style=flat-square)](https://github.com/mayu-live/framework/actions/workflows/test.yml)\n[![Release](https://img.shields.io/github/v/release/mayu-live/framework?sort=semver\u0026style=flat-square)](https://github.com/mayu-live/framework/releases)\n[![GitHub commit activity](https://img.shields.io/github/commit-activity/w/mayu-live/framework/main?style=flat-square)](https://github.com/mayu-live/framework/commits)\n[![License AGPL-3.0](https://img.shields.io/github/license/mayu-live/framework?style=flat-square)](https://github.com/mayu-live/framework/blob/main/COPYING) ![Status: Experimental](https://img.shields.io/badge/status-experimental-critical?style=flat-square)\n\n[Documentation](https://mayu.live/docs)\n\n# Description\n\nMayu is a live updating server side component-based\nVirtualDOM rendering framework written in Ruby.\n\nEverything runs on the server, except for a tiny little runtime that\ndeals with the connection to the server and updates the DOM.\n\nIt is very early in development and nothing is guaranteed to work.\nStill trying to figure out how to make a framework that is both\neasy to use and fun to work with.\n\nSome parts are quite messy and some files are very long.\nThis is fine. I like to paint with a broad brush until\nthings are put in place and things feel right.\n\nA starter kit is available at\n[github.com/mayu-live/starter](https://github.com/mayu-live/starter)!\nIf you have all dependencies installed, you will be able to deploy\nan app to [Fly.io](https://fly.io/) within a few minutes without\nhaving to configure anything!\n\n### Core features:\n\n- 100% Ruby\n- 100% Server Side\n- 100% Async\n- Interactive web apps without JavaScript\n- Hot-reloading in dev\n- Automatic asset handling\n- Built-in metrics\n- File-system based routing inspired by [Next.js](https://nextjs.org/docs/routing/introduction)\n- Designed for edge deployments\n- Powerful and compact templating using [Haml](https://haml.info/)\n- One component per file\n- One file per component\n- Lazy loading, prefetch hints, HTTP/2, caching\n\n## Table of contents\n\n- [Description](#description)\n- [Getting started](#getting-started)\n  - [Install dependencies](#install-dependencies)\n  - [Start the example app](#start-the-example-app)\n  - [Run the tests](#run-the-tests)\n- [Features](#features)\n  - [100% server side](#100-server-side)\n  - [100% async](#100-async)\n  - [Components](#components)\n  - [Scoped CSS](#scoped-css)\n  - [State management](#state-management)\n  - [Path based routing](#path-based-routing)\n  - [Hot reloading](#hot-reloading)\n  - [Optimized data transfer](#optimized-data-transfer)\n  - [Realtime metrics](#realtime-metrics)\n  - [Haml](#haml)\n- [Implementation notes](#implementation-notes)\n  - [Tests](#tests)\n  - [Virtual DOM](#virtual-dom)\n  - [Server](#server)\n    - [Development server](#development-server)\n    - [Production server](#production-server)\n- [Contributing](#contributing)\n\n# Getting started\n\n## Install dependencies\n\nMake sure that you have installed [Ruby](https://www.ruby-lang.org/en/downloads/)\nand [NodeJS](https://nodejs.org/en/download/).\nThe required versions are specified in the file `.tool-versions`\nin the project root.\n\n[ImageMagick](https://github.com/ImageMagick/ImageMagick) and\n[libwebp](https://chromium.googlesource.com/webm/libwebp) are\nalso required for resizing images.\n\nInstall Ruby dependencies:\n\n    bundle install\n\nInstall node dependencies:\n\n    npm install\n\nBuild browser runtime:\n\n    npm run build\n\nStart the example app\n\n    cd example\n    bundle install\n    bin/mayu dev\n\nNow, open https://localhost:9292/ in your browser.\n\nHTTP/2 requires HTTPS to work, therefore in development mode,\nMayu will use the [localhost](https://github.com/socketry/localhost) gem\nto generate a self-signed certificate for localhost.\n\nDepending on your system/browser you might need to do one of the following:\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eMacOS:\u003c/strong\u003e Add the certificate to the keychain\u003c/summary\u003e\n  \u003cblockquote\u003e\n      \u003col\u003e\n        \u003cli\u003eOpen \u003ccode\u003e~/.localhost/localhost.crt\u003c/code\u003e with Keychain Access.\u003c/li\u003e\n        \u003cli\u003eChoose \u003ci\u003eGet Info\u003c/i\u003e and open \u003ci\u003eTrust\u003c/i\u003e then choose `Always trust`.\u003c/li\u003e\n        \u003cli\u003eRestart your browsers.\u003c/li\u003e\n      \u003c/ol\u003e\n  \u003c/blockquote\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eChrome:\u003c/strong\u003e Enable self-signed certs for localhost\u003c/summary\u003e\n  \u003cblockquote\u003e\n      Go to \u003ccode\u003echrome://flags/#allow-insecure-localhost\u003c/code\u003e and enable the setting.\n      This will allow requests to localhost over HTTPS even when an invalid\n      certificate is presented.\n  \u003c/blockquote\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eFirefox:\u003c/strong\u003e Add an exception for the certificate\u003c/summary\u003e\n  \u003cblockquote\u003e\n      Firefox will show \u003cstrong\u003eWarning: Potential Security Risk Ahead\u003c/strong\u003e.\n      Click \u003ci\u003eAdvanced\u003c/i\u003e, then \u003ci\u003eAccept the Risk and Continue\u003c/i\u003e to add an exception\n      for this certificate.\n  \u003c/blockquote\u003e\n\u003c/details\u003e\n\n## Run the tests\n\n    rake test\n\n# Features\n\nMost of these features are implemented, however, there are lots of sneaky bugs.\nContributions in such as bug reports or pull requests are appreciated!\n:green_heart:\n\nThe [example app](https://github.com/mayu-live/framework/blob/main/example/)\nis deployed to [`https://mayu.live/`](https://mayu.live/) as a proof of concept.\n\n## 100% server side\n\nMayu keeps all state on the server and all HTML is being rendered on the server.\n\nThere is no need to implement an API, you access databases\nand private APIs directly in your callback handlers.\n\nMayu detects changes in components and sends instructions\non how to patch the DOM to the browser using the\n[Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).\n[Client stream implementation](https://github.com/mayu-live/framework/blob/main/lib/mayu/client/src/stream.ts).\n\nCallbacks are regular `POST`-requests to\n`/__mayu/session/#{session_id}/#{callback_id}`,\nwhere the body contains the\n[serialized event data](https://github.com/mayu-live/framework/blob/main/lib/mayu/client/src/serializeEvent.ts).\n\n## 100% async\n\n[socketry/async](https://github.com/socketry/async) makes it possible\nto do all this without blocking.\n\n### `app/components/Clock.haml`\n\n```haml\n:ruby\n  mount do\n    loop do\n      update(time: Time.now.to_s)\n      sleep 0.5\n    end\n  end\n%p= state[:time]\n```\n\nThis will print the current server time.\n\nThe component will render once every second even though it updates\ntwice per second, since the time string only changes once per second.\n\n[Analog SVG clock component](https://github.com/mayu-live/framework/blob/main/example/app/components/Clock.haml)\n\n### Async loading\n\nIt's also easy to defer rendering until some action has happened,\nfor example, the [form demo](https://mayu.live/demos/form) loads\ntab content asynchronously when the mouse enters the tab header,\nso when the user clicks the tab, the content is already loaded.\n\n[Tabs implementation](https://github.com/mayu-live/framework/blob/main/example/app/components/Layout/Tabs.haml)\n\n## Components\n\nComponents are the building blocks of a Mayu application.\nThey contain logic and return other components or HTML elements.\n\nYou might be familiar with ReactJS and other component based\nrendering libraries. This is the same thing, but in Ruby.\n\n## Scoped CSS\n\nAll class names and element names are given an unique name,\ninspired by [CSS Modules](https://github.com/css-modules/css-modules).\n\nClass names are applied automatically.\n\n### `app/components/Example.haml`\n\n```haml\n:css\n  .box {\n    padding: 1px;\n    border: 1px solid #000;\n  }\n\n  .hello {\n    font-weight: bold;\n  }\n\n  button {\n    background: #0f0;\n    color: #fff;\n  }\n.box\n  %p.hello Hello world\n  %button Click me!\n```\n\nThis would generate the following HTML:\n\n```html\n\u003cdiv class=\"/app/components/Example.box?MjQSEK\"\u003e\n  \u003cp class=\"/app/components/Example.hello?MjQSEK\"\u003eHello world\u003c/p\u003e\n  \u003cbutton class=\"/app/components/Example_button?MjQSEK\"\u003eClick me!\u003c/button\u003e\n\u003c/div\u003e\n```\n\nThose are valid class names, as long as the characters are escaped in the\nCSS-file. [Specification](https://www.w3.org/TR/css-syntax-3/#consume-name).\n\nThis will be inserted into `\u003chead\u003e`:\n\n```html\n\u003clink\n  rel=\"stylesheet\"\n  href=\"/__mayu/static/NtXGjOdgHqDJUnAhmk3NwuzFnkk8Z1NlBCE_XykVE-8=.css\"\n/\u003e\n```\n\nThe browser will also be made aware of the assets used on a page via the\n[Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link)-header,\nso that they can load even before the browser starts parsing the HTML.\n\n    $ curl -i https://localhost:9292 -k\n    HTTP/2 200\n    content-length: 5260\n    content-type: text/html; charset=utf-8\n    link: \u003c/__mayu/static/jkA-D11H90ChVHYqIOKn8I_A4w2MJ4nG-UEVP19UGqg=.js\u003e; rel=preload; as=script; crossorigin=anonymous; fetchpriority=high, \u003c/__mayu/static/NtXGjOdgHqDJUnAhmk3NwuzFnkk8Z1NlBCE_XykVE-8=.css\u003e; rel=preload; as=style, \u003c/__mayu/static/u6rK2NKHRcFKribL1sMcDdr1gXHbgYIVznfN5RJEKCA=.css\u003e; rel=preload; as=style, \u003c/__mayu/static/shJPApqH5hptQERL4DivMTX42leUQRht9vGW4X_Rr84=.css\u003e; rel=preload; as=style, \u003c/__mayu/static/ZStAGN7uGe7CU3cxSgAIOL550d1VDqVDUzdiQuFOXo8=.css\u003e; rel=preload; as=style\n\n### Separate CSS-files\n\nIf you have very complex CSS, or maybe generate CSS-files,\nyou can create a `.css`-file right next to your component.\n\nThis does the same thing as the previous example:\n\n#### `app/components/Example.css`\n\n```css\n.box {\n  padding: 1px;\n  border: 1px solid #000;\n}\n\n.hello {\n  font-weight: bold;\n}\n\n.button {\n  background: #0f0;\n  color: #fff;\n}\n```\n\n#### `app/components/Example.haml`\n\n```haml\n.box\n  %p.hello Hello world\n  %button.button Click me!\n```\n\nYou can also mix the two styles.\n\n### Source maps\n\nYou can debug the sources of your CSS using source maps.\n\n![Source maps screenshot](https://user-images.githubusercontent.com/41148/199131855-d6159b68-649c-4c7a-baf1-e1ad2c9bd281.png)\n\n## State management\n\nMayu comes with some basic state management inspired by\n[Redux Toolkit](https://redux-toolkit.js.org/).\n\nThis is implemented but not yet integrated into the VDOM logic.\n\n[Example store](https://github.com/mayu-live/prototype/blob/main/example/store/auth.rb)\n\nIdeally I would want something like [XState](https://xstate.js.org/),\nbut I'm not experienced with it so I can't make anything like it.\n\n## Path based routing\n\nRouting is inspired by the\n[Next.js Layouts RFC](https://nextjs.org/blog/layouts-rfc).\n\nIt's a simple and straight forward approach, and it's super\neasy to locate files using a fuzzy finder plugin.\n\nHere's the structure of a blog app:\n\n```\napp\n├── root.haml\n├── root.css\n└── pages\n    ├── page.haml\n    ├── layout.haml\n    ├── layout.css\n    ├── about\n    │   ├── page.haml\n    │   └── page.css\n    └── posts\n        ├── page.haml\n        ├── layout.haml\n        └── :id\n            └── page.haml\n```\n\nThis would create the following routes:\n\n| **path**      | **component**                    | **layouts**                                           |\n| ------------- | -------------------------------- | ----------------------------------------------------- |\n| `/`           | `app/pages/page.haml`            | `app/pages/layout.haml`                               |\n| `/about/`     | `app/pages/about/page.haml`      | `app/pages/layout.haml`                               |\n| `/posts/`     | `app/pages/posts/page.haml`      | `app/pages/layout.haml` `app/pages/posts/layout.haml` |\n| `/posts/:id/` | `app/pages/posts/[id]/page.haml` | `app/pages/layout.haml` `app/pages/posts/layout.haml` |\n| `/*`          | `app/pages/404.haml`             | `app/pages/layout.haml`                               |\n\nFor a real-world example, check out\n[`example/app/pages/`](https://github.com/mayu-live/framework/tree/main/example/app/pages).\n\n## Hot reloading\n\nThere is a resource system inspired by JavaScript bundlers that loads all\ntypes of files.\n\n### Development mode\n\nComponents and styles update immediately in the browser as you edit files.\nNo browser refresh needed.\n\n## Production mode\n\nBefore a server shuts down (when receiving the `SIGINT` signal),\nit will pause all sessions, serialize and encrypt them, and send\nthem to the client and close the connection.\n\nThe client will then reconnect and post the encrypted session\nwhich will be decrypted, verified, deserialized and resumed.\n\nI don't know how stable this is at the moment.\nSometimes it seems like it can't restore components properly,\nmaybe when their implementation has changed.\n\nWhenever [Issue #20](https://github.com/mayu-live/framework/issues/20)\nhas been fixed, it would be quite easy to serialize the browser DOM and\nsend it along with the encrypted state when resuming, and then use a\nDOM-diffing algorithm (like [morphdom](https://github.com/patrick-steele-idem/morphdom))\non the browser DOM vs the DOM generated by the VDOM.\n\n## Optimized data transfer\n\nEverything is minified and optimized and deliviered over HTTP/2.\nImages are scaled into different versions, non-binary assets are\ncompressed with Brotli.\n\nAsset filenames are based on their content hash so that they can\nbe cached easily without having to worry about expiring them when\nthey change.\n\nThe message stream uses [DecompressionStream](https://wicg.github.io/compression/#decompression-stream)\nwith the [`deflate-raw`](https://wicg.github.io/compression/#supported-formats)\nformat. Browsers that don't support DecompressionStream will download a\nreplacement based on [fflate](https://github.com/101arrowz/fflate).\n\nMessages are packed with [MessagePack](https://msgpack.org/index.html),\nwhich is supposed to be very efficient, although it's also the largest\ndependency at the moment. A good thing with MessagePack is that it\ncan send binary data, which is useful when transferring state.\n\nFirst page load with Slow 3G throttling (no cache):\n\n![Request waterfall](https://user-images.githubusercontent.com/41148/198865376-5382a538-44a3-4058-8ba6-6d178cc78b37.png)\n\nSecond page load with Slow 3G throttling (cache):\n\n![Request waterfall](https://user-images.githubusercontent.com/41148/198865399-d4d428ec-89c6-4469-bec1-964040c41c2c.png)\n\n## Realtime metrics\n\nMayu exposes a [Prometheus](https://prometheus.io/)-endpoint for metrics so you can see how your app performs.\n\nScreenshots from [Grafana on Fly.io](https://fly.io/docs/reference/metrics/#managed-grafana-preview).\n\n![Active sessions](https://user-images.githubusercontent.com/41148/193404404-9018c9d9-e575-48db-8845-3f56ced0c16f.png)\n![Patch times and counts](https://user-images.githubusercontent.com/41148/193398411-cc5bf2d6-d353-42eb-bcf5-ccc1feb7099a.png)\n\n## Haml\n\nMayu uses [Haml](https://haml.info/), it's pretty convenient.\n\nCheck out the [Haml Reference](https://haml.info/docs/yardoc/file.reference.html).\nMayu has some differences with regular Haml to make it work better with a virtual DOM,\n[you can read more about that in the documentation](https://mayu.live/docs/components).\n\nLook at this example:\n\n[`./example/app/pages/Counter.haml`](https://github.com/mayu-live/framework/blob/main/example/app/pages/Counter.haml)\n\nThat above code will be transformed into something like this:\n\n```ruby\n# frozen_string_literal: true\nSelf =\n  setup_component(\n    assets: [\"0tyaKLqdvUGGcwZkdPOdMiMoMZoO74sMmtyRTuksjaQ=.css\"],\n    styles: {\n      __Card: \"example/app/pages/Counter_Card?7d89edff\",\n      __article: \"example/app/pages/Counter_article?7d89edff\",\n      __output: \"example/app/pages/Counter_output?7d89edff\",\n      __button: \"example/app/pages/Counter_button?7d89edff\",\n    },\n  )\nbegin\n  Card = import(\"/app/components/UI/Card\")\n  def self.get_initial_state(initial_value: 0, **) = { count: initial_value }\n  def decrement_disabled = state[:count].zero?\n  def handle_decrement\n    update do |state|\n      count = [0, state[:count] - 1].max\n      { count: }\n    end\n  end\n  def handle_increment\n    update do |state|\n      count = state[:count] + 1\n      { count: }\n    end\n  end\nend\npublic def render\n  Mayu::VDOM::H[\n    Card,\n    Mayu::VDOM::H[\n      :article,\n      Mayu::VDOM::H[\n        :button,\n        \"－\",\n        **mayu.merge_props(\n          { class: :__button },\n          { title: \"Decrement\" },\n          {\n            onclick: mayu.handler(:handle_decrement),\n            disabled: decrement_disabled,\n          },\n        )\n      ],\n      Mayu::VDOM::H[\n        :output,\n        state[:count],\n        **mayu.merge_props({ class: :__output })\n      ],\n      Mayu::VDOM::H[\n        :button,\n        \"＋\",\n        **mayu.merge_props(\n          { class: :__button },\n          { title: \"Increment\" },\n          { onclick: mayu.handler(:handle_increment) },\n        )\n      ],\n      **mayu.merge_props({ class: :__article })\n    ],\n    **mayu.merge_props({ class: :__Card }, { class: :card })\n  ]\nend\n```\n\n[Check out more examples in the tests](https://github.com/mayu-live/framework/blob/main/lib/mayu/resources/transformers/haml.test.rb)\n\n# Implementation notes\n\n## Tests\n\nTests are located in the `lib/`-directory next to their implementation.\nSo for `lib/mayu/state.rb` the test would be located in\n`lib/mayu/state.test.rb`.\n\nThis pattern is quite common in JavaScript\n([Jest does this](https://jestjs.io/docs/configuration#testmatch-arraystring)),\nand it's quite convenient to have things that are related close to each other,\nrather than to have a separate tree for tests.\n\nIt's also preferred to test things on a higher level, and only write unit\ntests for specific edge cases and trickier situations.\n\nThe example app could also be considered to be a test.\nIt should always work and be updated to use the latest features.\n\n## Virtual DOM\n\nComponents return a `VDOM::Descriptor` which has a `type`, `props` and a `key`,\nsimilar to React, and `props` can also contain children.\n`VDOM::VTree` is responsible for keeping track of the `VDOM::VNode`s that make\nup the application. A `VNode` has a `Descriptor` and children which is an array\nof `VNode` objects. It can also have a component, in that case it would call\nthe appropriate lifecycle methods of that component and pass its descriptors'\nprops to the component before rendering.\n\nThe child diffing algorithm is quite inefficient. I have tried to implement\nthe algorithm in snabbdom/preact/million several times, but they rely\non DOM-operations for ordering (`node.insertBefore`) and the algorithm has\nto take care of that and make sure that the order is exactly the same in the\nVDOM as in the DOM after all patch operations have been applied.\n\nThe child diffing algorithm makes a few unnecessary moves, and there's lots of\nroom for improvement, but at least the order is correct.\n\n## Server\n\nThe server is configured in [`mayu.toml`](https://github.com/mayu-live/framework/blob/main/example/mayu.toml)\nin the project root.\n\n### Development\n\nFor development you probably want these settings:\n\n```toml\n[dev.server]\ncount = 1\nhot_swap = true\nself_signed_cert = true\n```\n\n### Production\n\nThe production server depends on the output from a build step that\nparses all inputs and generates static files.\n\n```toml\n[dev.server]\nhot_swap = false\nself_signed_cert = false\n```\n\n# Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/mayu-live/framework.\nThis project is intended to be a safe, welcoming space for collaboration,\nand contributors are expected to adhere to the\n[code of conduct](https://github.com/mayu-live/framework/blob/main/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayu-live%2Fframework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmayu-live%2Fframework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayu-live%2Fframework/lists"}