Code Monkey home page Code Monkey logo

fractalite's Introduction

Fractalite

A prototype to help explore development ideas for the next version of Fractal.

Features & Status | Demo | Documentation

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.

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.

📣 We need your feedback! 📣

The 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.

Please let us know your thoughts by opening a new issue using the 'Feedback' issue template or by jumping into the #fractalite channel in the Fractal community Discord to discuss. Feel free to message @mark with any questions you might have!


🚦 Features & status

This prototype is in the very early 'developer preview' stages and is currently focussed on development direction alongside plugin and adapter APIs.

Feedback, comments and/or pull requests on all aspects of the prototype are however always welcome!

Currently implemented

  • Middleware-based components parser/compiler
  • Plugin system for compiler and UI customisation
  • Adapter-based component rendering
  • Completely customisable nav generation
  • Component and scenario search
  • Easy asset referencing within components
  • Dynamic page builder
  • Zero-config asset bundling (via plugin)
  • Hybrid client/server side-rendered UI (using Vue)
  • Live-reloading development mode
  • Static build export

Still missing/in progress

  • Proper UI design & implementation
    • Responsive UI
    • Cross-browser tested
    • Loading states
    • More variables for theming
    • Proper landing page
  • More tests
  • Documentation
  • Additional template engine adapters
    • Handlebars
    • Twig
    • React
    • Others...?
  • More UI customisation hooks?
  • More extensive feature demo
  • Any other suggestions...?

📺 Demo

The most full-featured demo is the Nunjucks demo. It uses the Nunjucks adapter alongside the Asset Bundler and Notes plugins.

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.

Screenshot of the Nunjucks demo

Running the demo

  1. Download or clone this repo
  2. npm install - install top-level dependencies
  3. npm run bootstrap: bootstrap packages together (may take some time on first run!)

🛠 Development mode

  1. npm run demo - Start the development server
  2. View the app: http://localhost:3030.

Changes to the Nunjucks components will be instantly reflected in the UI.

📦 Static build mode

  1. npm run demo:build - Export flat-file version of the app & serve the dist directory
  2. View the static app: http://localhost:4040

Exported files can be found in the demos/nunjucks/build directory after export.

As the name suggests, the static build is not regenerated when component files are updated.

A hosted version of the static build can be found here

Other demos

  • Vue demo - Basic proof-of-concept, client-side rendered Vue integration. npm run demo:vue

📚 Documentation

Below is some preliminary documentation to help get across some of the key aspects of the Fractalite prototype.

This documentation assumes good knowledge of Fractal (v1) concepts and is not intended as a starter guide!

Installation

Note: Fractalite is not currently published to NPM. The following steps are for information purposes only until published.

Install via NPM:

npm i @frctl/fractalite --save-dev

Add the following NPM scripts to the project package.json file:

{
  "scripts": {
    "start": "fractalite start --port 3333",
    "build": "fractalite build"
  }
}

You can now start the Fractalite app by running the npm start command from within the project directory.

Project Configuration

Fractalite config is kept in a fractal.config.js file in the project root directory. Only the components property is required.

// fractal.config.js
const { resolve } = require('path');

module.exports = {
  // absolute path to the components directory
  components: resolve(__dirname, './src/components'),
};

The Nunjucks demo contains a annotated example of a project configuration file that contains more detail on the available options.

Components

Each Fractalite component is a directory containing one or more files.

In order for Fractalite to correctly identify a directory as a component, it must include at least one of the following:

  • A view file with a name matching view.* or *.view.* (i.e. view.html or button.view.njk)
  • A config file with a name matching config.* or *.config.* (i.e. config.yml or button.config.js)
  • A package.json file

A component directory can then also include any number of other related files and folders as required.

The file structure for a basic Nunjucks button component might look something like this:

button
├── button.config.js
├── button.css
└── view.njk

Scenarios

Component scenarios are a key concept in Fractalite.

A scenario provides an example implementation of the component by supplying a set of properties to render the component with.

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.

For example, a common use for a button component might be as a 'next' control. A simple scenario object representing that might look as follows:

{
  name: 'next', // reference name
  label: 'Next step', // display in UI navigation
  props: {
    text: 'Go Next',
    icon: './arrow-right.png'
  }  
}

props are similar to the context object in Fractal v1

Scenarios are defined in the component config file. The Fractalite UI creates a component preview for each scenario defined for that component.

Any relative paths to assets in the scenario props object are resolved to full URLs before rendering.

Rendering multiple scenario instances per preview

Sometimes 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:

Multiple buttons in a preview

To 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.

{
  name: 'next',
  props: {
    text: 'Go Next',
    icon: './arrow-right.png'
  },
  preview: [
    {
      label: 'Next'
    },
    {
      label: 'Next button with a long label that might wrap'
    }
  ]
}

The preview for this scenario will have two buttons in it, one for each of the preview items defined.

See the Previews section for details on how to customise the preview markup.

Adapter integration

Template engine adapters such as the Nunjucks adapter support including sub-components with scenario properties provided as their default property values:

{% component 'button' %} <!-- include `button` component with no props -->
{% component 'button/next' %}  <!-- include `button` component with props from `next` scenario -->
{% component 'button/next', { text: 'Forwards' } %}  <!-- include `button` component with props from `next` scenario merged with inline props -->

Configuration

Component config files can be JSON, YAML or CommonJS module format, although the latter is recommended for flexibility.

