Giter Club home page Giter Club logo

ember-ast-helpers's Introduction

ember-ast-helpers

This library is a utility belt to make AST transforms and shield users as much as possible from the nuances of the AST, as it is still private API.

Helpers

BuildTimeComponent

This class is the main interface users should use. It gives you a nice declarative way of defining complex transforms from curly components to HTMLElements that resembles Ember.Component in its API.

Basic API

The basic usage is simple:

let component = new BuildTimeComponent(node);
component.toNode()

This alone mimics the behaviour of Ember.Component in some ways:

  • Generates a div element
  • Binds the class= attribute received on invocation to the class on the element.

It also accepts an object with options to configure it, much like Ember.Component.extend(opts):

let component = new BuildTimeComponent(node, {
  tagName: 'span',
  classNames: ['my-component'],
  classNameBindings: ['isActive:is-active:is-disabled'],
  attributeBindings: ['title', 'ariaLabel:aria-label'],
  isActive: true
});
component.toNode()

This will be smart enough to generate the appropriate transformations:

Original Transformed
{{my-component class="simple-example"}} <span class="my-component is-active simple-example"></span>
{{my-component class=someClass}} <span class="my-component {{someClass}}"></span>
{{my-component class="simple-example" isActive=false}} <span class="my-component simple-example"></span>
{{my-component class="simple-example" isActive=isActive}} <span class="my-component {{if isActive 'is-active' 'is-disabled'}} simple-example"></span>
{{my-component class="simple-example" title="Hello" ariaLabel="World"}} <span class="my-component is-active simple-example" title="Hello" aria-label="World"></span>
{{my-component class="simple-example" title=title}} <span class="my-component is-active simple-example" title={{title}}></span>

Creating your own components

Just as you'd expect from Ember.Component, you can subclass BuildTimeComponent to configure it once and reuse it many times, all in a nice ES6 syntax. And classNames, classNameBindings and attributeBindings work as concatenated properties.

class MyComponent extends BuildTimeComponent {
  constructor(node, { tagName = 'span', isActive = true, ...rest }) {
    super(node, { tagName, isActive, ...rest });
    this.classNames = ['my-component'];
    this.classNameBindings = ['isActive:is-active:is-disabled'];
    this.attributeBindings = ['title', 'ariaLabel:aria-label'];
  }
}

In the future once Class properties are implemented (Stage 3 right now) you will be able to DRY up the code above:

// IMPORTANT, THE CODE BELOW DOES NOT WORK YET UNLESS YOU TRANSPILE IT
class MyComponent extends BuildTimeComponent {
  tagName = 'span'
  classNames = ['my-component']
  classNameBindings = ['isActive:is-active:is-disabled']
  attributeBindings = ['title', 'ariaLabel:aria-label']
  isActive = true
}

What about classes/attributes that cannot be expressed with simple bindings? For that, you can declare functions named <propName>Content and that function will win over runtime options, extension options or invocation options.

To clarify that, you need to understand that there is 4 ways components can get their title:

class Foo extends BuildTimeComponent {
  super(node, opts) {
    super(node, opts);
    this.title = 'Extension time title';
  },

  titleContent() {
    return "Computed title";
  }
}
let component = new Foo(node, { title: 'Initialization-time title' });
  {{my-foo title="Runtime title"}}

The precedence rules are:

  1. <propName>Content(){ } wins over everything. More on this later.
  2. In its absence, the runtime argument ({{my-foo propName="value"}}) wins.
  3. In the absence of both, the options passed when the component is instantiated (new Foo(node, { propName: 'value' })) wins.
  4. Lastly if none is provided, the default value when the class is defined is applied.

<propName>Content(){ } and how to use it

You just read above that the method <propName>Content wins over absolutely any other way the user has to provide <propName> to the component. However, typically you will compute the value of a property based on some inputs. Perhaps the runtime arguments, perhaps the init options, or perhaps all of them.

