Component Strategy

There are three types of components in Ink: Document, Template, and Component. Each type of component has a different strategy for rendering and updating the DOM. The Document component is the root component of the application and is responsible for rendering the entire application. The Template component is a reusable component that can be used in multiple places in the application. The Component component is a custom component that can be used to create complex UI elements.

Document

A document denoted by files with the .ink extension, is the root of each page view that should include the document markup starting with <html>. While it looks like another Ink component, there are some key differences in how it is used.
  • A document logic (<script>) is executed on the client side but is not a InkComponent, which means it cannot be re-rendered and does not have access to this context.
  • A document props() are the server props passed down to the client.
  • A document does not have access to NodeJS functionality. So things like fs are not available.
Recommendation: You should do server related logic on the server framework and pass the neccesary data to the client.
src/index.ts src/page.ink
src
index.ts page.ink
import http from 'http'; import ink from '@stackpress/ink/compiler'; const compiler = ink({ cwd: __dirname }); const server = http.createServer(async (req, res) => { //pass server props to document res.end(await compiler.render('./index.ink', { title: 'Hello World' })); });

Template

A template is resuable partial markup that can be imported by a component, document or another template. A template is not is not a InkComponent, but rather adds its markup to the parent component's final markup. You will not see a template in the DOM, but rather the markup it contains. For example, consider a document that contains the following markup. <script> const title = 'Hello World'; </script> <html> <head> <meta charset="utf-8" /> <title>{title}</title> </head> <body> <h1>{title}</h1> </body> </html> You can create a template for the head of your document and then import it. This allows you to reuse the head markup in multiple documents.
src/page.ink src/head.ink
src
page.ink head.ink
<link rel="import" type="template" href="./head.ink" name="html-head" /> <script> const title = 'Hello World'; </script> <html> <html-head /> <body> <h1>{title}</h1> </body> </html>
Note: Template partials do not process attributes or children if given. Variables used in a template should be declared in the parent component or document. This allows you to pass data to the template from the parent.

Component

All ink components are InkComponent that extends HTMLElement and therefore is both a web component and element just like any other element in the browser DOM. Components that do not use the <style> tag are affected by the global styles of the application. Components with the <style> tag enable the component's shadow DOM and will encapsulate the styles within the component and not be affected by global styles. With that said, there are several strategies that can be applied to Ink components.

Strategy 1: No Components

This strategy uses only documents and templates. This strategy is useful for simple applications that do not require complex UI elements. This is the best strategy for performant applications.
src/page.ink src/head.ink src/header.ink src/footer.ink
src
page.ink head.ink header.ink footer.ink
<link rel="import" type="template" href="./head.ink" name="html-head" /> <link rel="import" type="template" href="./header.ink" name="page-header" /> <link rel="import" type="template" href="./footer.ink" name="page-footer" /> <script> import { env } from '@stackpress/ink'; const { BUILD_ID, APP_DATA } = env(); const title = 'Hello World'; </script> <html> <html-head /> <body> <page-header /> <main> <h1>{title}</h1> </main> <page-footer /> </body> </html>

Strategy 2: Shallow Components

This strategy uses components that do not have a <style> tag and is useful for applications that require complex logic in components but using a shared global stylesheet.
src/page.ink src/header.ink src/footer.ink
src
page.ink header.ink footer.ink
<link rel="import" type="component" href="./header.ink" name="page-header" /> <link rel="import" type="component" href="./footer.ink" name="page-footer" /> <script> import { env } from '@stackpress/ink'; const { BUILD_ID, APP_DATA } = env(); const title = 'Hello World'; const brand = 'Acme Inc.'; const logo = '/logo.png'; </script> <html> <head> <meta charset="utf-8" /> <title>{title}</title> <link rel="stylesheet" type="text/css" href="/styles.css" /> <link rel="stylesheet" type="text/css" href={`/build/${BUILD_ID}.css`} /> <script data-app={APP_DATA} src={`/build/${BUILD_ID}.js`}></script> </head> <body> <page-header {brand} {logo} /> <main> <h1>{title}</h1> </main> <page-footer {brand} /> </body> </html>

Strategy 3: Partial Styling

This strategy uses components that do not have a <style> tag, but imports style via the <link> tag to utilize both global styles and specific styles that are needed for the component.
src/page.ink src/header.ink src/footer.ink
src
page.ink header.ink footer.ink
<link rel="stylesheet" type="text/css" href="/header.css" /> <script> import { props } from '@stackpress/ink'; const { brand, logo } = props(); </script> <header> <img src={logo} alt={brand} /> <h6>{brand}</h6> </header>

Strategy 4: Encapulation

This strategy uses components that have a <style> tag and encapsulates the styles within the component. This strategy is useful for applications that require complex UI elements that need to be styled in a specific way. This is also useful for components that are designed to be used in multiple projects.
src/page.ink src/header.ink src/footer.ink
src
page.ink header.ink footer.ink
<style> img { width: 100px; height: 100px; } h6 { margin: 0; } </style> <script> import { props } from '@stackpress/ink'; const { brand, logo } = props(); </script> <header> <img src={logo} alt={brand} /> <h6>{brand}</h6> </header>

Flash of Unstyled Content

Web Components (custom elements) are 100% defined in JavaScript. That includes their HTML and CSS. Those are programmatically added to the DOM through APIs. By the time the browser has interpreted and executed that code, there is a good chance that the rendering pipeline has already put the custom element on the screen. Since the browser doesn't know about the element the first time around it will render it without the intended styling. After the JavaScript of the custom element definition is executed and the browser, therefore, knows about the CSS rules that apply to that element it can update the view. A flash of unstyled content (FOUC) can cause irritating layout shifts as well as reveal content that should have been progressively disclosed. In order to prevent a reflow of other content you can add the following general solution to your global stylesheet. *:not(:defined) { opacity: 0; } This style will apply to all elements that are not defined, which are usually web components and will hide the content until the browser has fully rendered the component.