Config files must be named config.{ext} or {component-name}.config.{ext} - for example button.config.js or config.yml.

CommonJS formatted files should export a configuration object:

// button/button.config.js
module.exports = {
  label: 'A basic button',
  // other config here...
};

Config properties

See the demo button component for an annotated example of some of the available config options.

label [string]

The text that should be used to refer to the component in the UI. [Defaults to a title-cased version of the component name.]

scenarios [array]

An array of scenario objects.

// button/button.config.js
module.exports = {
  scenarios: [
    {
      name: 'next',
      props: {
        text: 'Go Next',
        icon: './arrow-right.png'
      }  
    },
    {
      name: 'prev',
      props: {
        text: 'Go Prev',
        icon: './arrow-left.png'
      }  
    }
  ]
}

View templates

View templates are template-engine specific files that contain the code required to render the component.

Fractalite adapters are responsible for determining how view templates are named, rendered and for any other framework/engine related integration details.

However, 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:

<!-- button/view.njk -->
<a class="button" href="{{ href }}">
  <span class="button__text">{{ text }}</span>
</a>

More complex frameworks such as Vue or React may have different requirements and feature support will be determined by the adapter used.

Linking to assets in view templates

Referencing local component assets in view templates can be done via relative paths:

button
├── next-arrow.png
└── view.njk
<!-- view.njk -->
<img src="./next-arrow.png">

Any relative paths in html attributes that expect a URL value will be dynamically rewritten to reference the asset correctly.

Previews

Rendered component instances are wrapped in an HTML document for previewing.

A typical project will need to be configured to inject the required styles and scripts into the preview to correctly display the component.

Adding CSS & JS to previews

The assets bundler plugin automatically injects bundled assets into previews so this step may not be needed if using it in your project.

The preview option in the project config file lets you specify scripts and stylesheets to be injected into to all component previews.

// fractal.config.js
const { resolve } = require('path');

module.exports = {
  // ...
  preview: {
    /*
     * Assets can be specified as either:
     *
     * 1) an absolute path to the file
     * 2) an external URL
     * 3) or an object with 'url' and 'path' keys
     */
    stylesheets: [
      resolve(__dirname, './dist/styles.css'), // (1)
      'http://example.com/external-styles.css', // (2)
      {
        url: '/custom/url/path.css',
        path: resolve(__dirname, './dist/yet-more-styles.css')
      } // (3)
    ],
    scripts: [
      // scripts can be added in the same way as stylesheets
    ]
  }
};

Individual components can also add local CSS/JS files from within their directory using relative paths:

button
├── preview.css
└── view.njk
// button/button.config.js
module.exports = {
  preview: [
    stylesheets: ['./preview.css'],
    scripts: [/* ... */],
  ]
}

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 for an annotated example of this in action.

Customising preview markup

As well as adding assets, Fractalite also exposes a number of ways to completely customise the preview markup and output:

  • 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)
  • 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)
  • The entire preview document template can also be completely overridden if required (only available as a global option)

Component-level customisation

// button/button.config.js
module.exports = {

  preview: {

    // add an in-preview title
    wrap(html, ctx) {
      return `
        <h4>${ctx.component.label} / ${ctx.scenario.label}</h4>
        ${html}`;
    },

    // wrap each item in the preview to space them out
    wrapEach(html, ctx) {
      return `<div style="margin-bottom: 20px">${html}</div>`;
    }
  }
}

Bespoke preview templates

Custom preview template markup can be provided by using the preview.template config option.

Note that preview templates are rendered using Nunjucks. The default preview template can be found here for reference.

// fractal.config.js
module.exports = {
  // ...
  preview: {
    template: `
    <!DOCTYPE html>
    <html>
    <head>
      {% for url in stylesheets %}<link rel="stylesheet" href="{{ url }}">{% endfor %}
      {% if css %}<style>{{ css | safe }}</style>{% endif %}
      <title>{{ meta.title | default('Preview') }}</title>
    </head>
    <body>
      <div id="app">
        <h1>A custom preview</h1>
        <div class="wrapper">
          {{ content | safe }}
        </div>
      </div>
      {% for url in scripts %}<script src="{{ url }}"></script>{% endfor %}
      {% if js %}<script>{{ js | safe }}</script>{% endif %}
    </body>
    </html>
    `
  }
};

For 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.

This allows you to use any template engine you like for the preview rendering (or none at all!).

// fractal.config.js
module.exports = {
  // ...
  preview: {
    template: function(content, opts){
      /*
       * The return value of the function should be the
       * fully rendered preview template string.
       *
       * Any stylesheets, scripts etc that have been
       * added in global or component config are available
       * in the `opts` object.
       */
      return `
      <html>
        <head>
          <!-- add stylesheets, meta etc -->
        </head>
        <body>${content}</body>
        <!-- add scripts etc -->
      </html>`;
    }
  }
};

Pages

Each project can specify a directory of pages to be displayed in the app.

Pages can either be Markdown documents (with a .md extension) or Nunjucks templates (with a .njk extension) and can define Jekyll-style front matter blocks for configuration options.

Usage

Add the absolute path to the pages directory to the project config file:

// fractal.config.js
const { resolve } = require('path');
module.exports = {
  // ...
  pages: resolve(__dirname, './pages'), // absolute path to the pages directory
};

Then create the pages:

./pages
├── about.njk
└── index.md

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.

