├── .gitignore ├── LICENSE.txt ├── README.md ├── contexts ├── lit-html-client.d.ts ├── lit-html-client.js └── lit-html-server.js ├── index.d.ts ├── lit-app.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /node_modules/ 3 | pnpm-lock.yaml -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Klaudhaus 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lit-app 2 | 3 | > Build web apps the _light_ way 4 | 5 | ## Features 6 | 7 | * [`lit-html`](https://lit-html.polymer-project.org/) templates - real HTML, real JS 8 | * Develop with ES6 modules and no transpiling 9 | * Simple access to powerful state management with [`lit-up`](https://github.com/klaudhaus/lit-up) 10 | * Works with Web Components 11 | * .. and without, using fragment functions 12 | * Use the same view fragments in dynamic JS apps and static HTML sites 13 | * Scales to large modular apps 14 | * Environment-specific code branching 15 | * Ready-made contexts with full client and server capabilities 16 | * Quickly prepare development environments and build systems 17 | 18 | ## Quick Start 19 | 20 | If you haven't built a `lit` app before, check out [`lit-html`](https://lit-html.polymer-project.org/) and [`lit-up`](https://github.com/klaudhaus/lit-up) first. 21 | 22 | * Install 23 | 24 | ```bash 25 | npm install lit-app 26 | ``` 27 | 28 | * View components 29 | 30 | In order to create context-agnostic view fragment modules, access all necessary `lit-html` associated functions, as well as the `up` event handler factory from `lit-up`, via the `lit-app` module. 31 | 32 | ```js 33 | // item-list-view.js 34 | import { html, classMap, up } from "lit-app" 35 | 36 | function selectItem ({ items, item }) { 37 | items.forEach(i => i.selected = false) 38 | item.selected = true 39 | } 40 | 41 | export const itemListView = items => html` 42 | ` 51 | ``` 52 | 53 | Now you can quickly use this view fragment in either a dynamic interactive app or a statically rendered web page. 54 | 55 | * Interative app in browser 56 | 57 | ```js 58 | import { app } from "lit-app/contexts/lit-html-client" 59 | import { itemListView as view } from "./item-list-view" 60 | 61 | const model = [{ label: "One "}, { label: "Two" }] 62 | 63 | app({ model, view }) 64 | // Interactive app is up and running 65 | ``` 66 | 67 | * Static website page in Node 68 | 69 | ```js 70 | import { renderToString } from "lit-app/contexts/lit-html-server" 71 | import { itemListView as view } from "./item-list-view" 72 | 73 | const model = [{ label: "One "}, { label: "Two" }] 74 | 75 | const html = renderToString(model(view)) 76 | // Now deploy the static HTML 77 | ``` 78 | 79 | 80 | 81 | ## Contexts 82 | 83 | In order to speed the preparation of development environments and build systems, two ready-made implementation contexts are delivered within this package and can be imported as follows: 84 | 85 | ```js 86 | import { app } from "lit-app/contexts/lit-html-client" 87 | 88 | // or 89 | 90 | import { renderToString, renderToStream, renderToBuffer } 91 | from "lit-app/contexts/lit-html-server" 92 | ``` 93 | 94 | `lit-html-client` sets up a context with all the functionality from `lit-html` such as the `html` literal tag and all the standard directives. As a convenience, it also exports a proxy to the `app` function from `lit-up` which automatically adds the appropriate render implementation along with a `bootstrap` wrapper that shares the app's `up` function. You can thus bootstrap a fully interactive application with one call, with the signature: 95 | 96 | ``app({ model, view, bootstrap, logger, element })`` 97 | 98 | For more details on the parameter keys and default values see [`lit-up`](https://github.com/klaudhaus/lit-up#appoptions). 99 | 100 | `lit-html-client` provides a reference to the default browser API for `fetch`. 101 | 102 | `lit-html-server` sets up a context based on the implementation from `@popeindustries/lit-html-server`, and conveniently exports the string, stream and buffer rendering methods from its underlying implementation. 103 | 104 | `lit-html-server` provides a reference to `node-fetch` implentation. 105 | 106 | > Note: Using these ready made contexts is convenient for app development and static site build systems, but more efficient production JavaScript bundles can be made for apps that do not use every standard directive by using `appContext()` directly to assign only the necessary imports. 107 | > 108 | > ROADMAP: `lit-app-scan` to automatically prepare an efficient client or server context based on static analysis of provided view, model and bootstrap module imports. 109 | 110 | 111 | ## API 112 | 113 | ### `appContext( options )` 114 | 115 | Sets up an implementation context to be consumed by modules such as view fragments. 116 | 117 | If you use the ready-made contexts described above then all of this is set up for you and you have no need to call `appContext` directly. 118 | 119 | The `options` object should include the `lit-html` (or equivalent) core functions (`html`, `svg`) and directives (`classMap`, `ifDefined`, `guard` etc.) that may be needed in the app. 120 | 121 | It can also include a reference to a `lit-up` app's `up` function (typically obtained as an argument to the `bootstrap` handler) which allows view fragments to access `up` via a simple import without coupling them to a specific app or needing to pass it through the entire view hierarchy. 122 | 123 | It can also include an `env` object whose keys will be available to view fragments for conditional branching based on environment variables. 124 | 125 | It can also specify a `fetch` implementation. This can be useful in functions related to `lit-up` state. 126 | 127 | If not specified, `up` defaults to a no-op function suitable for static rendering, while all `lit-html` related functions are undefined by default so that any that are used by fragment functions without having been provided will quickly show up as errors in development. 128 | 129 | If there are multiple calls to `appContext`, references set up in earlier calls will only be overwritten by function keys that are specifically included in later calls, allowing a context to be set up incrementally. 130 | 131 | Here are some examples of calling `appContext` directly, instead of using ready-made contexts. They use the `itemListView` component shown above. 132 | ```js 133 | import { html, render } from "lit-html" 134 | import { classMap } from "lit-html/directives/classMap" 135 | import { app } from "lit-up" 136 | import { appContext } from "lit-app" 137 | 138 | import { itemListView as view } from "./item-list-view" 139 | 140 | const model = [{ label: "One "}, { label: "Two" }] 141 | 142 | const bootstrap = ({ up }) => appContext({ html, classMap, up }) 143 | 144 | app({ model, view, render, bootstrap }) 145 | ``` 146 | 147 | Or in a static website: 148 | 149 | ```js 150 | import { html, renderToString } from "@popeindustries/lit-html-server" 151 | import { classMap } from "@popeindustries/lit-html-server/directives/classMap" 152 | import { appContext } from "lit-app" 153 | 154 | import { itemListView } from "./item-list-view" 155 | 156 | const model = [{ label: "One "}, { label: "Two" }] 157 | 158 | appContext({ html, classMap }) 159 | const html = await renderToString(itemListView(model)) 160 | 161 | // Now deploy the static html output 162 | ``` 163 | 164 | 165 | 166 | ### ```html, svg, asyncAppend, asyncReplace, cache, classMap, ifDefined, guard, live, repeat, styleMap, templateContent, unsafeHTML, unsafeSVG, until``` 167 | 168 | Once the application context is initialised, all the `lit-html` related functions and directives (from whichever underlying implementation) are proxied via `lit-app`, enabling fully isomorphic view fragments. 169 | 170 | ### `up(update, data)` 171 | 172 | The `up` event handler factory from `lit-up` is also proxied via `lit-app`, allowing easy access to the application-specific `up` function for your view fragments. 173 | 174 | See [`lit-up`](https:github.com/klaudhaus/lit-up) for more details on how to use this powerful yet concise state management system. 175 | 176 | NOTE: This is only appropriate if you know that this `lit-app` is bundled (by `rollup` or similar) separately from any code that may set up another `lit-up` app on the same page - i.e. you are only dealing with one `up` function. 177 | 178 | ### `env` 179 | 180 | The `env` reference can be imported from lit-app, providing a container for environment variables that can be used to deliver different behavior, for example different logging in development and production. 181 | 182 | ```js 183 | import { app, env } from "lit-app" 184 | 185 | const logger = env.isProd 186 | ? false 187 | : ({ name, time, data } => console.log(`${name} ${time} ${data}`)) 188 | 189 | app({model, view, logge}) 190 | ``` 191 | 192 | You could also use a dynamic import expression to enable inclusion of whole code modules based on environment, enabling unnecessary development code to be excluded from production builds. 193 | 194 | ### `fetch` 195 | 196 | This key is intended to proxy the platform-specific `fetch` function, which is very useful in `lit-up` state management functions such as `bootstrap`. 197 | 198 | The default contexts set up proxies to platform-appropriate fetch implementations. 199 | 200 | ## Application Structure 201 | 202 | ### Using Web Components 203 | 204 | You can use any standards-compliant Web Component in interactive `lit-app` applications. However, bear in mind that web components generally rely on browser APIs to work, so they can't be used in content that may be rendered as static HTML. 205 | 206 | To use a Web Component, install it in your app as per its own documentation and then use the appropriate tag name and set its attributes, properties and event handlers (using `up`) accordingly. 207 | 208 | ```js 209 | import { WiredButton, WiredInput } from "wired-elements" 210 | 211 | const view = ({ model, up }) => html` 212 | 214 | 216 | Greet ${model.name}` 217 | ``` 218 | 219 | If you identify a generic component that is commonly reused across different interactive applications, and possibly other front-end frameworks, it is a good candidate to be implemented as a Web Component using a library such as `lit-element` or `haunted`. 220 | 221 | If you wish to split up your application view into smaller components for the purpose of clarity and organisation, rather than for reuse across different frameworks and organisations, building them as Web Components may be overkill. Also, Web Components may not be the best choice for SVG applications as there are some compatibility problems with the SVG namespace. 222 | 223 | ### Using Fragment Functions 224 | 225 | Ideally we should be able to create template code that can be reused across interactive front-end apps, static site build systems, server-side rendered sites, headless unit tests etc. 226 | 227 | When rendered statically, the component should display its given state. When used interactively, the view should have seamless access to link user events to application state management. 228 | 229 | Fragment functions implement a component, or fragment of the view, as a single function (which may itself call other fragment functions). They can accept data properties, event handlers and inner content. 230 | 231 | Implementing your view components as Fragment Functions with dependencies resolved via `lit-app` makes them "isomorphic" - they can be used in both interactive front-end apps as well as static pages. 232 | 233 | ```js 234 | // content-button.js 235 | 236 | import { html } from "lit-app" 237 | 238 | const contentButton = ({ label, content, click }) => html` 239 | ` 243 | ``` 244 | 245 | ```js 246 | 247 | import { html, up } from "lit-app" 248 | 249 | import { contentButton } from "./content-button.js" 250 | 251 | const view = ({ showUserProfile, showNews }) => html` 252 |
253 | ${contentButton({ 254 | label: "User", 255 | content: html`` 256 | click: up(showUserProfile) 257 | })} 258 | ${contentButton({ 259 | label: "News", 260 | content: html`` 261 | click: up(showNews) 262 | })} 263 |
` 264 | ``` 265 | -------------------------------------------------------------------------------- /contexts/lit-html-client.d.ts: -------------------------------------------------------------------------------- 1 | export { app } from "lit-up" 2 | -------------------------------------------------------------------------------- /contexts/lit-html-client.js: -------------------------------------------------------------------------------- 1 | import { html, svg, render } from "lit-html" 2 | 3 | import { asyncAppend } from "lit-html/directives/async-append" 4 | import { asyncReplace } from "lit-html/directives/async-replace" 5 | import { cache } from "lit-html/directives/cache" 6 | import { classMap } from "lit-html/directives/class-map" 7 | import { ifDefined } from "lit-html/directives/if-defined" 8 | import { guard } from "lit-html/directives/guard" 9 | import { live } from "lit-html/directives/live" 10 | import { repeat } from "lit-html/directives/repeat" 11 | import { styleMap } from "lit-html/directives/style-map" 12 | import { unsafeHTML } from "lit-html/directives/unsafe-html" 13 | import { unsafeSVG } from "lit-html/directives/unsafe-svg" 14 | import { templateContent } from "lit-html/directives/template-content" 15 | import { until } from "lit-html/directives/until" 16 | 17 | import { app as litUpApp } from "lit-up" 18 | 19 | import { appContext } from "../lit-app" 20 | 21 | appContext({ 22 | html, svg, 23 | asyncAppend, asyncReplace, cache, classMap, 24 | fetch: window && window.fetch, 25 | ifDefined, guard, live, repeat, styleMap, 26 | templateContent, unsafeHTML, unsafeSVG, until 27 | }) 28 | 29 | export const app = ({ model, view, element, bootstrap, logger }) => { 30 | const wrappedBootstrap = (args) => { 31 | appContext({ up: args.up }) 32 | if (typeof bootstrap === "function") return bootstrap(args) 33 | } 34 | 35 | return litUpApp({ model, view, element, bootstrap: wrappedBootstrap, logger, render }) 36 | } 37 | -------------------------------------------------------------------------------- /contexts/lit-html-server.js: -------------------------------------------------------------------------------- 1 | import { html, svg } from "@popeindustries/lit-html-server" 2 | 3 | import { asyncAppend } from "@popeindustries/lit-html-server/directives/async-append" 4 | import { asyncReplace } from "@popeindustries/lit-html-server/directives/async-replace" 5 | import { cache } from "@popeindustries/lit-html-server/directives/cache" 6 | import { classMap } from "@popeindustries/lit-html-server/directives/class-map" 7 | import { ifDefined } from "@popeindustries/lit-html-server/directives/if-defined" 8 | import { guard } from "@popeindustries/lit-html-server/directives/guard" 9 | import { repeat } from "@popeindustries/lit-html-server/directives/repeat" 10 | import { styleMap } from "@popeindustries/lit-html-server/directives/style-map" 11 | import { unsafeHTML } from "@popeindustries/lit-html-server/directives/unsafe-html" 12 | import { until } from "@popeindustries/lit-html-server/directives/until" 13 | 14 | import fetch from "node-fetch" 15 | 16 | import { appContext } from "../lit-app" 17 | 18 | export { renderToString, renderToStream, renderToBuffer } from "@popeindustries/lit-html-server" 19 | 20 | appContext({ 21 | html, svg, 22 | asyncAppend, asyncReplace, cache, classMap, ifDefined, 23 | fetch, 24 | guard, repeat, styleMap, unsafeHTML, until, 25 | // Stub substitutes for missing directives 26 | live: x => x, 27 | templateContent: x => x, 28 | unsafeSVG: unsafeHTML, 29 | }) 30 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export { 2 | TemplateResult, 3 | SVGTemplateResult, 4 | html, 5 | svg 6 | } from "lit-html" 7 | 8 | import { Up } from "lit-up" 9 | 10 | export { Update, UpdateResult, View, ViewResult } from "lit-up" 11 | 12 | export { asyncAppend } from "lit-html/directives/async-append" 13 | export { asyncReplace } from "lit-html/directives/async-replace" 14 | export { cache } from "lit-html/directives/cache" 15 | export { classMap } from "lit-html/directives/class-map" 16 | export { ifDefined } from "lit-html/directives/if-defined" 17 | export { guard } from "lit-html/directives/guard" 18 | export { live } from "lit-html/directives/live" 19 | export { repeat } from "lit-html/directives/repeat" 20 | export { styleMap } from "lit-html/directives/style-map" 21 | export { templateContent } from "lit-html/directives/template-content" 22 | export { unsafeHTML } from "lit-html/directives/unsafe-html" 23 | export { unsafeSVG } from "lit-html/directives/unsafe-svg" 24 | export { until } from "lit-html/directives/until" 25 | 26 | export const up: Up 27 | 28 | export function fetch (input: RequestInfo, init?: RequestInit) : Promise 29 | -------------------------------------------------------------------------------- /lit-app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Specify implementations of lit-html associated functions. 3 | * 4 | * This allows for implementation-agnostic view modules, 5 | * such as components that may be used with `lit-html` or `lit-html-server`. 6 | * 7 | * Also enables central registration of the `up` function from `lit-up`, 8 | * as an alternative pattern for accessing `up` instead of passing through views, 9 | * whilst maintaining component reusability across apps. 10 | * For situations where you know that your app and its dependencies will be 11 | * bundled separately from any other `lit-up` apps that may be on the same page. 12 | * 13 | * @param _html { Function } 14 | * @param _svg { Function } 15 | * @param _unsafeHTML { Function } 16 | * @param _unsafeSVG { Function } 17 | * @param _asyncAppend { Function } 18 | * @param _asyncReplace { Function } 19 | * @param _cache { Function } 20 | * @param _env { Object } 21 | * @param _fetch { Object } 22 | * @param _fetch { Function } 23 | * @param _classMap { Function } 24 | * @param _ifDefined { Function } 25 | * @param _guard { Function } 26 | * @param _live { Function } 27 | * @param _repeat { Function } 28 | * @param _styleMap { Function } 29 | * @param _templateContent { Function } 30 | * @param _until { Function } 31 | * @param _up { Function } 32 | */ 33 | export const appContext = ({ 34 | html: _html = html, 35 | svg: _svg = svg, 36 | asyncAppend: _asyncAppend = asyncAppend, 37 | asyncReplace: _asyncReplace = asyncReplace, 38 | cache: _cache = cache, 39 | classMap: _classMap = classMap, 40 | env: _env = {}, 41 | fetch: _fetch = fetch, 42 | ifDefined: _ifDefined = ifDefined, 43 | guard: _guard = guard, 44 | live: _live = live, 45 | repeat: _repeat = repeat, 46 | styleMap: _styleMap = styleMap, 47 | templateContent: _templateContent = templateContent, 48 | unsafeHTML: _unsafeHTML = unsafeHTML, 49 | unsafeSVG: _unsafeSVG = unsafeSVG, 50 | until: _until = until, 51 | up: _up = up 52 | }) => { 53 | html = _html 54 | svg = _svg 55 | asyncAppend = _asyncAppend 56 | asyncReplace = _asyncReplace 57 | cache = _cache 58 | classMap = _classMap 59 | Object.assign(env, _env) 60 | fetch = _fetch 61 | ifDefined = _ifDefined 62 | guard = _guard 63 | live = _live 64 | repeat = _repeat 65 | styleMap = _styleMap 66 | templateContent = _templateContent 67 | unsafeHTML = _unsafeHTML 68 | unsafeSVG = _unsafeSVG 69 | until = _until 70 | up = _up 71 | } 72 | 73 | // Mutable references to the functions of `lit-html` or equivalent implementation 74 | 75 | export let html 76 | 77 | export let svg 78 | 79 | export let asyncAppend 80 | 81 | export let asyncReplace 82 | 83 | export let cache 84 | 85 | export let classMap 86 | 87 | export let ifDefined 88 | 89 | export let guard 90 | 91 | export let live 92 | 93 | export let repeat 94 | 95 | export let styleMap 96 | 97 | export let templateContent 98 | 99 | export let unsafeHTML 100 | 101 | export let unsafeSVG 102 | 103 | export let until 104 | 105 | // Mutable reference to the `up` function from `lit-up` - defaults to a noop for static rendering 106 | export let up = () => {} 107 | 108 | // Mutable reference to the platform-specific `fetch` implementation 109 | export let fetch 110 | 111 | // A container for environment specific variables 112 | export const env = {} 113 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-app", 3 | "version": "1.1.6", 4 | "description": "Build implementation-agnostic `lit` apps and sites", 5 | "main": "lit-app.js", 6 | "module": "lit-app.js", 7 | "keywords": [], 8 | "author": "tstewart@klaudhaus.com", 9 | "repository": "github:klaudhaus/lit-app", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@popeindustries/lit-html-server": "^3.1.0", 13 | "lit-html": "^1.2.1", 14 | "lit-up": "^1.1.3", 15 | "node-fetch": "^2.6.0" 16 | } 17 | } 18 | --------------------------------------------------------------------------------