JSPM

  • Created
  • Published
  • Downloads 143
  • Score
    100M100P100Q93969F
  • License MIT

Extracts, Renders And Exports For Dynamic Render JSX Components From Within HTML.

Package Exports

  • competent

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (competent) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

competent

npm version

competent is Extracts, Renders And Exports For Dynamic Render JSX Components From Within HTML.

yarn add -E competent

Table Of Contents

API

The package is available by importing its default function:

import competent from 'competent'

competent(
  components: Object,
  config?: Config,
): _restream.Rule

Creates a rule for Replaceable from the restream package that replaces HTML with rendered JSX components. The configuration object will be needed to export components, so that they can then be rendered on the page using JavaScript.

Example Usage
<html lang="en">

<npm-package style="background:red;">splendid</npm-package>
<npm-package style="background:green;">@a-la/jsx</npm-package>
<npm-package style="background:grey;">unknown-package</npm-package>

<hello-world from="Art Deco">
  An example usage of competent.
</hello-world>
<friends count="10"/>
</html>
For example, the above HTML page can be rendered with Competent by creating a Replaceable rule:
/* alanode example/ */
import competent from 'competent'
import aqt from '@rqt/aqt'
import read from '@wrote/read'
import { Replaceable } from 'restream'

/**
 * A standard JSX component.
 */
const HelloWorld = ({ from, children, competent: c }) => {
  c.setPretty(false)
  return (<p>Hello World From {from}.{children}</p>)
}

/**
 * A string component.
 */
const FriendCount = ({ count }) => {
  return `You have ${count} friends.`
}

/**
 * An async component.
 */
const NpmPackage = async ({ style, children, competent: c }) => {
  c.export()
  let [pck] = children
  pck = encodeURIComponent(pck)
  const { statusCode, body } =
    await aqt('https://registry.npmjs.com/' + pck)
  if (statusCode == 404) throw new Error(`Package ${pck} not found.`)
  const { name, versions, description } = body
  const keys = Object.keys(versions)
  const version = keys[keys.length - 1]
  return <div style={style}>
    <span className="name">{name}</span>
    <span className="ver">{version}</span>
    <p>{description}</p>
  </div>
}

const CompetentExample = async () => {
  let i = 0
  const exported = []
  const file = await read('example/index.html')
  const rule = competent({
    'hello-world': HelloWorld,
    'npm-package': NpmPackage,
    'friends': FriendCount,
  }, {
    getId() {
      i++
      return `c${i}`
    },
    getProps(props, meta) {
      meta.setPretty(true, 60)
      return { ...props, competent: meta }
    },
    onFail(key, err) {
      console.error('Component %s did not render:', key)
      console.error(err.message)
    },
    markExported(key, id, props, children) {
      exported.push({ key, id, props, children })
    },
  })
  const r = new Replaceable(rule)
  const res = await Replaceable.replace(r, file)
  return { res, exported }
}

export default CompetentExample
The output will contain rendered JSX.
<html lang="en">

<div style="background:red;" id="c1">
  <span class="name">splendid</span>
  <span class="ver">1.6.1</span>
  <p>Static Web Site Generator With JSX As HTML.</p>
</div>
<div style="background:green;" id="c2">
  <span class="name">@a-la/jsx</span>
  <span class="ver">1.6.0</span>
  <p>The JSX Transform For ÀLaMode And Other Packages.</p>
</div>
<npm-package style="background:grey;">unknown-package</npm-package>

<p>Hello World From Art Deco.
  An example usage of competent.
</p>
You have 10 friends.
</html>
The logging will be output to stderr.
Component npm-package did not render:
Package unknown-package not found.
Exported packages:
[ { key: 'npm-package',
    id: 'c1',
    props: { style: 'background:red;' },
    children: [ 'splendid' ] },
  { key: 'npm-package',
    id: 'c2',
    props: { style: 'background:green;' },
    children: [ '@a-la/jsx' ] } ]

Object<string, *> _competent.Props: The properties extracted from HTML and to be passed to the component for rendering.

_competent.Meta: Service methods for competent.

Name Type Description
export* function(boolean=) When called, marks the component for export and adds an id if the root element of the hyper result did not have it. Individual instances can pass the false value if they don't want to get exported.
setPretty* function(boolean, number=) The function which controls whether to enable pretty printing, and the line width.
renderAgain* function(boolean=) Render the result of the component again. This is needed when a self-closing component might contain other components when rendered. No recursion is allowed otherwise the program will be stuck.

_competent.Config: Options for the program. All functions will be called with the Replaceable instance as their this context.