Reference tags

Reference 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.

Reference tags take the form {target:identifier:property}.

  • target: one of component, page, file, inspect or preview
  • identifier: unique identifier for the target - for example the component name or page handle
  • property: optional, defaults to url

Some example reference tags:

 <!-- button component inspector URL -->
{inspect:button}

 <!-- standalone preview URL for button component with 'next scenario' -->
{preview:button/next}

 <!-- URL of raw source of the button view template -->
{file:button/view.njk}

<!-- URL of the about page -->
{page:about}

<!-- title of the about page -->
{page:about:title}

Nunjucks templates

Nunjucks 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:

<!-- about.njk -->
<p>The following components are available</p>
<ul>
  {% for component in components %}
  <li><a href="{{ component.url }}">{{ component.label }}</a></li>
  {% endfor %}  
</ul>

Front Matter

The following page configuration options are available and can be set in a front matter block at the top of pages that require it.

title [string]

The title displayed at the top of the page.

label [string]

Used to refer to the page in any navigation

handle [string]

Used in reference tags to refer to the page. Defaults to the page URL with slashes replaced by dashes.

markdown [boolean]

Whether or not to run the page contents through the markdown renderer. [Defaults to true for .md pages, false for all others.]

template [boolean]

Whether or not to run the page contents through the Nunjucks renderer. [Defaults to true for .njk pages, false for all others.]

Application UI

Many aspects of the Fractalite UI can be configured, customised or overridden.

Navigation

The sidebar navigation contents can be customised in the project fractal.config.js config file using the nav.items property.

The value of this property can either be an array of navigation items or a generator function that returns an array of items.

Each item in the nav array should either be an object with the following properties:

  • label: Text to be displayed for the nav item
  • url: URL to link to (if children are not specified)
  • children: Array of child navigation items (if url is not specified)

Or it an be a Page, Component or File instance. Component instances will automatically have their scenarios added as child items.

If 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.

Hard-coding items

Most projects will want to dynamically generate their navigation, however it may occasionally be useful to hard-code the nav for some use cases.

// fractal.config.js
module.exports = {
  // ...
  nav: {
    items: [
      {
        label: 'Welcome',
        url: '/'
      },
      {
        label: 'Components',
        children: [
          {
            label: 'Next Button',
            url: '/inspect/button/next'
          },
          {
            label: 'Call to Action',
            url: '/inspect/cta/default'
          }
        ]
      },
      {
        label: 'Github',
        url: 'https://github.com/org/project'
      },
    ]
  }
};

Dynamically generating items

A generator function can be supplied instead of hard-coding the items.

The 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.

The generator function should return an array of navigation items in the same format as the hard-coded example above.

The example below shows components can be filtered before generating the navigation:

// fractal.config.js
module.exports = {
  // ...
  nav: {
    items(state, toTree){
      // filter components by some custom property in the config
      const components = state.components.filter(component => {
        return component.config.customProp === true;
      });
      // return the navigation tree
      return [
        {
          label: 'Components',
          children: components // flat list of filtered components
        },
        {
          label: 'Pages',
          children: toTree(state.pages) // tree of pages
        }
      ];
    }
  }
};

Theming

Fractalite offers a number of options for customising the look and feel of the UI.

Theme variables

Basic colour and style changes can be made by customising the available theme variables in the global config file:

// fractal.config.js
module.exports = {
  // ...
  theme: {
    vars: {
      'sidebar-bg-color': 'red'
    }
  }
};

Custom CSS & JS

More complex needs can be met by adding custom stylesheets, scripts or 'inline' CSS/JS to override the default theme:

// fractal.config.js
module.exports = {
  // ...
  theme: {
    stylesheets: [
      {
        url: '/custom/url/path.css',
        path: resolve(__dirname, './dist/styles.css')
      }
    ],
    scripts: [/* ... */],
    css:`
      body {
        background: pink;
      }
    `,
    js: `
      console.log(window.location);
    `
  }
};

Template overrides

It is also possible to override some or all of the templates used in generating the UI by specifiying a custom views directory:

// fractal.config.js
const { resolve } = require('path');

module.exports = {
  // ...
  theme: {
    views: resolve(__dirname, './custom-theme-views') // path to views directory
  }
};

Any files placed within this custom views directory will override their equivalents in the application views directory.

custom-theme-views
└── partials
    └── brand.njk // override sidebar branding template

Theme modules

Themes 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:

// ./custom-theme.js
module.exports = function(app){
  // Can customise app instance here...
  return {
    // ...and return any theme config opts here
    vars: {
      'tabs-highlight-color--active': 'pink'
    }
  }
}
// fractal.config.js
module.exports = {
  // ...
  theme: require('./custom-theme.js')
};

Plugins

Plugins the primary way that the Fractalite app can be customised, and can affect both the UI and the component parsing/compilation process.

Plugins are added in the project config file:

// fractal.config.js
module.exports = {
  plugins: [
    require('./plugins/example')({
      // customisation opts here
    })
  ]
};

A plugin is a function that receives app, compiler and adapter instances as it's arguments.

A useful pattern is to wrap the plugin function itself in a 'parent' function so that it can receive runtime options:

// plugins/example.js
module.exports = function(opts = {}){
  // any plugin initialiation here
  return function(app, compiler, adapter){
    // this is the plugin function itself
    console.log('This is an example plugin');
  }
};

