{"id":13752722,"url":"https://github.com/johannschopplich/plain-spa","last_synced_at":"2025-05-09T20:34:10.362Z","repository":{"id":105024846,"uuid":"250499872","full_name":"johannschopplich/plain-spa","owner":"johannschopplich","description":"🦢 Modern SPA deployable as-is without any bundlers","archived":true,"fork":false,"pushed_at":"2020-06-30T12:49:36.000Z","size":997,"stargazers_count":14,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-07T12:48:40.898Z","etag":null,"topics":["dom","javascript","reactive","sinuous","spa"],"latest_commit_sha":null,"homepage":"https://plain-spa.jhnn.dev","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johannschopplich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2020-03-27T10:04:00.000Z","updated_at":"2023-05-02T20:11:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"1ef5113c-784e-4ddb-a565-1c36ef3d15f4","html_url":"https://github.com/johannschopplich/plain-spa","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johannschopplich%2Fplain-spa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johannschopplich%2Fplain-spa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johannschopplich%2Fplain-spa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johannschopplich%2Fplain-spa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johannschopplich","download_url":"https://codeload.github.com/johannschopplich/plain-spa/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253321722,"owners_count":21890449,"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":["dom","javascript","reactive","sinuous","spa"],"created_at":"2024-08-03T09:01:10.021Z","updated_at":"2025-05-09T20:34:10.348Z","avatar_url":"https://github.com/johannschopplich.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./img/favicon.svg\" alt=\"Logo of Plain SPA\" height=\"114\"\u003e\n\u003c/p\u003e\n\n\u003ch3 align=\"center\"\u003ePlain SPA\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  Modern single-page application deployable as-is without any bundlers\u003cbr\u003e\n\u003c/p\u003e\n\n\u003cbr\u003e\n\n## Key Features\n\nThis boilerplate uses:\n  - [Sinuous](https://github.com/luwes/sinuous) (1247 bytes fast, reactive UI library),\n  - [Storeon](https://github.com/storeon/storeon) (167 bytes event-based Redux-like state manager) and\n  - [Storeon Router](https://github.com/storeon/router) (570 bytes modern router) under the hood.\n\nAll three tiny libraries come at cost of just 2 kilobytes combined. I chose them carefully to not reinvent the wheel after looking up a lot of lightweight UI and router libraries.\n\nFor example Sinuous comes up with **tagged templates** and **observables**:\n\n```js\nimport { observable, html } from './modules/sinuous/index.js'\n\nconst counter = observable(0)\nconst view = () =\u003e html`\n  \u003cdiv\u003eCounter ${counter}\u003c/div\u003e\n`\n\ndocument.body.append(view())\nsetInterval(() =\u003e counter(counter() + 1), 1000)\n```\n\nThe app includes several example routes to show Storeon Router's capabilities.\n\n## Background\n\nAlthough I fell in love with the possibilities offered by today's libraries like Vue or React, the tooling around them throws me off sometimes. Grasping how the dependencies interact with each other. Hundreds of megabytes for transpiling all the code. Recently I had to dig deep into Webpack's internals to figure out how to omit the asset size evaluation of images inside the public folder.\n\nIf one deviates from the standard tooling, working with it quickly becomes uncomfortable.\n\nSine ES6+ features are available in modern browsers by default, there will be no advantage of transpiling in the future (hopefully). In service workers they are used regularly in this very moment.\n\nI wanted to create a simple SPA with some of the glory provided by bigger frameworks, but keep the setup as simple as possible to understand what is actually happening. Plain JavaScript, runnable in the browsers without bundling or a `dist` directory to deploy.\n\nTo drop `npm` itself as a dependency, the packages in use have been imported manually into `js/modules/*`. That sure isn't best practice, but let's you clone the repository and execute it in an instant.\n\n## Bundling\n\n\u003e Doesn't the headline say \"deployable as-is without any bundlers\"?\n\nAbsolutely. The project is ready to ship for production purposes.\n\n**However**, you can tree-shake and compile htm syntax to hyperscript for a smaller app footprint.\n\n```js\n// E.g. the following input:\nhtml`\n  \u003cdiv id=\"foo\"\u003ehello ${you}\u003c/div\u003e\n`\n\n// becomes:\nh('div', { id: 'foo' }, 'hello ', you)\n```\n\nRun `npm i \u0026\u0026 npm run build` to bundle and minify your app. I tried to decrease the tooling as much as possible. Merely 10 Megabytes (unpacked) of Node modules will be installed.\n\nFinally change the source path to the entry script file inside `index.html` to `/build/bundle.js`.\n\nThis example application bundled amounts to **3294 bytes** in total.\n\n## Syntax Highlighting\n\nSyntax highlighting for HTML in ES6 multiline strings is probably not covered by your code editor of choice by default. If you're using Visual Studio Code, I recommend [es6-string-html](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html) to add support.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohannschopplich%2Fplain-spa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohannschopplich%2Fplain-spa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohannschopplich%2Fplain-spa/lists"}