{"id":13804348,"url":"https://github.com/cibernox/ember-ast-helpers","last_synced_at":"2025-05-07T13:01:30.564Z","repository":{"id":46938726,"uuid":"101108129","full_name":"cibernox/ember-ast-helpers","owner":"cibernox","description":"Utility belt to level-up your Ember AST transforms","archived":false,"fork":false,"pushed_at":"2022-12-06T19:51:00.000Z","size":487,"stargazers_count":40,"open_issues_count":21,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-18T07:58:39.548Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/cibernox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-22T21:17:27.000Z","updated_at":"2020-10-23T22:44:53.000Z","dependencies_parsed_at":"2023-01-23T15:00:10.556Z","dependency_job_id":null,"html_url":"https://github.com/cibernox/ember-ast-helpers","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cibernox%2Fember-ast-helpers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cibernox%2Fember-ast-helpers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cibernox%2Fember-ast-helpers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cibernox%2Fember-ast-helpers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cibernox","download_url":"https://codeload.github.com/cibernox/ember-ast-helpers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252883219,"owners_count":21819157,"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-08-04T01:00:46.298Z","updated_at":"2025-05-07T13:01:30.453Z","avatar_url":"https://github.com/cibernox.png","language":"TypeScript","funding_links":[],"categories":["Packages"],"sub_categories":["AST"],"readme":"# ember-ast-helpers\n\nThis library is a utility belt to make AST transforms and shield users as much as possible from\nthe nuances of the AST, as it is still private API.\n\n\n\n## Helpers\n\n### BuildTimeComponent\n\nThis class is the main interface users should use. It gives you a nice declarative way of defining\ncomplex transforms from curly components to HTMLElements that resembles `Ember.Component` in its\nAPI.\n\n#### Basic API\n\nThe basic usage is simple:\n```js\nlet component = new BuildTimeComponent(node);\ncomponent.toNode()\n```\n\nThis alone mimics the behaviour of `Ember.Component` in some ways:\n- Generates a `div` element\n- Binds the `class=` attribute received on invocation to the class on the element.\n\nIt also accepts an object with options to configure it, much like `Ember.Component.extend(opts)`:\n\n```js\nlet component = new BuildTimeComponent(node, {\n  tagName: 'span',\n  classNames: ['my-component'],\n  classNameBindings: ['isActive:is-active:is-disabled'],\n  attributeBindings: ['title', 'ariaLabel:aria-label'],\n  isActive: true\n});\ncomponent.toNode()\n```\n\nThis will be smart enough to generate the appropriate transformations:\n\n| Original                                       | Transformed                          |\n|------------------------------------------------|--------------------------------------|\n| `{{my-component class=\"simple-example\"}}`      | `\u003cspan class=\"my-component is-active simple-example\"\u003e\u003c/span\u003e` |\n| `{{my-component class=someClass}}`      | `\u003cspan class=\"my-component {{someClass}}\"\u003e\u003c/span\u003e` |\n| `{{my-component class=\"simple-example\" isActive=false}}`      | `\u003cspan class=\"my-component simple-example\"\u003e\u003c/span\u003e` |\n| `{{my-component class=\"simple-example\" isActive=isActive}}`   | `\u003cspan class=\"my-component {{if isActive 'is-active' 'is-disabled'}} simple-example\"\u003e\u003c/span\u003e` |\n| `{{my-component class=\"simple-example\" title=\"Hello\" ariaLabel=\"World\"}}`   | `\u003cspan class=\"my-component is-active simple-example\" title=\"Hello\" aria-label=\"World\"\u003e\u003c/span\u003e` |\n| `{{my-component class=\"simple-example\" title=title}}`   | `\u003cspan class=\"my-component is-active simple-example\" title={{title}}\u003e\u003c/span\u003e` |\n\n#### Creating your own components\nJust as you'd expect from `Ember.Component`, you can subclass `BuildTimeComponent` to configure it\nonce and reuse it many times, all in a nice ES6 syntax. And `classNames`, `classNameBindings` and\n`attributeBindings` work as concatenated properties.\n\n```js\nclass MyComponent extends BuildTimeComponent {\n  constructor(node, { tagName = 'span', isActive = true, ...rest }) {\n    super(node, { tagName, isActive, ...rest });\n    this.classNames = ['my-component'];\n    this.classNameBindings = ['isActive:is-active:is-disabled'];\n    this.attributeBindings = ['title', 'ariaLabel:aria-label'];\n  }\n}\n```\n\nIn the future once Class properties are implemented (Stage 3 right now) you will be able to DRY up the\ncode above:\n\n```js\n// IMPORTANT, THE CODE BELOW DOES NOT WORK YET UNLESS YOU TRANSPILE IT\nclass MyComponent extends BuildTimeComponent {\n  tagName = 'span'\n  classNames = ['my-component']\n  classNameBindings = ['isActive:is-active:is-disabled']\n  attributeBindings = ['title', 'ariaLabel:aria-label']\n  isActive = true\n}\n```\n\n\nWhat about classes/attributes that cannot be expressed with simple bindings?\nFor that, you can declare functions named `\u003cpropName\u003eContent` and that function\nwill win over runtime options, extension options or invocation options.\n\nTo clarify that, you need to understand that there is 4 ways components can get their title:\n```js\nclass Foo extends BuildTimeComponent {\n  super(node, opts) {\n    super(node, opts);\n    this.title = 'Extension time title';\n  },\n\n  titleContent() {\n    return \"Computed title\";\n  }\n}\nlet component = new Foo(node, { title: 'Initialization-time title' });\n```\n```hbs\n  {{my-foo title=\"Runtime title\"}}\n```\n\nThe precedence rules are:\n\n1) `\u003cpropName\u003eContent(){ }` wins over everything. More on this later.\n2) In its absence, the runtime argument (`{{my-foo propName=\"value\"}}`) wins.\n3) In the absence of both, the options passed when the component is instantiated (`new Foo(node, { propName: 'value' })`) wins.\n4) Lastly if none is provided, the default value when the class is defined is applied.\n\n#### `\u003cpropName\u003eContent(){ }` and how to use it\n\nYou just read above that the method `\u003cpropName\u003eContent` wins over absolutely any other way the user\nhas to provide `\u003cpropName\u003e` to the component. However, typically you will compute the value of a property\nbased on some inputs. Perhaps the runtime arguments, perhaps the init options, or perhaps all of them.\n\nWithin this method, you can access all those values:\n\n1) `this.\u003cpropName\u003e` for values assigned with `this.\u003cpropName\u003e` inside the constructor\n2) `this.options.\u003cpropName\u003e` for values passed on the initialization (`new Foo(node, { propName: 'value' })`)\n3) `this.attrs.\u003cpropName\u003e` for values passed on the template (`{{my-foo propName=value}}`)\n\n#### PositionalParams\n\nUnlike `Ember.Component`, positional params can be listed without reopening the class\n\n```js\nclass MyLink extends BuildTimeComponent {\n  constructor(node, { tagName = 'span', isActive = true, ...rest }) {\n    super(node, { tagName, isActive, ...rest });\n    this.positionalParams = ['url', 'text'];\n    this.attributeBindings = ['url:href', 'text:aria-label'];\n  }\n}\n// {{my-link \"foo/bar.html\" \"About us\"}}\n```\n\n### Layout\n\nLike regular components, build-time components can define their own layout, using a similar approach\nto the one used in `ember-cli-htmlbars-inline-precompile`.\n`BuildTimeComponent`s are smart enough to detect if the component is invoked with or without a block,\nand simplify conditionals for you, and replace arguments in the templates with the equivalent values\nin the parent scope.\n\nP.e, Given a component with a layout like this:\n```js\nclass MyComponent extends BuildTimeComponent {\n  constructor(node: BuildTimeComponentNode, opts?: Partial\u003cBuildTimeComponentOptions\u003e) {\n    super(node, opts);\n    this.layout`\n      \u003cspan\u003e\n        {{other-component thing=value}}\n        {{#if hasBlock}}\n          {{yield}}\n        {{else}}\n          \u003ci\u003eDefault\u003c/i\u003e\n          \u003ci\u003eContent\u003c/i\u003e\n        {{/if}}\n      \u003c/span\u003e\n      \u003cstrong\u003eOther content for {{world}}\u003c/strong\u003e\n    `\n  }\n```\n\nWhen it is invoked like this:\n\n```hbs\n{{my-component world=planet value=\"Dog\"}}\n```\n\nThen it compiles down to:\n\n```hbs\n\u003cdiv\u003e\n  \u003cspan\u003e\n    {{other-component thing=\"Dog\"}}\n      \u003ci\u003eDefault\u003c/i\u003e\n      \u003ci\u003eContent\u003c/i\u003e\n  \u003c/span\u003e\n  \u003cstrong\u003eOther content for {{planet}}\u003c/strong\u003e\n\u003c/div\u003e\n```\n\n**NOTE ON TEMPLATES**: It is important to note that since BuildTimeComponents are transformed in compile time,\nthe template of this kind of components is going to be inlined as many times as times the component is\ninvoked. If the template is very large and the component is used very often, this can have a negative impact\non the size of the application. Although this kind of repetition compresses very well with gzip, have this\nin mind if your component has a large template.\n\n### Other helpers\n\n- `buildAttr(builder, attributeName, content) =\u003e AttrNode`:  Content can be pretty much anything. JS Strings, `StringLiteral`s, `TextNode`s, `PathExpression`, `ConcatStatement`s ... Just pass things down, it will do the right thing.\n\n- `appendToContent(builder, content, dataToAppend, options) =\u003e 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.\n\n- `interpolateProperties(interpolation: string, { divisor = ':', skipIfMissing = true, skipIfMissingDynamic = false, callback })`: A convenient method to generate interpolate values into strings. It accepts an object with options.\nP.e: `styleContent: interpolateProperties('color: $color$; background-color: $bgColor$')`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcibernox%2Fember-ast-helpers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcibernox%2Fember-ast-helpers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcibernox%2Fember-ast-helpers/lists"}