Example plugin - author info

The 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.

// plugins/author-info.js
module.exports = function(opts = {}) {
  return function authorPlugin(app, compiler) {

    const authorDefaults = {
      name: 'Unknown Author',
      email: null
    };

    /*
     * First add a compiler middleware function
     * to extract author info from the component config
     * and create a .author property on the component
     * object with a normalized set of properties.
     */
    compiler.use(components => {
      components.forEach(component => {
        const authorConfig = component.config.author || {};
        component.author = { ...authorDefaults, ...authorConfig };
      });
    });

    /*
     * Then add an inspector panel to display the
     * author information in the UI. The panel templates
     * are rendered using Nunjucks and have access to the
     * current component, scenario and compiler state.
     */
    app.addInspectorPanel({
      name: 'component-author',
      label: 'Author Info',
      template: `
         <div class="author-panel">
           <h3>Author information</h3>
           <ul>
             <li><strong>Name:</strong> {{ component.author.name }}</li>
             {% if component.author.email %}
             <li><strong>Email:</strong> {{ component.author.email }}</li>
             {% endif %}
           </ul>
         </div>
       `,
       css: `
        .author-panel {
          padding: 20px;
        }
        .author-panel ul {
          margin-top: 20px;
        }
      `
    });
  };
};

Author information can then be added to component config files and will be displayed in the UI:

// button/button.config.js
module.exports = {
  author: {
    name: "Daffy Duck",
    email: '[email protected]'
  }
}

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.

Assets bundler plugin

The asset bundler plugin uses Parcel to provide a zero-config asset bundling solution for Fractalite.

It handles asset compilation, hot module reloading (HMR) and automatically adds all generated assets into Fractalite component previews.

First add it to the project config file:

// fractal.config.js
module.exports = {
  // ...  
  plugins: [
    require('@frctl/fractalite-plugin-assets-bundler')({
      entryFile: './src/preview.js',
      outFile: './dist/build.js'
    })
  ]
};

Then create the global entry file to bundle the required assets as per your project requirements. An example might look like this:

// ./assets/preview.js
import '../components/**/*.scss'
import button from '../components/button/button.js'

See the Parcel docs on module resolution for more info on paths, globbing and aliases: https://parceljs.org/module_resolution.html

Dynamic entry file building

The asset bundler also support dynamic creation of the entry file using the entryBuilder config option.

The entry builder will be re-run whenever changes are made to the components directory.

const { relative } = require('path');

const bundlerPlugin = require('@frctl/fractalite-plugin-assets-bundler')({

  /*
   * The entryBuilder function receives state and context
   * objects and should return a string of the entry file contents.
   *
   * This example dynamically build an entry file that imports all
   * css files from components.
   */
  entryBuilder(state, ctx) {
    let entry = '';
    state.files.filter(f => f.ext === '.scss').forEach(file => {
      entry += `import '${relative(ctx.dir, file.path)}'\n`; // import paths need to be relative to the ctx.dir property
    });
    return entry;
  },

  // entry and out files must still be specified
  entryFile: './src/preview.js',
  outFile: './dist/build.js'
})

Notes plugin

The notes plugin adds a inspector panel to display component notes.

Notes can be defined via the notes property in the component config file, or alternatively kept in a markdown file in the component directory.

// fractal.config.js
module.exports = {
  // ...  
  plugins: [
    require('@frctl/fractalite-plugin-notes')({
      notesFile: 'notes.md' // optional, only if notes should be read from files
    })
  ]
};

Adapters

Adapters allow Fractalite to support many different template engines and frameworks.

Fractalite currently supports a single adapter per project. Adapters are specified in the project configuration file:

// fractal.config.js
module.exports = {
  adapter: require('./example-adapter.js')({
    // customisation opts here
  })
};

All 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.

Adapters also have access to both the compiler and the app instances so can also perform much more complicated integration with the application if required.

Structure of an adapter

An 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.

As with plugins, a useful pattern is to wrap the adapter function itself in a 'parent' function so that it can receive runtime options:

// example-adapter.js
module.exports = function(opts = {}){
  // any adapter initialiation here
  return function(app, compiler){
    // do anything with app/compiler here
    return function render(component, props, state){
      // do rendering here...
      return html;
    }
  }
};

Render functions can be asynchronous - just return a Promise that resolves to the HTML string.

Example adapter - Mustache

The following example is for a basic adapter for Mustache templates. To keep it simple it does not include support for partials.

const Mustache = require('mustache');

module.exports = function(opts = {}) {
  /*
   * Allow users to override the default view name
   */
  const viewName = opts.viewName || 'view.mustache';

  return function mustacheAdapter() {
    /*
     * Asynchronous render function.
     *
     * Looks for a matching view file in the list of component files,
     * reads it's contents and then renders the string using the
     * Mustache.render method.
     */
    return async function render(component, props) {
      const tpl = component.files.find(file => file.basename === viewName);
      if (!tpl) {
        throw new Error(`Cannot render component - no view file found.`);
      }
      const tplContents = await tpl.getContents();
      return Mustache.render(tplContents, props);
    };
  };
};

Advanced adapters

More 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:

// example-adapter.js
module.exports = function(opts = {}){
  // any adapter initialiation here
  return function(app, compiler){
    // do anything with app/compiler here
    return {
      render(component, props, state){
        // do rendering here...
        return html;
      },
      // any additional integration methods here
    }
  }
};