Within this method, you can access all those values:

  1. this.<propName> for values assigned with this.<propName> inside the constructor
  2. this.options.<propName> for values passed on the initialization (new Foo(node, { propName: 'value' }))
  3. this.attrs.<propName> for values passed on the template ({{my-foo propName=value}})

PositionalParams

Unlike Ember.Component, positional params can be listed without reopening the class

class MyLink extends BuildTimeComponent {
  constructor(node, { tagName = 'span', isActive = true, ...rest }) {
    super(node, { tagName, isActive, ...rest });
    this.positionalParams = ['url', 'text'];
    this.attributeBindings = ['url:href', 'text:aria-label'];
  }
}
// {{my-link "foo/bar.html" "About us"}}

Layout

Like regular components, build-time components can define their own layout, using a similar approach to the one used in ember-cli-htmlbars-inline-precompile. BuildTimeComponents are smart enough to detect if the component is invoked with or without a block, and simplify conditionals for you, and replace arguments in the templates with the equivalent values in the parent scope.

P.e, Given a component with a layout like this:

class MyComponent extends BuildTimeComponent {
  constructor(node: BuildTimeComponentNode, opts?: Partial<BuildTimeComponentOptions>) {
    super(node, opts);
    this.layout`
      <span>
        {{other-component thing=value}}
        {{#if hasBlock}}
          {{yield}}
        {{else}}
          <i>Default</i>
          <i>Content</i>
        {{/if}}
      </span>
      <strong>Other content for {{world}}</strong>
    `
  }

When it is invoked like this:

{{my-component world=planet value="Dog"}}

Then it compiles down to:

<div>
  <span>
    {{other-component thing="Dog"}}
      <i>Default</i>
      <i>Content</i>
  </span>
  <strong>Other content for {{planet}}</strong>
</div>

NOTE ON TEMPLATES: It is important to note that since BuildTimeComponents are transformed in compile time, the template of this kind of components is going to be inlined as many times as times the component is invoked. If the template is very large and the component is used very often, this can have a negative impact on the size of the application. Although this kind of repetition compresses very well with gzip, have this in mind if your component has a large template.

Other helpers

  • buildAttr(builder, attributeName, content) => AttrNode: Content can be pretty much anything. JS Strings, StringLiterals, TextNodes, PathExpression, ConcatStatements ... Just pass things down, it will do the right thing.

  • appendToContent(builder, content, dataToAppend, options) => newContent: It takes cares of the nuances of joining content together. It can be used by example to construct the content of an attribute like class from several pieces. It accepts pretty much anything. By default it adds a space between values, but that can be changed passing prependSpace: false on the options.

  • interpolateProperties(interpolation: string, { divisor = ':', skipIfMissing = true, skipIfMissingDynamic = false, callback }): A convenient method to generate interpolate values into strings. It accepts an object with options. P.e: styleContent: interpolateProperties('color: $color$; background-color: $bgColor$')

ember-ast-helpers's People

Contributors

cibernox avatar deepflame avatar ef4 avatar joukevandermaas avatar samselikoff avatar turbo87 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

ember-ast-helpers's Issues

Optional template part

I want to do something like:

this.layout`
{{#if title}}
  <title>{{title}}</title>
{{/if}}
<path d={{iconPath}}></path>`

For our layout, but this doesn't ever display the <title> tag.

Removing the if block I'm able to do:

this.layout`
<title>{{title}}</title>
<path d={{iconPath}}></path>`

but then I'm left with an empty <title></title> when the title isn't set.

Is this possible to do with these helpers or do I need to find a different path?

Cannot find module '@glimmer/syntax' with 0.4

Latest published seems to be 0.4, but it throws error:

Cannot find module '@glimmer/syntax'

Downgrading to 0.3.5 seemed to get me going.

Should we make 0.4 a pre-release? I couldn't find it in Github.

Let me know if I can help!

How do I import BuildTimeComponent?

I tried copying this line from Ember Font Awesome

const { default: BuildTimeComponent, interpolateProperties } = require('ember-ast-helpers/build-time-component');

but I got an error

Cannot find module 'ember-ast-helpers/build-time-component'

and my node-fu is extremely weak.

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.