Name Type Description Default
getId function(): string The function which returns an id for the html element. -
getProps function(!_competent.Props, !_competent.Meta) The function which takes the parsed properties from HTML and competent's meta methods, and returns the properties object to be passed to the component. By default, returns the properties simply merged with meta. -
markExported function(string, string, !_competent.Props, !Array<string>) If the component called the export meta method, this function will be called at the end of the replacement rule with its key, root id, properties and children as strings. -
removeOnError boolean If there was an error when rendering the component, controls whether the HTML should be be left on the page. false
onSuccess function(string) The callback at the end of a successful replacement with the component's key. -
onFail function(string, !Error, number, string) The callback at the end of failed replacement with the component's key, error object, position number and the string which was fed to the rule. -
getContext function(): !Object The function to be called to get the properties to set on the child Replaceable started to recursively replace inner HTML. This is needed if the root Replaceable was assigned some properties that are referenced in components. -

makeComponentsScript(
  components: Array<comps>,
  componentsLocation: string,
  includeH?: boolean,
  io?: boolean,
): string

Based on the exported components that were detected using the rule, generates a script for the web browser to dynamically render them with Preact.

import CompetentExample from './'
import { makeComponentsScript } from 'competent'

(async () => {
  const { exported } = await CompetentExample()
  console.log(makeComponentsScript(exported, '../components'))
})()
import { render } from 'preact'
import Components from '../components'

[{
  key: 'npm-package',
  id: 'c1',
  props: {
    style: 'background:red;',
  },
  children: ["splendid"],
},
{
  key: 'npm-package',
  id: 'c2',
  props: {
    style: 'background:green;',
  },
  children: ["@a-la/jsx"],
}]
  .map(({ key, id, props = {}, children }) => {
    const el = document.getElementById(id)
    if (!el) {
      console.warn('Parent element for component %s with id %s not found', key, id)
      return
    }
    const parent = el.parentElement
    if (!parent) {
      console.warn('Parent of element for component %s with id %s not found', key, id)
      return
    }
    const Comp = Components[key]
    if (!Comp) {
      console.warn('Component with key %s was not found.', key)
      return
    }

    render(h(Comp, props, children), parent, el)
  })

Intersection Observer

Competent can generate code that will utilise the IntesectionObserver browser capability to detect when the element into which the components needs to be rendered comes into view, and only mount it at that point. This will only work when IntesectionObserver is present either natively, or via a polyfill. When the io argument value is passed as a string, it will be set as the root margin, e.g., 0 0 76px 0. The other options are not available at the moment.

import CompetentExample from './'
import { makeComponentsScript } from 'competent'

(async () => {
  const { exported } = await CompetentExample()
  console.log(
    makeComponentsScript(exported, '../components', false, {}, true))
})()
import { render } from 'preact'
import Components from '../components'

function makeIo(rootMargin = '0px 0px 76px 0px') {
  const io = new IntersectionObserver((entries) => {
    entries.forEach(({ target, isIntersecting }) => {
      if (isIntersecting) {
        if (target.render) {
          console.warn('rendering component %s into the element %s ',
            target.render.meta.key, target.render.meta.id)
          target.render()
          io.unobserve(target)
        }
      }
    })
  }, { rootMargin })
  return io
}
const io = makeIo();[{
  key: 'npm-package',
  id: 'c1',
  props: {
    style: 'background:red;',
  },
  children: ["splendid"],
},
{
  key: 'npm-package',
  id: 'c2',
  props: {
    style: 'background:green;',
  },
  children: ["@a-la/jsx"],
}]
  .map(({ key, id, props = {}, children }) => {
    const el = document.getElementById(id)
    if (!el) {
      console.warn('Parent element for component %s with id %s not found', key, id)
      return
    }
    const parent = el.parentElement
    if (!parent) {
      console.warn('Parent of element for component %s with id %s not found', key, id)
      return
    }
    const Comp = Components[key]
    if (!Comp) {
      console.warn('Component with key %s was not found.', key)
      return
    }

    parent.render = () => {
      render(h(Comp, props, children), parent, el)
    }
    parent.render.meta = { key, id }
    io.observe(parent)
  })

Known Limitations

Currently, it is not possible to match nested components.

<Component>
  <Component example />
  <Component test boolean></Component>
</Component>
<component-processed />
</component>

This is because the RegExp is not capable of doing that sort of thing, because it cannot balance matches, however when Competent switches to a non-regexp parser it will become possible.

Who Uses Competent

Competent is used by:

  • Documentary: a documentation pre-processor that supports JSX for reusable components when generating README files.
  • Splendid: a static website generator that allows to write JSX components in HTML, and bundles JS compiler with Google Closure Compiler to also dynamically render them on the page.

Art Deco © Art Deco 2019 Tech Nation Visa Tech Nation Visa Sucks