The adapter can then choose to implement any of the following additional methods to provide a deeper integration with the core:

.getTemplateString(component)

Should 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.

API

Compiler

compiler.use(fn)

Push a compiler middleware function onto the stack.

Middleware receive the components array as the first argument, and a Koa-style next function as the second argument.

Middleware can mutate the contents of the components array as needed. Asynchronous middleware is supported.

Unlike in Koa middleware the next function only needs to be called if the middleware should wait for latter middleware to complete before running.

// 'plain' middleware, no awaiting
compiler.use(components => {
  components.forEach(component => {
    // ...
  })
})

// 'asynchronous' middleware
compiler.use(async components => {
  await theAsyncTask();
  components.forEach(component => {
    // ...
  })
})

// middleware that waits for latter middleware to complete first
compiler.use(async (components, next) => {
  await next();
  components.forEach(component => {
    // ...
  })
})

compiler.getState()

Returns an object representing the current state of the compiler. By default this includes components and files properties.

const state = compiler.getState();

state.components.forEach(component => {
  console.log(component.name);
});

state.files.forEach(component => {
  console.log(component.path);
});

compiler.parse()

Re-parse the component directory and update the internal compiler state. Returns a Promise that resolves to a state object.

Application

Properties

app.mode

app.router

app.views

UI

app.addInspectorPanel(props)

app.getInspectorPanels()

app.removeInspectorPanel(name)

Previews

app.addPreviewStylesheet(url, [path])

app.addPreviewScript(url, [path])

app.addPreviewCSS(css)

app.addPreviewJS(js)

app.beforeScenarioRender(fn)

app.afterScenarioRender(fn)

app.beforePreviewRender(fn)

app.afterPreviewRender(fn)

app.addPreviewWrapper(fn, wrapEach)

Routing

app.addRoute(name, path, handler)

app.url(name, params)

Lifecycle

app.beforeStart(fn)

app.on(event, handler)

app.emit(event, [...args])

Views

app.addViewPath(path)

app.addViewExtension(name, ext)

app.addViewFilter(name, filter)

app.addViewGlobal(name, value)

Assets

app.addStylesheet(url, [path])

app.addScript(url, [path])

app.addCSS(css)

app.addJS(js)

app.addStaticDir(name, path, [mount])

app.serveFile(url, path)

Utils

app.utils.renderMarkdown(str)

app.utils.highlightCode(code)

app.utils.parseFrontMatter(str)

app.utils.renderPage(str, [props], [opts])

app.utils.addReferenceLookup(key, handler)

Other

app.extend(methods)

Adapter

adapter.render(component, props)

adapter.renderAll(component, arrayOfProps)

adapter.getTemplateString(component)

Component

Properties

component.name

component.label

component.config

component.files

component.scenarios

component.isComponent

component.root

component.path

component.relative

component.url

component.previewUrl

Methods

component.matchFiles(matcher)

Page

Properties

page.label

page.title

page.position

page.url

page.isPage

Methods

page.getContents()

File

Properties

file.name

file.path

file.relative

file.basename

file.dirname

file.extname

file.ext

file.stem

file.stats

file.size

file.url

file.isFile

Methods

file.setContents(str)

file.getContents()

fractalite's People

Contributors

allmarkedup avatar andrejilderda avatar lebenleben avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

tylert88

fractalite's Issues

Initial short term feedback

High level feedback:

Absolutely love it, I've only really had time with the Nunjucks demo, but I feel like i'm going to miss some of the features from this already going back to current Fractal. I think you've smashed it and basically have nothing bad to say at all. I've noted down some random thoughts and feedback, let me know if anything doesn't make sense. Some high level Standout things:

  • The search is absolutely 10/10, hugely needed addition IMO and so nice to have this out the box, especially with per component hiding / aliases - is the plan to make this expandable? Just thinking it'd be great to optionally plug something like docsearch in.

  • Love the new info panel, I think the information provided is great and the new ability to see file sizes and skip between 'scenarios' quickly is a nice touch.

  • Mostly like the new naming conventions, especially 'props' in place of context. I'm not personally 100% sold on the 'Scenarios' direction but not a huge deal (see below)

  • Config file is way easier to digest and has some really nice new features

  • The application UI feels a lot snappier and is just a pleasure to use

  • Pages change is very welcome and much much more robust, love the removal of markdown dependancy

  • View templates being more obviously split from data feels cleaner and more modern

  • New direction for themeing and plugins will be a huge hit IMO

More in depth feedback

Config / Preview

I think the project config file is way cleaner and easier to digest than before.

Really love the new ability to skeleton out your own navigation, super convenient and often requested.

I really like the new wrap ability especially how you can set this globally AND per component, it took me a litte while to get to grips with the docs for this but now I understand it i'm really into it. I also at first really wasn't a fan of not actually having a preview.html file, however I can now see why and think this is really great.

Scenarios

As mentioned I don't think I'm completely sold on the new name, my main feedback for this would be that the previous 'variant' naming lent itself way more to something like a 'primary' or 'secondary' button and directly correlated to something like a 'modifier' in BEM world. I can see how something like 'scenario' is way less restrictive though, just thought i'd give my initial thoughts.

Moving away from the name, I really like the new direction, rendering multiple scenarious per instance is my favourite addition, it's a quick and easy way to test out / show different content without having to make a weird pseudo variant. As mentioned I also love the name change from context to props, definitely sold on that. It'd be really cool if we could do Vue style required checks for component props, or optionally be able to pass in an object etc, rather than just a string.

