├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── deno.jsonc ├── deno.lock ├── examples └── lume │ ├── .gitignore │ ├── _config.ts │ ├── alien.client.ts │ ├── alien.vto │ ├── core.client.ts │ ├── core.vto │ ├── deno.json │ ├── e2e.test.ts │ ├── index.vto │ ├── solid-hmr.client.ts │ ├── solid.client.ts │ ├── solid.vto │ ├── tiny.client.ts │ └── tiny.vto ├── packages ├── alien │ ├── LICENSE │ ├── deno.jsonc │ ├── mod.ts │ └── mod_test.ts ├── cli │ ├── LICENSE │ ├── README.md │ ├── cli.ts │ ├── deno.jsonc │ └── mod_test.ts ├── core │ ├── LICENSE │ ├── deno.jsonc │ ├── mod.ts │ └── mod_test.ts ├── incentive │ ├── LICENSE │ ├── deno.jsonc │ ├── mod.ts │ └── mod_test.ts ├── solid │ ├── LICENSE │ ├── deno.jsonc │ ├── mod.ts │ └── mod_test.ts └── tiny │ ├── LICENSE │ ├── deno.jsonc │ ├── mod.ts │ └── mod_test.ts ├── scripts ├── build_alien.ts ├── build_core.ts ├── build_incentive.ts ├── build_solid.ts ├── build_tiny.ts ├── bump-version.ts └── publish_npm.ts └── src ├── core.eta ├── generate.ts ├── inc └── types.eta └── tiny.eta /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | tags: 7 | - "**" 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | concurrency: 13 | group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | tests: 18 | runs-on: ubuntu-22.04 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install Deno 24 | uses: denoland/setup-deno@v2 25 | with: 26 | deno-version: v2.1 27 | 28 | - name: format 29 | run: deno fmt --check 30 | 31 | - name: lint 32 | run: deno lint 33 | 34 | - name: test 35 | run: deno test -A 36 | 37 | - name: Generate files 38 | run: deno task generate 39 | 40 | - name: Check if generated files are up to date 41 | run: | 42 | if [[ -n "$(git status --porcelain)" ]]; then 43 | echo "Generated files are not up to date. Please run 'deno task generate' and commit the changes." 44 | git status 45 | git diff 46 | exit 1 47 | fi 48 | 49 | publish_npm: 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - uses: actions/checkout@v4 54 | - name: Install Node.js 55 | uses: actions/setup-node@v4 56 | with: 57 | node-version: "20.x" 58 | registry-url: "https://registry.npmjs.org" 59 | - name: Install Deno 60 | uses: denoland/setup-deno@v2 61 | with: 62 | deno-version: v2.1 63 | 64 | - name: Publish package dry-run 65 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 66 | run: scripts/publish_npm.ts --dry-run 67 | 68 | - name: Publish package 69 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 70 | env: 71 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 72 | run: scripts/publish_npm.ts 73 | 74 | publish_jsr: 75 | runs-on: ubuntu-latest 76 | 77 | permissions: 78 | contents: read 79 | id-token: write 80 | 81 | steps: 82 | - uses: actions/checkout@v4 83 | 84 | - name: Install Deno 85 | uses: denoland/setup-deno@v2 86 | with: 87 | deno-version: v2.1 88 | 89 | - name: Publish package dry-run 90 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 91 | run: deno publish --dry-run 92 | 93 | - name: Publish package 94 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 95 | run: deno publish 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # dnt 3 | npm 4 | 5 | # Local Netlify folder 6 | .netlify 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://github.com/denoland/vscode_deno/issues/66#issuecomment-1631804057 3 | "deno.enable": true, 4 | "deno.lint": true, 5 | "editor.formatOnSave": true, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "denoland.vscode-deno" 8 | }, 9 | "[astro]": { 10 | "editor.defaultFormatter": "astro-build.astro-vscode" 11 | }, 12 | "typescript.tsserver.experimental.enableProjectDiagnostics": false 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024, JLarky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lift-html 2 | 3 | | package | bundle size (plain -> gzip) | 4 | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 5 | | @lift-html/tiny | | 6 | | @lift-html/core | | 7 | | @lift-html/solid (includes solid-js) | | 8 | | @lift-html/alien (includes alien-signals) | | 9 | | @lift-html/solid (includes solid-js and useAttributes) | | 10 | 11 | ## Welcome to the non-isomorphic web 12 | 13 | Lift HTML is a new way to build your JavaScript applications, especially if all 14 | that you know is isomorphic libraries like React, Vue, Preact, Angular, Lit, 15 | Svelte, SolidJS, Ember and Qwik. It's going to be less new if you have 16 | experience with primerally server-side frameworks like Rails, Django, Laravel. 17 | Those are built with expectation that you are going to write a lot of HTML+CSS 18 | and plop a couple of script tags here are there. 19 | 20 | With lift-html you can start with as low overhead as 150 bytes 21 | (`@lift-html/tiny`) to get type safety when declaring your custom elements and 22 | simplified API (I tested that every web component used in 23 | [Astro website](https://astro.build/) could be built with `@lift-html/tiny`). 24 | 25 | If you jump up to `@lift-html/core`, in the less than 600 bytes you get HMR (Hot 26 | Module Replacement) support, full support of web components features like 27 | `formAssociated` and `observedAttributes` with type safety and nice API: `init` 28 | and `deinit` callbacks instead of `constructor`, `connectedCallback`, 29 | `adoptedCallback`, `disconnectedCallback` (I yet to find an example of a web 30 | component that can't be written with `@lift-html/core`). 31 | 32 | After this you may enjoy a buffet of opt-in (and tree-shakeable) features like 33 | `@lift-html/incentive` that gives you 34 | [Hotwire Stimulus](https://stimulus.hotwired.dev/reference/targets) or 35 | [GitHub Catalyst](https://github.github.io/catalyst/guide-v2/targets)-like API 36 | to work with targets inside of your components. Or various integrations to make 37 | your attributes reactive like `@lift-html/solid` that also gives you ability to 38 | use APIs like `createSignal` and `createEffect` inside of your components. 39 | 40 | ## What is lift-html 41 | 42 | lift-html is a tiny library for building HTML Web Components (and CSS Web 43 | Components), components that are meant to enhance existing HTML on the page 44 | instead of rendering it on the client or hydrating it. 45 | 46 | Code for `liftHtml` is public domain see more in the Vendoring section. 47 | 48 | ## Show me the code 49 | 50 | ```html 51 | 52 | 53 | 56 | 57 | 74 | ``` 75 | 76 | via [codepen](https://codepen.io/jlarky/pen/vYoPzNE?editors=1000), total code 77 | size 78 | [3.41kb gzip](https://bundlejs.com/?q=https%3A%2F%2Fesm.sh%2F%40lift-html%2Fsolid%2Chttps%3A%2F%2Fesm.sh%2Fsolid-js&treeshake=%5B%7BliftSolid%7D%5D%2C%5B%7BcreateSignal%2CcreateEffect%7D%5D&share=PTAEBMFMDMEsDtKgIagMYFcDOAXA9gLaiQA2kBk8OAUKMQB4AOeATjunvLqALICeAIQw588UAF5QJWNBwBlPNPAAKAEQE%2BAWgBGw0aoA0oAN606oBLBzKAlCbPmOXdrpGcJoHAAtYWAHQAjhiQLHxypJBo%2BCxqrvo2ANwO5jKgygCEcZx23ix4AO6giIUAoix5MaoAPBo6epwAfKAE2OxonDjICCigVVnwDaqJyXT9fuC%2ByNpk4B7QyCRYkEmOdO3OoADa7RhURks4AMJ4uzgAuh5oLJDIOJBysADm8AvKAAzDq6BjnGjSaABrDy2CRNA7HU7KHZUEEAalAAEZPqsrjc7iVoNBItYQeImqYvqN6vA-Hd6EcOpR2JIAAaHf4ArAALlAABJjNDrDYAL40larbnIujcgxmQUJIA) 79 | and with no-build 80 | [8.94kb](https://bundlejs.com/?q=https%3A%2F%2Fesm.sh%2F%40lift-html%2Fsolid%2Chttps%3A%2F%2Fesm.sh%2Fsolid-js&treeshake=%5B*%5D%2C%5B*%5D&share=PTAEBMFMDMEsDtKgIagMYFcDOAXA9gLaiQA2kBk8OAUKMQB4AOeATjunvLqALICeAIQw588UAF5QJWNBwBlPNPAAKAEQE%2BAWgBGw0aoA0oAN606oBLBzKAlCbPmOXdrpGcJoHAAtYWAHQAjhiQLHxypJBo%2BCxqrvo2ANwO5jKgygCEcZx23ix4AO6giIUAoix5MaoAPBo6epwAfKAE2OxonDjICCigVVnwDaqJyXT9fuC%2ByNpk4B7QyCRYkEmOdO3OoADa7RhURks4AMJ4uzgAuh5oLJDIOJBysADm8AvKAAzDq6BjnGjSaABrDy2CRNA7HU7KHZUEEAalAAEZPqsrjc7iVoNBItYQeImqYvqN6vA-Hd6EcOpR2JIAAaHf4ArAALlAABJjNDrDYAL40larbnIujcgxmQUJIA) 81 | 82 | ```html 83 | 84 | 85 | 88 | 89 | 110 | ``` 111 | 112 | via [codepen](https://codepen.io/jlarky/pen/ogvZMLR?editors=1000), total code 113 | size 114 | [563 bytes gzip](https://bundlejs.com/?q=https%3A%2F%2Fesm.sh%2F%40lift-html%2Fcore&treeshake=%5B{liftHtml}%5D&share=PTAEBMFMDMEsDtKgIagMYFcDOAXA9gLaiQA2kBk8OAUJAB4AOeATjunvLqALICeAQhhz54oALygSsaDgASOAiQAUAIgK8AtACMhIlQBpQAb2qhQCWDiUBKY6bPtObHcI7jQOABawsAOgCOGJDMvADKpJBo%2BMyqLnrWANz2ZtKgSgCEcRy2Xsx4AO6giIUAosx5MSoAPOrauhwAfKAE2GxoHDjICCigVVnwDSqJyaD9vuA%2ByFpk4O7QyCRYkEkOkpBteBhU7gAMKw7MlFAxw6tjHGhSaADW7jbiTSarZu1bOADU7-urh-DHNt9QABfJIjaBbKKwNy-f62J7PMY4eg4ADCHUobAkAAMUVdrlgAFygAAkRleVCBWMBQPsQP01CBiSAA) 115 | and with no-build 116 | [574 bytes gzip](https://bundlejs.com/?q=https%3A%2F%2Fesm.sh%2F%40lift-html%2Fcore&treeshake=%5B*%5D&share=PTAEBMFMDMEsDtKgIagMYFcDOAXA9gLaiQA2kBk8OAUJAB4AOeATjunvLqALICeAQhhz54oALygSsaDgASOAiQAUAIgK8AtACMhIlQBpQAb2qhQCWDiUBKY6bPtObHcI7jQOABawsAOgCOGJDMvADKpJBo%2BMyqLnrWANz2ZtKgSgCEcRy2Xsx4AO6giIUAosx5MSoAPOrauhwAfKAE2GxoHDjICCigVVnwDSqJyaD9vuA%2ByFpk4O7QyCRYkEkOkpBteBhU7gAMKw7MlFAxw6tjHGhSaADW7jbiTSarZu1bOADU7-urh-DHNt9QABfJIjaBbKKwNy-f62J7PMY4eg4ADCHUobAkAAMUVdrlgAFygAAkRleVCBWMBQPsQP01CBiSAA) 117 | 118 | ## Why you need web components framework 119 | 120 | Web Components are a browser primitive, kind of like `document.createElement`. 121 | You are not expected to use them directly because of the amount of boilerplate 122 | and DX issues. But similarly to `document.createElement`, there are plently of 123 | usecases that require you to get your hands dirty with the native API. 124 | 125 | `lift-html` is an attempt to create tiny wrapper around web components that 126 | solves enough of DX issues without introducing a completely new paradigm. If I 127 | would put it in a single sentence, it would be: "When using lift-html I don't 128 | want to feel like I'm writing a component (web or otherwise), I just want to 129 | write HTML, CSS and JS/TS". 130 | 131 | To achieve that we had to depart a bit from the web components vibe. Namely: 132 | 133 | 167 | 168 | But otherwise if you are looking at vanilla HTML Web Component and lift-html one 169 | you might notice that they are 100% the same. 170 | 171 |
172 | ThemeToggle Example: vanilla vs lift-html (click to expand) 173 | 174 | Here's vanilla HTML Web Component from 175 | [Astro source code](https://github.com/withastro/astro/blob/45c3f333872a236d7c6a70ac805356737cdc68ec/examples/portfolio/src/components/ThemeToggle.astro): 176 | 177 | ```html 178 | 199 | ``` 200 | 201 | Now compare that to one using lift-html: 202 | 203 | ```html 204 | 225 | ``` 226 | 227 | If you can hardly notice the difference, that's the point. Apart from a couple 228 | `super` and `this` missing, the code is the same. The biggest difference is that 229 | we are using `init` instead of `constructor` because it's actually considered 230 | [wrong](https://x.com/JLarky/status/1856139102241890740) to access DOM from the 231 | constructor. And if you are a pedant you also noticed that `isDark` is now just 232 | a function instead of a method. 233 | 234 | Another note on implementation, this code is technically safe to run on the 235 | server because it doesn't reference global `HTMLElement` class and 236 | `customElements.define` method. It's also safe to run `liftHtml` multiple times, 237 | the last implementation will win. 238 | 239 |
240 | 241 | But the same can't be said about components using traditional web components 242 | frameworks. Your `lift-html` will likely look differently compared to web 243 | component authored in another web component framework. This is intentional, if 244 | you want to use framework components, just use framework components. For example 245 | a lot of code in other web components frameworks will have parts that look like 246 | this: 247 | 248 | - `class X extends MyFramework {}` and `@element` or `define()` - all that is 249 | done inside `liftHtml` 250 | - `@attribute` - you can pass `observedAttributes` to `liftHtml` and use helpers 251 | like `useAttributes` with your favorite flavor of reactivity 252 | - `shadow: true` - you can just directly call 253 | `this.attachShadow({mode: 'open'})` in `init` if you need it 254 | - `@state` - that part is outside of the scope of `@lift-html/core`, you are 255 | free to build something of your own or use `@lift-html/solid` or 256 | `@lift-html/signal` for that 257 | - `static styles = ...` just use `