{"id":19601802,"url":"https://github.com/frctl/fractalite","last_synced_at":"2025-04-27T17:32:03.406Z","repository":{"id":35275184,"uuid":"171938277","full_name":"frctl/fractalite","owner":"frctl","description":"A prototype to help explore development ideas for future Fractal versions 🛠","archived":false,"fork":false,"pushed_at":"2023-01-07T03:15:53.000Z","size":8870,"stargazers_count":26,"open_issues_count":39,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-21T02:25:39.060Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/frctl.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}},"created_at":"2019-02-21T20:12:52.000Z","updated_at":"2024-10-05T12:35:28.000Z","dependencies_parsed_at":"2023-01-15T17:31:02.753Z","dependency_job_id":null,"html_url":"https://github.com/frctl/fractalite","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frctl%2Ffractalite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frctl%2Ffractalite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frctl%2Ffractalite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frctl%2Ffractalite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frctl","download_url":"https://codeload.github.com/frctl/fractalite/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250807395,"owners_count":21490492,"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":[],"created_at":"2024-11-11T09:20:23.385Z","updated_at":"2025-04-27T17:32:03.039Z","avatar_url":"https://github.com/frctl.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fractalite\n\nA prototype to help explore development ideas for the next version of [Fractal](https://fractal.build).\n\n[Features \u0026 Status](#features) | [Demo](#demo)  | [Documentation](#docs)\n\n\u003e Development work on a potential Fractal v2 release was halted a while ago after it became clear that it incorporated too many conceptual changes from v1 and the codebase had become too large and unwieldy.\n\u003e\n\u003e This prototype has been create to explore a middle ground, (hopefully) incorporating many of the v2 improvements into a package that is conceptually much closer to the current fractal v1.x release.\n\n### 📣 We need your feedback! 📣\n\nThe aim of this prototype is to provide the ground work for the next version of Fractal. It's very much a work in progress and we'd love to get **as much input from the community as possible** to help shape that process.\n\n**Please let us know your thoughts** by [opening a new issue using the 'Feedback' issue template](https://github.com/frctl/fractalite/issues/new?assignees=allmarkedup\u0026labels=Feedback\u0026template=feedback.md\u0026title=Feedback+) or by jumping into the `#fractalite` channel in the [Fractal community Discord](https://discord.gg/vuRz4Yx) to discuss. Feel free to message `@mark` with any questions you might have!\n\n---\n\n\u003ch2 id=\"features\"\u003e🚦 Features \u0026 status\u003c/h2\u003e\n\nThis prototype is in the very early 'developer preview' stages and is currently focussed on development direction alongside plugin and adapter APIs.\n\nFeedback, comments and/or pull requests on **all** aspects of the prototype are however always welcome!\n\n#### Currently implemented\n\n* Middleware-based components parser/compiler\n* [Plugin system](#plugins) for compiler and UI customisation\n* [Adapter-based](#adapters) component rendering\n* Completely customisable [nav generation](#navigation)\n* Component and scenario search\n* Easy [asset referencing](#view-template-assets) within components\n* Dynamic page builder\n* Zero-config [asset bundling](#assets-bundler-plugin) (via plugin)\n* Hybrid client/server side-rendered UI (using [Vue](https://vuejs.org))\n* Live-reloading development mode\n* Static build export\n\n#### Still missing/in progress\n\n* [ ] Proper UI design \u0026 implementation\n  * [ ] Responsive UI\n  * [ ] Cross-browser tested\n  * [ ] Loading states\n  * [ ] More variables for theming\n  * [ ] Proper landing page\n* [ ] More tests\n* [ ] Documentation\n* [ ] Additional template engine adapters\n  * [ ] Handlebars\n  * [ ] Twig\n  * [ ] React\n  * [ ] Others...?\n* [ ] More UI customisation hooks?\n* [ ] More extensive feature demo\n* [ ] Any other suggestions...?\n\n\n\u003ch2 id=\"demo\"\u003e📺 Demo\u003c/h2\u003e\n\nThe most full-featured demo is the [Nunjucks demo](demos/nunjucks). It uses the [Nunjucks adapter](packages/adapter-nunjucks) alongside the [Asset Bundler](packages/plugin-assets-bundler) and [Notes](packages/plugin-notes) plugins.\n\n\u003e The source code for the Nunjucks demo contains commented examples of some of the main features of this prototype and is worth investigating in conjunction with the web UI.\n\n\u003cimg src=\"./assets/nunjucks-demo.png\" alt=\"Screenshot of the Nunjucks demo\" width=\"100%\"\u003e\n\n### Running the demo\n\n1. Download or clone this repo\n2. `npm install` - install top-level dependencies\n3. `npm run bootstrap`: bootstrap packages together (may take some time on first run!)\n\n#### 🛠 Development mode\n\n1. `npm run demo` - Start the development server\n2. View the app: http://localhost:3030.\n\nChanges to the Nunjucks components will be instantly reflected in the UI.\n\n#### 📦 Static build mode\n\n1. `npm run demo:build` - Export flat-file version of the app \u0026 serve the `dist` directory\n2. View the static app: http://localhost:4040\n\nExported files can be found in the `demos/nunjucks/build` directory after export.\n\n\u003e As the name suggests, the static build is **not** regenerated when component files are updated.\n\n_A hosted version of the static build [can be found here](https://build-ozscagysic.now.sh)_\n\n#### Other demos\n\n* [Vue demo](demos/vue) - Basic proof-of-concept, client-side rendered Vue integration. `npm run demo:vue`\n\n\u003ch2 id=\"docs\"\u003e📚 Documentation\u003c/h2\u003e\n\nBelow is some preliminary documentation to help get across some of the key aspects of the Fractalite prototype.\n\n\u003e This documentation assumes good knowledge of Fractal (v1) concepts and is not intended as a starter guide!\n\n* [Installation](#installation)\n* [Project Configuration](#project-configuration)\n* **Components**\n  * [Overview](#components)\n  * [Scenarios](#scenarios)\n  * [Configuration](#configuration)\n  * [View templates](#view-templates)\n* **Previews**\n  * [Overview](#previews)\n  * [Adding CSS \u0026 JS to previews](#preview-css-js)\n  * [Customising preview markup](#preview-markup)\n* **Pages**\n  * [Overview](#pages)\n  * [Usage](#pages-usage)\n  * [Reference tags](#pages-reference-tags)\n  * [Nunjucks templates](#pages-nunjucks-templates)\n  * [Front Matter](#pages-frontmatter)\n* **Application UI**\n  * [Overview](#application-ui)\n  * [Navigation](#navigation)\n  * [Theming](#theming)\n* **Plugins**\n  * [Overview](#plugins)\n  * [Example plugin - author info](#example-plugin-author-info)\n  * [Assets bundler plugin](#assets-bundler-plugin)\n  * [Notes plugin](#notes-plugin)\n* **Adapters**\n  * [Overview](#adapters)\n  * [Structure of an adapter](#adapter-structure)\n  * [Example adapter - Mustache](#adapter-example-mustache)\n* **API**\n  * [Compiler](#api-compiler)\n  * [Application](#api-application)\n  * [Adapter](#api-adapter)\n  * [Component](#api-component)\n  * [Page](#api-page)\n  * [File](#api-file)\n\n## Installation\n\n**Note: Fractalite is not currently published to NPM.** The following steps are for information purposes only until published.\n\nInstall via NPM:\n\n```bash\nnpm i @frctl/fractalite --save-dev\n```\n\nAdd the following NPM scripts to the project `package.json` file:\n\n```js\n{\n  \"scripts\": {\n    \"start\": \"fractalite start --port 3333\",\n    \"build\": \"fractalite build\"\n  }\n}\n```\n\nYou can now start the Fractalite app by running the `npm start` command from within the project directory.\n\n## Project Configuration\n\nFractalite config is kept in a `fractal.config.js` file in the project root directory. Only the `components` property is required.\n\n```js\n// fractal.config.js\nconst { resolve } = require('path');\n\nmodule.exports = {\n  // absolute path to the components directory\n  components: resolve(__dirname, './src/components'),\n};\n```\n\n\u003e The Nunjucks demo contains a [annotated example](demos/nunjucks/fractal.config.js) of a project configuration file that contains more detail on the available options.\n\n## Components\n\nEach Fractalite component is a directory containing one or more files.\n\u003c!--\nEach component directory will typically contain a set of files including as component config file, a view template file (or the framework-specific equivalent) and any other related assets. --\u003e\n\nIn order for Fractalite to correctly identify a directory as a component, it must include **at least one** of the following:\n\n* A view file with a name matching `view.*` or `*.view.*` (i.e. `view.html` or `button.view.njk`)\n* A config file with a name matching `config.*` or `*.config.*` (i.e. `config.yml` or `button.config.js`)\n* A `package.json` file\n\nA component directory can then also include any number of other related files and folders as required.\n\nThe file structure for a basic [Nunjucks](https://mozilla.github.io/nunjucks) button component might look something like this:\n\n```\nbutton\n├── button.config.js\n├── button.css\n└── view.njk\n```\n\n### Scenarios\n\n**Component scenarios** are a key concept in Fractalite.\n\nA scenario provides an example implementation of the component by supplying a set of properties to render the component with.\n\n\u003e Scenarios are very similar to the concept of `variants` in Fractal v1, but refined and renamed to better suit the way in which v1 variants have been used in practice. They can also be thought of as similar to the 'story' concept in StorybookJS.\n\nFor example, a common use for a `button` component might be as a 'next' control. A simple scenario object representing that might look as follows:\n\n```js\n{\n  name: 'next', // reference name\n  label: 'Next step', // display in UI navigation\n  props: {\n    text: 'Go Next',\n    icon: './arrow-right.png'\n  }  \n}\n```\n\n\u003e `props` are similar to the `context` object in Fractal v1\n\nScenarios are defined in the **component config file**. The Fractalite UI creates a component preview for each scenario defined for that component.\n\nAny relative paths to assets in the scenario `props` object are resolved to full URLs before rendering.\n\n#### Rendering multiple scenario instances per preview\n\nSometimes you may want to render multiple instances of the same scenario in one preview window - say for example to test the `next` button scenario with labels of differing lengths:\n\n\u003cimg src=\"./assets/scenario-previews.png\" alt=\"Multiple buttons in a preview\" width=\"600\"\u003e\n\nTo support this, each scenario can define a `preview` property as an array of props. Each item in this array will be **merged with the default scenario props**, and the preview will render one instance for each set of merged props.\n\n```js\n{\n  name: 'next',\n  props: {\n    text: 'Go Next',\n    icon: './arrow-right.png'\n  },\n  preview: [\n    {\n      label: 'Next'\n    },\n    {\n      label: 'Next button with a long label that might wrap'\n    }\n  ]\n}\n```\n\nThe preview for this scenario will have two buttons in it, one for each of the preview items defined.\n\n\u003e See the [Previews](#previews) section for details on how to customise the preview markup.\n\n#### Adapter integration\n\nTemplate engine adapters such as the [Nunjucks adapter](packages/adapter-nunjucks) support including sub-components with scenario properties provided as their default property values:\n\n```html\n{% component 'button' %} \u003c!-- include `button` component with no props --\u003e\n{% component 'button/next' %}  \u003c!-- include `button` component with props from `next` scenario --\u003e\n{% component 'button/next', { text: 'Forwards' } %}  \u003c!-- include `button` component with props from `next` scenario merged with inline props --\u003e\n```\n\n### Configuration\n\nComponent **config files** can be JSON, YAML or CommonJS module format, although the latter is recommended for flexibility.\n\nConfig files must be named `config.{ext}` or `{component-name}.config.{ext}` - for example `button.config.js` or `config.yml`.\n\nCommonJS formatted files should export a configuration object:\n\n```js\n// button/button.config.js\nmodule.exports = {\n  label: 'A basic button',\n  // other config here...\n};\n```\n#### Config properties\n\n\u003e See the demo button component for an [annotated example](demos/nunjucks/src/components/01-units/button/button.config.js) of some of the available config options.\n\n**`label`** [string]\n\n\nThe text that should be used to refer to the component in the UI. [_Defaults to a title-cased version of the component name._]\n\n**`scenarios`** [array]\n\nAn array of [scenario objects](#scenarios).\n\n```js\n// button/button.config.js\nmodule.exports = {\n  scenarios: [\n    {\n      name: 'next',\n      props: {\n        text: 'Go Next',\n        icon: './arrow-right.png'\n      }  \n    },\n    {\n      name: 'prev',\n      props: {\n        text: 'Go Prev',\n        icon: './arrow-left.png'\n      }  \n    }\n  ]\n}\n```\n\n### View templates\n\nView templates are **template-engine specific** files that contain the code required to render the component.\n\nFractalite **adapters** are responsible for determining how view templates are named, rendered and for any other framework/engine related integration details.\n\nHowever, in the case of 'simple' template engines such as Nunjucks or Handlebars, views are typically templated fragments of HTML as in the following (Nunjucks) example:\n\n```html\n\u003c!-- button/view.njk --\u003e\n\u003ca class=\"button\" href=\"{{ href }}\"\u003e\n  \u003cspan class=\"button__text\"\u003e{{ text }}\u003c/span\u003e\n\u003c/a\u003e\n```\n\nMore complex frameworks such as Vue or React may have different requirements and feature support will be determined by the adapter used.\n\n\u003ch4 id=\"view-template-assets\"\u003eLinking to assets in view templates\u003c/h4\u003e\n\nReferencing local component assets in view templates can be done via relative paths:\n\n```\nbutton\n├── next-arrow.png\n└── view.njk\n```\n\n```html\n\u003c!-- view.njk --\u003e\n\u003cimg src=\"./next-arrow.png\"\u003e\n```\n\nAny relative paths in [html attributes that expect a URL value](https://www.npmjs.com/package/html-url-attributes) will be dynamically rewritten to reference the asset correctly.\n\n## Previews\n\nRendered component instances are wrapped in an HTML document for previewing.\n\nA typical project will need to be configured to inject the required styles and scripts into the preview to correctly display the component.\n\n\u003ch3 id=\"preview-css-js\"\u003eAdding CSS \u0026 JS to previews\u003c/h3\u003e\n\n\u003e The [assets bundler plugin](#assets-bundler-plugin) automatically injects bundled assets into previews so this step may not be needed if using it in your project.\n\nThe `preview` option in the  project config file lets you specify scripts and stylesheets to be  injected into to all component previews.\n\n```js\n// fractal.config.js\nconst { resolve } = require('path');\n\nmodule.exports = {\n  // ...\n  preview: {\n    /*\n     * Assets can be specified as either:\n     *\n     * 1) an absolute path to the file\n     * 2) an external URL\n     * 3) or an object with 'url' and 'path' keys\n     */\n    stylesheets: [\n      resolve(__dirname, './dist/styles.css'), // (1)\n      'http://example.com/external-styles.css', // (2)\n      {\n        url: '/custom/url/path.css',\n        path: resolve(__dirname, './dist/yet-more-styles.css')\n      } // (3)\n    ],\n    scripts: [\n      // scripts can be added in the same way as stylesheets\n    ]\n  }\n};\n```\n\nIndividual components can also add **local CSS/JS** files from within their directory using relative paths:\n\n```\nbutton\n├── preview.css\n└── view.njk\n```\n\n```js\n// button/button.config.js\nmodule.exports = {\n  preview: [\n    stylesheets: ['./preview.css'],\n    scripts: [/* ... */],\n  ]\n}\n```\n\n\u003e It's also possible to add 'inline' JS/CSS code to the previews using the `preview.css` and `preview.js` config options. See the Nunjucks demo [button component config](demos/nunjucks/src/components/01-units/button/button.config.js) for an annotated example of this in action.\n\n\u003ch3 id=\"preview-markup\"\u003eCustomising preview markup\u003c/h3\u003e\n\nAs well as adding assets, Fractalite also exposes a number of ways to completely customise the preview markup and output:\n\n- Each individually rendered scenario instance can be wrapped in custom HTML using the `preview.wrapEach` option (available both **globally** and on a **component-by-component** basis)\n- Each _set_ of scenario instances can be wrapped in custom html using the `preview.wrap` option (available both **globally** and on a **component-by-component** basis)\n- The entire preview document template can also be completely overridden if required (only available as a global option)\n\n#### Component-level customisation\n\n```js\n// button/button.config.js\nmodule.exports = {\n\n  preview: {\n\n    // add an in-preview title\n    wrap(html, ctx) {\n      return `\n        \u003ch4\u003e${ctx.component.label} / ${ctx.scenario.label}\u003c/h4\u003e\n        ${html}`;\n    },\n\n    // wrap each item in the preview to space them out\n    wrapEach(html, ctx) {\n      return `\u003cdiv style=\"margin-bottom: 20px\"\u003e${html}\u003c/div\u003e`;\n    }\n  }\n}\n```\n\n#### Bespoke preview templates\n\nCustom preview template markup can be provided by using the `preview.template` config option.\n\n\u003e Note that preview templates are rendered using Nunjucks. The default preview template can be [found here](packages/fractalite/views/preview.njk) for reference.\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  preview: {\n    template: `\n    \u003c!DOCTYPE html\u003e\n    \u003chtml\u003e\n    \u003chead\u003e\n      {% for url in stylesheets %}\u003clink rel=\"stylesheet\" href=\"{{ url }}\"\u003e{% endfor %}\n      {% if css %}\u003cstyle\u003e{{ css | safe }}\u003c/style\u003e{% endif %}\n      \u003ctitle\u003e{{ meta.title | default('Preview') }}\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n      \u003cdiv id=\"app\"\u003e\n        \u003ch1\u003eA custom preview\u003c/h1\u003e\n        \u003cdiv class=\"wrapper\"\u003e\n          {{ content | safe }}\n        \u003c/div\u003e\n      \u003c/div\u003e\n      {% for url in scripts %}\u003cscript src=\"{{ url }}\"\u003e\u003c/script\u003e{% endfor %}\n      {% if js %}\u003cscript\u003e{{ js | safe }}\u003c/script\u003e{% endif %}\n    \u003c/body\u003e\n    \u003c/html\u003e\n    `\n  }\n};\n```\n\nFor even more control over the preview rendering process it is also possible to provide a function instead of a string as the `preview.template` value.\n\nThis allows you to use any template engine you like for the preview rendering (or none at all!).\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  preview: {\n    template: function(content, opts){\n      /*\n       * The return value of the function should be the\n       * fully rendered preview template string.\n       *\n       * Any stylesheets, scripts etc that have been\n       * added in global or component config are available\n       * in the `opts` object.\n       */\n      return `\n      \u003chtml\u003e\n        \u003chead\u003e\n          \u003c!-- add stylesheets, meta etc --\u003e\n        \u003c/head\u003e\n        \u003cbody\u003e${content}\u003c/body\u003e\n        \u003c!-- add scripts etc --\u003e\n      \u003c/html\u003e`;\n    }\n  }\n};\n```\n\n## Pages\n\nEach project can specify a directory of pages to be displayed in the app.\n\nPages can either be Markdown documents (with a `.md` extension) or Nunjucks templates (with a `.njk` extension) and can define Jekyll-style [front matter](https://jekyllrb.com/docs/front-matter/) blocks for configuration options.\n\n\u003ch3 id=\"pages-usage\"\u003eUsage\u003c/h3\u003e\n\nAdd the absolute path to the pages directory to the project config file:\n\n```js\n// fractal.config.js\nconst { resolve } = require('path');\nmodule.exports = {\n  // ...\n  pages: resolve(__dirname, './pages'), // absolute path to the pages directory\n};\n```\n\nThen create the pages:\n\n```\n./pages\n├── about.njk\n└── index.md\n```\n\n\u003e If an `index` file (either with `.md` or `.njk` extension) is added in the root of the pages directory then this will override the default application welcome page.\n\n\u003ch3 id=\"pages-reference-tags\"\u003eReference tags\u003c/h3\u003e\n\nReference tags can be used in pages to make linking to other pages, component previews and source files both easier and less fragile. They also allow basic access to properties of page and component objects.\n\nReference tags take the form `{target:identifier:property}`.\n\n* `target`: one of `component`, `page`, `file`, `inspect` or `preview`\n* `identifier`: unique identifier for the target - for example the component name or page handle\n* `property`: optional, defaults to `url`  \n\nSome example reference tags:\n\n```html\n \u003c!-- button component inspector URL --\u003e\n{inspect:button}\n\n \u003c!-- standalone preview URL for button component with 'next scenario' --\u003e\n{preview:button/next}\n\n \u003c!-- URL of raw source of the button view template --\u003e\n{file:button/view.njk}\n\n\u003c!-- URL of the about page --\u003e\n{page:about}\n\n\u003c!-- title of the about page --\u003e\n{page:about:title}\n```\n\n\u003ch3 id=\"pages-nunjucks-templates\"\u003eNunjucks templates\u003c/h3\u003e\n\nNunjucks templates (pages with a `.njk` extension) have access to the current compiler state properties as well as any data provided in the front matter block:\n\n```html\n\u003c!-- about.njk --\u003e\n\u003cp\u003eThe following components are available\u003c/p\u003e\n\u003cul\u003e\n  {% for component in components %}\n  \u003cli\u003e\u003ca href=\"{{ component.url }}\"\u003e{{ component.label }}\u003c/a\u003e\u003c/li\u003e\n  {% endfor %}  \n\u003c/ul\u003e\n```\n\n\u003ch3 id=\"pages-frontmatter\"\u003eFront Matter\u003c/h3\u003e\n\nThe following page configuration options are available and can be set in a front matter block at the top of pages that require it.\n\n**`title`** [string]\n\nThe title displayed at the top of the page.\n\n**`label`** [string]\n\nUsed to refer to the page in any navigation\n\n**`handle`** [string]\n\nUsed in reference tags to refer to the page. Defaults to the page URL with slashes replaced by dashes.\n\n**`markdown`** [boolean]\n\nWhether or not to run the page contents through the markdown renderer. _[Defaults to `true` for `.md` pages, false for all others.]_\n\n**`template`** [boolean]\n\nWhether or not to run the page contents through the Nunjucks renderer. _[Defaults to `true` for `.njk` pages, false for all others.]_\n\n## Application UI\n\nMany aspects of the Fractalite UI can be configured, customised or overridden.\n\n### Navigation\n\nThe sidebar navigation contents can be customised in the project `fractal.config.js` config file using the `nav.items` property.\n\nThe value of this property can either be an array of navigation items or a generator function that returns an array of items.\n\nEach item in the nav array should *either* be an object with the following properties:\n\n* `label`: Text to be displayed for the nav item\n* `url`: URL to link to (if `children` are not specified)\n* `children`: Array of child navigation items (if `url` is not specified)\n\n*Or* it an be a `Page`, `Component` or `File` instance. `Component` instances will automatically have their scenarios added as child items.\n\nIf no value for the `nav.items` property is supplied then the default nav generator will be used which includes links to all pages and components.\n\n#### Hard-coding items\n\nMost projects will want to dynamically generate their navigation, however it may occasionally be useful to hard-code the nav for some use cases.\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  nav: {\n    items: [\n      {\n        label: 'Welcome',\n        url: '/'\n      },\n      {\n        label: 'Components',\n        children: [\n          {\n            label: 'Next Button',\n            url: '/inspect/button/next'\n          },\n          {\n            label: 'Call to Action',\n            url: '/inspect/cta/default'\n          }\n        ]\n      },\n      {\n        label: 'Github',\n        url: 'https://github.com/org/project'\n      },\n    ]\n  }\n};\n```\n\n#### Dynamically generating items\n\nA generator function can be supplied instead of hard-coding the items.\n\nThe generator will receive the compiler `state` as its first argument and a `toTree` utility function as the second argument. The `toTree` utility can be used to generate a file-system based tree from a flat array of components or files.\n\nThe generator function should return an array of navigation items in the same format as the hard-coded example above.\n\nThe example below shows components can be filtered before generating the navigation:\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  nav: {\n    items(state, toTree){\n      // filter components by some custom property in the config\n      const components = state.components.filter(component =\u003e {\n        return component.config.customProp === true;\n      });\n      // return the navigation tree\n      return [\n        {\n          label: 'Components',\n          children: components // flat list of filtered components\n        },\n        {\n          label: 'Pages',\n          children: toTree(state.pages) // tree of pages\n        }\n      ];\n    }\n  }\n};\n```\n\n### Theming\n\nFractalite offers a number of options for customising the look and feel of the UI.\n\n#### Theme variables\n\nBasic colour and style changes can be made by customising the available theme variables in the global config file:\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  theme: {\n    vars: {\n      'sidebar-bg-color': 'red'\n    }\n  }\n};\n```\n\n#### Custom CSS \u0026 JS\n\nMore complex needs can be met by adding custom `stylesheets`, `scripts` or 'inline' CSS/JS to override the default theme:\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  theme: {\n    stylesheets: [\n      {\n        url: '/custom/url/path.css',\n        path: resolve(__dirname, './dist/styles.css')\n      }\n    ],\n    scripts: [/* ... */],\n    css:`\n      body {\n        background: pink;\n      }\n    `,\n    js: `\n      console.log(window.location);\n    `\n  }\n};\n```\n\n#### Template overrides\n\nIt is also possible to override some or all of the templates used in generating the UI by specifiying a custom views directory:\n\n```js\n// fractal.config.js\nconst { resolve } = require('path');\n\nmodule.exports = {\n  // ...\n  theme: {\n    views: resolve(__dirname, './custom-theme-views') // path to views directory\n  }\n};\n```\n\nAny files placed within this custom views directory will override their equivalents in the [application views directory](packages/fractalite/views).\n\n```\ncustom-theme-views\n└── partials\n    └── brand.njk // override sidebar branding template\n```\n\n#### Theme modules\n\nThemes can also be provided as modules. In this case the module will receive the `app` instance and should return a set of theme config values:\n\n```js\n// ./custom-theme.js\nmodule.exports = function(app){\n  // Can customise app instance here...\n  return {\n    // ...and return any theme config opts here\n    vars: {\n      'tabs-highlight-color--active': 'pink'\n    }\n  }\n}\n```\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...\n  theme: require('./custom-theme.js')\n};\n```\n\n## Plugins\n\nPlugins the primary way that the Fractalite app can be customised, and can affect both the UI and the component parsing/compilation process.\n\nPlugins are added in the project config file:\n\n```js\n// fractal.config.js\nmodule.exports = {\n  plugins: [\n    require('./plugins/example')({\n      // customisation opts here\n    })\n  ]\n};\n```\n\nA plugin is a function that receives `app`, `compiler` and `adapter` instances as it's arguments.\n\nA useful pattern is to wrap the plugin function itself in a 'parent' function so that it can receive runtime options:\n\n```js\n// plugins/example.js\nmodule.exports = function(opts = {}){\n  // any plugin initialiation here\n  return function(app, compiler, adapter){\n    // this is the plugin function itself\n    console.log('This is an example plugin');\n  }\n};\n```\n\n### Example plugin - author info\n\nThe following is an example of a fairly basic plugin that reads author information from component config files and adds a tab to the component inspector UI to display this information.\n\n```js\n// plugins/author-info.js\nmodule.exports = function(opts = {}) {\n  return function authorPlugin(app, compiler) {\n\n    const authorDefaults = {\n      name: 'Unknown Author',\n      email: null\n    };\n\n    /*\n     * First add a compiler middleware function\n     * to extract author info from the component config\n     * and create a .author property on the component\n     * object with a normalized set of properties.\n     */\n    compiler.use(components =\u003e {\n      components.forEach(component =\u003e {\n        const authorConfig = component.config.author || {};\n        component.author = { ...authorDefaults, ...authorConfig };\n      });\n    });\n\n    /*\n     * Then add an inspector panel to display the\n     * author information in the UI. The panel templates\n     * are rendered using Nunjucks and have access to the\n     * current component, scenario and compiler state.\n     */\n    app.addInspectorPanel({\n      name: 'component-author',\n      label: 'Author Info',\n      template: `\n         \u003cdiv class=\"author-panel\"\u003e\n           \u003ch3\u003eAuthor information\u003c/h3\u003e\n           \u003cul\u003e\n             \u003cli\u003e\u003cstrong\u003eName:\u003c/strong\u003e {{ component.author.name }}\u003c/li\u003e\n             {% if component.author.email %}\n             \u003cli\u003e\u003cstrong\u003eEmail:\u003c/strong\u003e {{ component.author.email }}\u003c/li\u003e\n             {% endif %}\n           \u003c/ul\u003e\n         \u003c/div\u003e\n       `,\n       css: `\n        .author-panel {\n          padding: 20px;\n        }\n        .author-panel ul {\n          margin-top: 20px;\n        }\n      `\n    });\n  };\n};\n```\nAuthor information can then be added to component config files and will be displayed in the UI:\n\n```js\n// button/button.config.js\nmodule.exports = {\n  author: {\n    name: \"Daffy Duck\",\n    email: 'daffy@duck.com'\n  }\n}\n```\n\n\u003e Note that in the simple example above the compiler middleware could have been skipped in favour of a little more verbosity in the template. In more complex real-world examples however this is not always the case.\n\n### Assets bundler plugin\n\nThe [asset bundler plugin](packages/plugin-assets-bundler) uses [Parcel](https://parceljs.org) to provide a zero-config asset bundling solution for Fractalite.\n\nIt handles asset compilation, hot module reloading (HMR) and automatically adds all generated assets into Fractalite component previews.\n\nFirst add it to the project config file:\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...  \n  plugins: [\n    require('@frctl/fractalite-plugin-assets-bundler')({\n      entryFile: './src/preview.js',\n      outFile: './dist/build.js'\n    })\n  ]\n};\n```\n\nThen create the global entry file to bundle the required assets as per your project requirements. An example might look like this:\n\n```js\n// ./assets/preview.js\nimport '../components/**/*.scss'\nimport button from '../components/button/button.js'\n```\n\n\u003e See the Parcel docs on module resolution for more info on paths, globbing and aliases: https://parceljs.org/module_resolution.html\n\n#### Dynamic entry file building\n\nThe asset bundler also support dynamic creation of the entry file using the `entryBuilder` config option.\n\nThe entry builder will be re-run whenever changes are made to the components directory.\n\n```js\nconst { relative } = require('path');\n\nconst bundlerPlugin = require('@frctl/fractalite-plugin-assets-bundler')({\n\n  /*\n   * The entryBuilder function receives state and context\n   * objects and should return a string of the entry file contents.\n   *\n   * This example dynamically build an entry file that imports all\n   * css files from components.\n   */\n  entryBuilder(state, ctx) {\n    let entry = '';\n    state.files.filter(f =\u003e f.ext === '.scss').forEach(file =\u003e {\n      entry += `import '${relative(ctx.dir, file.path)}'\\n`; // import paths need to be relative to the ctx.dir property\n    });\n    return entry;\n  },\n\n  // entry and out files must still be specified\n  entryFile: './src/preview.js',\n  outFile: './dist/build.js'\n})\n```\n\n### Notes plugin\n\nThe [notes plugin](packages/plugin-notes) adds a inspector panel to display component notes.\n\nNotes can be defined via the `notes` property in the component config file, or alternatively kept in a markdown file in the component directory.\n\n```js\n// fractal.config.js\nmodule.exports = {\n  // ...  \n  plugins: [\n    require('@frctl/fractalite-plugin-notes')({\n      notesFile: 'notes.md' // optional, only if notes should be read from files\n    })\n  ]\n};\n```\n\n## Adapters\n\nAdapters allow Fractalite to support many different template engines and frameworks.\n\nFractalite currently supports a single adapter per project. Adapters are specified in the project configuration file:\n\n```js\n// fractal.config.js\nmodule.exports = {\n  adapter: require('./example-adapter.js')({\n    // customisation opts here\n  })\n};\n```\n\nAll adapters **must** provide a `render` function which receives a component, a set of props, and a state object and returns a string representing the rendered component.\n\nAdapters also have access to both the `compiler` and the `app` instances so can also perform much more complicated integration with the application if required.\n\n\u003ch3 id=\"adapter-structure\"\u003eStructure of an adapter\u003c/h3\u003e\n\nAn adapter is a function that receives `app` and `compiler` instances as it's arguments, and must return a **render function** or an **adapter object that includes a render function amongst its properties**.\n\nAs with [plugins](#plugins), a useful pattern is to wrap the adapter function itself in a 'parent' function so that it can receive runtime options:\n\n```js\n// example-adapter.js\nmodule.exports = function(opts = {}){\n  // any adapter initialiation here\n  return function(app, compiler){\n    // do anything with app/compiler here\n    return function render(component, props, state){\n      // do rendering here...\n      return html;\n    }\n  }\n};\n```\n\nRender functions can be asynchronous  - just return a `Promise` that resolves to the HTML string.\n\n\u003ch3 id=\"adapter-example-mustache\"\u003eExample adapter - Mustache\u003c/h3\u003e\n\nThe following example is for a basic adapter for Mustache templates. To keep it simple it does not include support for partials.\n\n```js\nconst Mustache = require('mustache');\n\nmodule.exports = function(opts = {}) {\n  /*\n   * Allow users to override the default view name\n   */\n  const viewName = opts.viewName || 'view.mustache';\n\n  return function mustacheAdapter() {\n    /*\n     * Asynchronous render function.\n     *\n     * Looks for a matching view file in the list of component files,\n     * reads it's contents and then renders the string using the\n     * Mustache.render method.\n     */\n    return async function render(component, props) {\n      const tpl = component.files.find(file =\u003e file.basename === viewName);\n      if (!tpl) {\n        throw new Error(`Cannot render component - no view file found.`);\n      }\n      const tplContents = await tpl.getContents();\n      return Mustache.render(tplContents, props);\n    };\n  };\n};\n\n```\n\n### Advanced adapters\n\nMore advanced adapters can return an object instead of a simple `render` function - in this case the `render` function must be provided as a method on the returned object:\n\n```js\n// example-adapter.js\nmodule.exports = function(opts = {}){\n  // any adapter initialiation here\n  return function(app, compiler){\n    // do anything with app/compiler here\n    return {\n      render(component, props, state){\n        // do rendering here...\n        return html;\n      },\n      // any additional integration methods here\n    }\n  }\n};\n```\n\nThe adapter can then choose to implement any of the following additional methods to provide a deeper integration with the core:\n\n#### .getTemplateString(component)\n\nShould return a string representation of a source template, if relevant to the target template engine or framework. Can return string or a `Promise` that resolves to a string.\n\n\n\n## API\n\n\u003ch2 id=\"api-compiler\"\u003eCompiler\u003c/h2\u003e\n\n#### `compiler.use(fn)`\n\nPush a compiler middleware function onto the stack.\n\nMiddleware receive the `components` array as the first argument, and a Koa-style `next` function as the second argument.\n\nMiddleware can mutate the contents of the components array as needed. Asynchronous middleware is supported.\n\n**Unlike in Koa middleware** the `next` function _only_ needs to be called if the middleware should wait for latter middleware to complete before running.\n\n```js\n// 'plain' middleware, no awaiting\ncompiler.use(components =\u003e {\n  components.forEach(component =\u003e {\n    // ...\n  })\n})\n\n// 'asynchronous' middleware\ncompiler.use(async components =\u003e {\n  await theAsyncTask();\n  components.forEach(component =\u003e {\n    // ...\n  })\n})\n\n// middleware that waits for latter middleware to complete first\ncompiler.use(async (components, next) =\u003e {\n  await next();\n  components.forEach(component =\u003e {\n    // ...\n  })\n})\n```\n\n#### `compiler.getState()`\n\nReturns an object representing the current state of the compiler. By default this includes `components` and `files` properties.\n\n```js\nconst state = compiler.getState();\n\nstate.components.forEach(component =\u003e {\n  console.log(component.name);\n});\n\nstate.files.forEach(component =\u003e {\n  console.log(component.path);\n});\n```\n\n#### `compiler.parse()`\n\nRe-parse the component directory and update the internal compiler state. Returns a Promise that resolves to a state object.\n\n\u003ch2 id=\"api-application\"\u003eApplication\u003c/h2\u003e\n\n### Properties\n\n#### `app.mode`\n#### `app.router`\n#### `app.views`\n\n### UI\n\n#### `app.addInspectorPanel(props)`\n#### `app.getInspectorPanels()`\n#### `app.removeInspectorPanel(name)`\n\n### Previews\n\n#### `app.addPreviewStylesheet(url, [path])`\n#### `app.addPreviewScript(url, [path])`\n#### `app.addPreviewCSS(css)`\n#### `app.addPreviewJS(js)`\n#### `app.beforeScenarioRender(fn)`\n#### `app.afterScenarioRender(fn)`\n#### `app.beforePreviewRender(fn)`\n#### `app.afterPreviewRender(fn)`\n#### `app.addPreviewWrapper(fn, wrapEach)`\n\n### Routing\n\n#### `app.addRoute(name, path, handler)`\n#### `app.url(name, params)`\n\n### Lifecycle\n\n#### `app.beforeStart(fn)`\n#### `app.on(event, handler)`\n#### `app.emit(event, [...args])`\n\n### Views\n\n#### `app.addViewPath(path)`\n#### `app.addViewExtension(name, ext)`\n#### `app.addViewFilter(name, filter)`\n#### `app.addViewGlobal(name, value)`\n\n### Assets\n\n#### `app.addStylesheet(url, [path])`\n#### `app.addScript(url, [path])`\n#### `app.addCSS(css)`\n#### `app.addJS(js)`\n#### `app.addStaticDir(name, path, [mount])`\n#### `app.serveFile(url, path)`\n\n### Utils\n\n#### `app.utils.renderMarkdown(str)`\n#### `app.utils.highlightCode(code)`\n#### `app.utils.parseFrontMatter(str)`\n#### `app.utils.renderPage(str, [props], [opts])`\n#### `app.utils.addReferenceLookup(key, handler)`\n\n### Other\n\n#### `app.extend(methods)`\n\n\u003ch2 id=\"api-adapter\"\u003eAdapter\u003c/h2\u003e\n\n#### `adapter.render(component, props)`\n#### `adapter.renderAll(component, arrayOfProps)`\n#### `adapter.getTemplateString(component)`\n\n\u003ch2 id=\"api-component\"\u003eComponent\u003c/h2\u003e\n\n### Properties\n\n#### `component.name`\n#### `component.label`\n#### `component.config`\n#### `component.files`\n#### `component.scenarios`\n#### `component.isComponent`\n#### `component.root`\n#### `component.path`\n#### `component.relative`\n#### `component.url`\n#### `component.previewUrl`\n\n### Methods\n\n#### `component.matchFiles(matcher)`\n\n\u003ch2 id=\"api-page\"\u003ePage\u003c/h2\u003e\n\n### Properties\n\n#### `page.label`\n#### `page.title`\n#### `page.position`\n#### `page.url`\n#### `page.isPage`\n\n### Methods\n\n#### `page.getContents()`\n\n\u003ch2 id=\"api-file\"\u003eFile\u003c/h2\u003e\n\n### Properties\n\n#### `file.name`\n#### `file.path`\n#### `file.relative`\n#### `file.basename`\n#### `file.dirname`\n#### `file.extname`\n#### `file.ext`\n#### `file.stem`\n#### `file.stats`\n#### `file.size`\n#### `file.url`\n#### `file.isFile`\n\n### Methods\n\n#### `file.setContents(str)`\n#### `file.getContents()`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrctl%2Ffractalite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrctl%2Ffractalite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrctl%2Ffractalite/lists"}