One thing that slightly confused me in the docs and within the nunjucks example, with a component do you not now set a 'base' component? In the example the Button component has 2 scenarios, next and prev, however there's no default 'Button' props - in adapter integration section it explains it includes support for rendering sub-components (https://github.com/frctl/fractalite#adapter-integration) - however, the example doesn't actually have a base button config so it's a little confusing.

View templates

Not too much to say here, like the change to having a 'view' file, feels like a nicer split between view and data.

Pages

Really into this change, I personally think pages felt a little cumbersome before and removing the markdown requirement is a really good change IMO. Reference tags feel like a really nice upgrade from the previous methods too.

One question I had was this part of the docs (https://github.com/frctl/fractalite#nunjucks-templates) epxlained how you have access to the current compiler state, this is awesome, I was wondering if there's any plans to potentially be more granular with the state, as in, how hard would it be to do something like:

{% for component in components.units %}
{% endfor %}

to just loop over the components within the 01-units folder. That would be a really awesome feature and make building 'kitchen sink' style overview pages super easy.

Application UI

Not too much to say here, as peviously mentioned, the updates to the nav config are very welcome and super robust, really like this. The application feels clean out the box and as mentioned feels really snappy. Like the new cleaner out the box styles.

Themeing

Again, very welcome addition, I always found the current themeing method a bit of a pain and this feels lots nicer. Some feedback regarding the theme variables, it'd be nice if these where documented somewhere as you currenty have to find them in the theme style file. Obviously this is something that's probably already apparent, just thought it was worth mentioning. Didn't have time to dig into template overrides, but the method of configuring them seems way way easier than before.

Plugins

New plugins seem great, didn't have too much time to dig into this but from what i've seen, being able to easily create your own panels is something that's been requested a bunch of times so think that'll definitely be a hit.

The asset bundler plugin is a nice addition, quite often we have people asking about asset compilation for Fractal and don't immediately realise there isn't anything currently out the box for this, I think this will be a hugely welcome addition.

Initial feedback

Initial thoughts

Wow, way to go @allmarkedup, very impressive what you have done so far!

Did you have any difficulties getting the prototype demo up and running?

No, works like a charm. I'm also happily surprised with the documentation so far.

Were your initial gut feelings about the prototype direction positive or negative?

Very positive. Simpler to setup, more flexible and powerful.

Do you think this prototype represents a good development direction for the next version of Fractal?

Yes, definitely.

Components

v1-style 'single file components' are no longer supported. Does that pose any difficulties for you?

I'm not sure what you mean by 'single file components', so I guess not. ;) Do you mean a component should now always have a config-file associated with it? Having a single view template in a folder still seems to work.

Do you have any thoughts on the naming of configuration properties?

Props is definitely better than context data. Your rationale to use ‘scenarios’ over ‘variants’ makes sense, so I’d say let’s stick with it.

Only thing that made me raise an eyebrow was the term ‘front matter’ that I had never heard before (even though I've used it). The first paragraph in the Jekyll docs explains it clearly. Shall we add something similar or a simple code example to the doc?

Adapters

Which template engines/frameworks would you most like to see integration with? (2 - 3 max)

  • DustJS
  • HAML
  • Handlebars
  • Marko
  • Mustache
  • Nunjucks
  • Pug
  • React
  • Twig
  • Vue
  • Other - please specify below

I think it'd be wise to also support React since it's so popular.

For developers...

Does the plugin model make sense to you? Any questions/queries about it?

I've managed to get the example author-info plugin to work and it all seems straightforward. But I must admit I need to dive into the architecture some more to discover the possibilities.

Are there customisations that you'd like to make that you think might not be possible in the current plugin system implementation?

Maybe, see below my notes on the nice to have's.

Do you think the monorepo approach is a good route future Fractal development?

Yes, definitely!

Other

Is there anything missing from the prototype that currently exists in v1 and that you think should be part of the core of any future versions (i.e. not implemented as a plugin)?

  • Unrendered view template (as suggested by Benoît #10)
  • Statuses? But maybe better as a plugin.

Any other comments/suggestions/ideas?

👇

Design system ‹ › Component library

I primarily have been looking at the prototype from a front-end/designers perspective with an interest in using Fractal as a design system tool. I think v2 has great potential to become the open-source tool to quickly get (the technical part of) a design system up and running. V1 missed some flexibility, especially in terms of customizing the component UI. For me this mainly had to do with the separation between documentation and component pages, where in design systems the components are usually part of a documentation page (including its variants, ahum… scenario’s ;)). To illustrate the difference between a design system and a component library, compare Carbon’s and Gov.uk with Fractal v1 and Storybook’s way of presenting a button. As a documentation tool the ‘design system’ is preferred, while working on components the latter view is. This is why I was excited about the idea of the ‘workbench’ view vs. the built ‘presentation’ view that was outlined earlier. Is that still part of the general idea? Maybe this is possible since the overall setup is more flexible, but I can’t connect the dots yet on how to achieve this.

Just a few of the things I like

  • Easy to setup
  • Multiple scenario previews
  • Search aliases for components
  • The way the nav is build and can be customized easily
  • Reference tags
  • Being able to (optionally) run Markdown through the Nunjucks parser and vice versa is really handy. Works great in my tests so far.
  • The new preview features are great! Wrap(), wrapEach(), inject js and css. It might clutter the config-file, but I guess you can always import if necessary.
  • The auto-rewriting of url’s

Nice to have’s

  • Reference tag for rendering the components iframe (so that we’re able to create more dynamic documentation pages). Like {iframe:button/next}/{include:button/next}/{render:button/next} or whatever we want to call it.
  • That you can’t click on the top level component when there are multiple scenario’s/variants has always kinda bugged me.
    • It’s not possible to directly open the component when there are children (it then expands the tree). How about opening the default scenario + expand the tree when the title is clicked? This would save a click.
  • I see a lot of design systems having only top-level components documentation pages where all variants are rendered onto this page, like fe. Carbon. How about an extra optional view file specifically for component documentation purposes? Like having an optional *.page.*-file besides a *.view.*-file in the component folder which is used as a component documentation page (in which you could include the component scenario’s with the relevant documentation yourself as in previous sub-bullet). I’m not sure if my explanation is clear, but I think this could be huge for component documentation pages as you often see in design systems.

*Do you know about the props editor in Storybook called ‘knobs’ (see here)? I haven’t used it myself and could totally live without, but I’m just curious.

Vue demo is broken on windows

Reported by mihkeleidast in the slack channel - "Something (prettier?) is removing slashes in src/assets/preview.js from the imports"

Feature request: Write access for plugins?

As suggested in #15:

"It also might be interesting to consider if there's merit in a standard pattern for plugins to feed data back into a running Fractal server instance. This could be pretty simple, e.g. allowing plugins to register for a hook, and then providing an endpoint that accepts (say) something like a component handle, a plugin handle and an arbitrary payload that is received by the plugin fn registered to the hook.

A couple of example workflows that I can think of that could make use of something like this:

Design review: Our design team will often use our Fractal instances to design review components while they're in dev. It would be great to be able to update component metadata / config to store data about that process in the component's config file, e.g. storing a timestamp or updating component status after a designer has hit a "complete review" button in the fractal UI.

In-browser prop editor: Being able to fiddle about with prop values live in the browser a la Storybook Knobs would be great, but to get it working for SSR templates like Twig you would need a way to tell Fractal to re-render a given component with a revises set of prop values. See also my FR for typed props or similar below.

To be clear, I'm suggesting Fractalite should just provide the hook for plugins to register on and endpoint to invoke the hook, and leave everything else to plugins."

Error: Cannot find module '@frctl/fractalite-support'

Hi,

I followed the instructions from the README.md file but got this error when running npm run demo.

Node version: v10.15.2
NPM version: v6.4.1

Steps to reproduce:

npm install
npm run bootstrap
npm run demo

I tried to install: npm install -g @frctl/fractalite-support but it does not exist.

1st feedback

Initial thoughts

Did you have any difficulties getting the prototype demo up and running?

Nope, all good so far.

Were your initial gut feelings about the prototype direction positive or negative?

Very positive!

Do you think this prototype represents a good development direction for the next version of Fractal?

I'm amazed by the amount of work done so far and the overall direction it takes. It brings a lot of tiny missing features on the UI while allowing deeper customization everywhere. I'm sure it will be very appreciated!

Components

Did you like the @name folder name convention for components?

Yes but I can live without it.

v1-style 'single file components' are no longer supported. Does that pose any difficulties for you?

No, I've almost always ended-up creating directories for all of them.

Do you have any thoughts on the naming of configuration properties?

  • “Props” is better and closer to what people are used to in React or Vue communities.
  • “Scenarios” is probably slightly better than ”Variants”

Adapters

Which template engines/frameworks would you most like to see integration with? (2 - 3 max)

  • DustJS
  • HAML
  • Handlebars
  • Marko
  • Mustache
  • Nunjucks
  • Pug
  • React
  • Twig
  • Vue
  • Other - please specify below

For developers...

Does the plugin model make sense to you? Any questions/queries about it?

The plugins system looks amazing; I can imagine a Vue plugin where you could use vue-docgen-api to read the Vue component and automatically document all the methods, props, etc. in a panel; just like Vue Styleguidist does.

Are there customisations that you'd like to make that you think might not be possible in the current plugin system implementation?

Not that I can think of right now.

Does the Adapter system implementation make sense you you?

Yep, nothing wrong I've seen so far.

Do you think the monorepo approach is a good route future Fractal development?

Hell yes!

Other

Changes & features I enjoy so far:

  • “Standard” configuration file that exports an object
  • The performance of the UI, it's super fast!
  • The search of course, a long-time awaited feature
  • The indication of the preview container dimensions (had to implement that manually on some projects already)
  • The way you define where components are, feels closer to what Styleguidist do for example, it will definitely help keeping components assets and styleguide-related files together.
  • Auto-rewrite of assets path (so many support request about the path helper currently)
  • Being able to link to other components/pages/…
  • Full navigation customization (we often want the display the Changelog and a link to the repository)
  • Granualar theme customization and not just a limited set of predefined colors, at the moment this requires quite some stuff to be done.
  • Assets bundler with auto-injection
  • The ability to customize where the notes comes from, a pretty often requested feature from the community
  • Fractal-owned Vue adapter

Things I'd love to see happen in the future

  • Being able to specify options to the assets bundler plugin (such as global to expose the bundle as UMD)
  • The ability to use multiple adapters in the same project

Is there anything missing from the prototype that currently exists in v1 and that you think should be part of the core of any future versions (i.e. not implemented as a plugin)?

  • Components tags? That could be helpful for the search.
  • "View" panel that displays the original template? It's often preferred to the HTML panel by our developers.

Any other comments/suggestions/ideas?

  • There's no more default variant/scenario, right?
  • It says preview markup/css/js can be customized globally or per-component. But what about collections? In the past I've usually had the default defined globally and then wanted variants for a whole collection (like "Example pages" where you want to remove the body padding for all components for example).

Finally a big thanks for everything you did so far! You can count me in to help you improve the theme/UI, write some docs and potentially create some plugins related to Vue.

Feedback

Hi Mark,

Really like the way Fractalite is shaping up. I work with @samuelgoddard so not going to repeat everything he said in #3, as I essentially agree with him. I do have a couple of specific FRs for consideration though (see below)

Initial thoughts

Were your initial gut feelings about the prototype direction positive or negative?

Very positive.

Do you think this prototype represents a good development direction for the next version of Fractal?

Yes

Components

v1-style 'single file components' are no longer supported. Does that pose any difficulties for you?

Not for us, we always used the directory-per-component approach previously anyway.

Do you have any thoughts on the naming of configuration properties?

(For example, v1 'variants' and 'context data' config properties have been renamed to 'scenarios' and 'props' respectively. Are those names better/worse/the same?)

  • Props over config every time, that's how we internally talk about the context a component receives as a dev team anyway (though that could be because we do a lot of Vue stuff)
  • Scenarios over variants is more subjective. We've previously been using a BEM approach to which variants maps really nicely. But I'm actually coming round more to scenarios as being more flexible (esp with respect to just changing the content of a component rather than it's classes / markup pattern)

Adapters

Which template engines/frameworks would you most like to see integration with? (2 - 3 max)

  • DustJS
  • HAML
  • Handlebars
  • Marko
  • Mustache
  • Nunjucks
  • Pug
  • React
  • Twig
  • Vue
  • Other - please specify below

For developers...

Are there customisations that you'd like to make that you think might not be possible in the current plugin system implementation?

The current plugin implementation works really well for extending Fractal to present additional information.

It also might be interesting to consider if there's merit in a standard pattern for plugins to feed data back into a running Fractal server instance. This could be pretty simple, e.g. allowing plugins to register for a hook, and then providing an endpoint that accepts (say) something like a component handle, a plugin handle and an arbitrary payload that is received by the plugin fn registered to the hook.

A couple of example workflows that I can think of that could make use of something like this:

  • Design review Our design team will often use our Fractal instances to design review components while they're in dev. It would be great to be able to update component metadata / config to store data about that process in the component's config file, e.g. storing a timestamp or updating component status after a designer has hit a "complete review" button in the fractal UI.

  • In-browser prop editor. Being able to fiddle about with prop values live in the browser a la Storybook Knobs would be great, but to get it working for SSR templates like Twig you would need a way to tell Fractal to re-render a given component with a revises set of prop values. See also my FR for typed props or similar below.

To be clear, I'm suggesting Fractalite should just provide the hook for plugins to register on and endpoint to invoke the hook, and leave everything else to plugins.

Do you think the monorepo approach is a good route future Fractal development?

Personally I find it way easer to work with. I guess one thing to have regard to is keeping the overall bar to participation in the project low enough to encourage the widest range of useful contributions. A monorepo makes sense to me personally but might be confusing to devs that aren't familiar with lerna etc. This is probably just something that we can solve with docco tho.

Other

Is there anything missing from the prototype that currently exists in v1 and that you think should be part of the core of any future versions (i.e. not implemented as a plugin)?

1.Component statuses. May be on your list already (as it's in 1.x) but can't currently see it in the roadmap, so adding here.

Being able to set statuses per component is super useful. We do a lot of work where we're handling front end to back end teams, using Fractal our single source of truth for components, so being able to clearly label what's ready / WIP / deprecated is great.

Could be a plugin easily enough I guess, but I think there's a strong argument for putting them in core.

  1. Typed props. I saw you mentioned JSON schema support in #3 as a potential plugin idea, so this is essentially a +1 for that or similar. Some initial use cases I can think of:
  • Easier component tests!
  • Self documenting for back-end teams in terms of the contract the component expects when they're receiving components to integrate
  • Enabling a plugin for a Storybook Knobs style in-browser prop editor

Suggestion: Search by tags

Hi there,

Thanks for working on this prototype. I work on a 5-person UX team that is developing and maintaining a design system for our company. We use fractal currently, with some custom code for searching document titles. We saw that you've got Component Search on your list, and are super excited to see this functionality, and wanted to share a specific issue we had that might be worth addressing.

Because our documentation is intended for people new to our company as well as people who have been here a long time, but are just getting familiar with the system, we have encountered quite a few situations where people will know a slightly different name for a component than the one used in our system. For example: our applications use a lot of tabular data, but our term for these displays is "datagrid." If a brand-new team member comes to familiarize themselves with our documentation, they might search for"table," and get no results, as our search just scans title fields. I tried hiding strings, etc as a workaround, but our current implementation is just too narrow. So it'd be great if there were some way to have components show up for multiple "aliases" in the search without having to include something like "AKA ____" in the title, perhaps via a tag attribute or something.

All the best,
Adam

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.