Skip to content

Commit

Permalink
Merge pull request #59 from rtablada/rt/ast-for-nesting
Browse files Browse the repository at this point in the history
AST Transform for Nested Invocation with ::

Co-authored-by: Robert Jackson <[email protected]>
  • Loading branch information
rwjblue and rwjblue authored Apr 4, 2019
2 parents 6bf7e97 + 57061bd commit e6a6582
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 9 deletions.
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# ember-angle-bracket-invocation-polyfill

This addon provides a polyfill for angle bracket invocation syntax as described in
[RFC 311](https://github.com/emberjs/rfcs/pull/311). It's the same components you
know and love, no longer surrounded by mustaches. \o/
This addon provides a polyfill for angle bracket invocation syntax as described
in [RFC 311](https://github.com/emberjs/rfcs/pull/311) and [RFC
457](https://emberjs.github.io/rfcs/0457-nested-lookups.html). It's the same
components you know and love, no longer surrounded by mustaches. \o/

[![Build Status](https://travis-ci.org/rwjblue/ember-angle-bracket-invocation-polyfill.svg?branch=master)](https://travis-ci.org/rwjblue/ember-angle-bracket-invocation-polyfill)

Expand All @@ -16,7 +17,9 @@ You will additionally need to ensure ember-cli-htmlbars-inline-precompile is at

## Usage

The best usage guide is [the RFC itself](https://github.com/emberjs/rfcs/blob/master/text/0311-angle-bracket-invocation.md),
The best usage guide are the RFCs themselves
([emberjs/rfcs#311](https://emberjs.github.io/rfcs/0311-angle-bracket-invocation.html)
[emberjs/rfcs#457](https://emberjs.github.io/rfcs/0457-nested-lookups.html)),
but here are a few examples of "before"/"after" to whet your appetite:

**Before**:
Expand Down Expand Up @@ -116,8 +119,14 @@ but here are a few examples of "before"/"after" to whet your appetite:
<Title />
```

- Completely innert when running Ember 3.4 or higher
- Supports Ember 2.12, 2.16, 2.18, 3.1, 3.2, 3.3
- Supports invoking components nested in subfolders:

```
<Foo::Bar />
```

- Completely innert when running Ember 3.10 or higher
- Supports Ember 2.12, 2.16, 2.18, 3.1, 3.2, 3.3, 3.4, 3.8, 3.9
- Test all the features listed above 😘

## Limitations
Expand Down
19 changes: 19 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
let emberVersion = checker.forEmber();

this.shouldPolyfill = emberVersion.lt('3.4.0-alpha.1');
this.shouldPolyfillNested = emberVersion.lt('3.10.0-alpha.1');

let parentChecker = new VersionChecker(this.parent);
let precompileVersion = parentChecker.for('ember-cli-htmlbars-inline-precompile');
Expand All @@ -33,6 +34,14 @@ module.exports = {
params: {},
};
registry.add('htmlbars-ast-plugin', pluginObj);
} else if (this.shouldPolyfillNested) {
let pluginObj = this._buildNestedPlugin();
pluginObj.parallelBabel = {
requireFile: __filename,
buildUsing: '_buildPlugin',
params: {},
};
registry.add('htmlbars-ast-plugin', pluginObj);
}
},

Expand All @@ -46,6 +55,16 @@ module.exports = {
};
},

_buildNestedPlugin() {
return {
name: 'nested-component-invocation-support',
plugin: require('./lib/ast-nested-transform'),
baseDir() {
return __dirname;
},
};
},

included() {
this._super.included.apply(this, arguments);

Expand Down
90 changes: 90 additions & 0 deletions lib/ast-nested-transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict';

const reLines = /(.*?(?:\r\n?|\n|$))/gm;
const ALPHA = /[A-Za-z]/;

class AngleBracketPolyfill {
constructor(options) {
this.syntax = null;
this.sourceLines = options.contents && options.contents.match(reLines);
}

transform(ast) {
let b = this.syntax.builders;

// in order to debug in https://https://astexplorer.net/#/gist/0590eb883edfcd163b183514df4cc717
// **** copy from here ****

function dasherize(string) {
return string.replace(/[A-Z]/g, function(char, index) {
if (index === 0 || !ALPHA.test(string[index - 1])) {
return char.toLowerCase();
}

return `-${char.toLowerCase()}`;
});
}

let rootProgram;
let letBlock;
let yieldedComponents = new Map();

function ensureLetWrapper() {
if (!letBlock) {
letBlock = b.block('let', [], b.hash([]), b.program(rootProgram.body), null, null);
rootProgram.body = [letBlock];
}
}

let counter = 0;
function localNameForYieldedComponent(tag) {
let localName = yieldedComponents.get(tag);
if (!localName) {
localName = tag.replace(/::/g, '') + '_ANGLE_' + counter++;
let transformedPath = dasherize(tag.replace(/::/g, '/'));

let positionalArg = b.sexpr(b.path('component'), [b.string(transformedPath)]);
letBlock.params.push(positionalArg);
letBlock.program.blockParams.push(localName);

yieldedComponents.set(tag, localName);
}

return localName;
}

let visitor = {
// supports [email protected]
Template(node) {
rootProgram = node;
},

// supports glimmer-vm < 0.39
Program(node) {
// on older ember versions `Program` is used for both the "wrapping
// template" and for each block
if (!rootProgram) {
rootProgram = node;
}
},

ElementNode(node) {
let tag = node.tag;

if (tag.indexOf('::') !== -1) {
ensureLetWrapper();

let localName = localNameForYieldedComponent(tag);
node.tag = localName;
}
},
};
// **** copy to here ****

this.syntax.traverse(ast, visitor);

return ast;
}
}

module.exports = AngleBracketPolyfill;
11 changes: 8 additions & 3 deletions lib/ast-transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class AngleBracketPolyfill {
});
}

function replaceNestedComponents(string) {
return string.replace(/::/g, '/');
}

function isSimple(mustache) {
return mustache.params.length === 0 && mustache.hash.pairs.length === 0;
}
Expand Down Expand Up @@ -142,7 +146,8 @@ class AngleBracketPolyfill {
let selfClosing = getSelfClosing(element);
let hasAttrSplat = element.attributes.find(n => n.name === '...attributes');
let dasherizedComponentName = dasherize(tag);
let singleWordComponent = dasherizedComponentName.indexOf('-') === -1;
let nestedComponentName = replaceNestedComponents(dasherizedComponentName);
let singleWordComponent = nestedComponentName.indexOf('-') === -1;

if (isLocal || isNamedArgument || isThisPath) {
let path = b.path(tag);
Expand All @@ -160,7 +165,7 @@ class AngleBracketPolyfill {
hasAttrSplat,
};
} else if (isUpperCase && singleWordComponent) {
let path = b.string(dasherizedComponentName);
let path = b.string(nestedComponentName);

return {
kind: 'DynamicComponent',
Expand All @@ -171,7 +176,7 @@ class AngleBracketPolyfill {
} else if (isUpperCase) {
return {
kind: 'StaticComponent',
componentName: dasherizedComponentName,
componentName: nestedComponentName,
selfClosing,
hasAttrSplat,
};
Expand Down
33 changes: 33 additions & 0 deletions tests/integration/components/angle-bracket-invocation-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,39 @@ module('Integration | Component | angle-bracket-invocation', function(hooks) {
assert.dom('h2').hasText("rwjblue's component");
assert.dom('p').hasText('Contents');
});

test('nested paths do not conflict with non-nested paths with similar names', async function(assert) {
this.owner.register('template:components/foo/bar', hbs`hi rwjblue!`);
this.owner.register('template:components/foo-bar', hbs`hi rtablada!`);

await render(hbs`
<Foo::Bar data-foo="bar"/>
<FooBar data-foo="baz" />
`);

assert.dom('[data-foo="bar"]').hasText('hi rwjblue!');
assert.dom('[data-foo="baz"]').hasText('hi rtablada!');
});

test('invoke nested path', async function(assert) {
this.owner.register('template:components/foo/bar', hbs`hi rwjblue!`);

await render(hbs`
<Foo::Bar data-foo="bar"/>
`);

assert.dom('[data-foo="bar"]').exists();
});

test('invoke deeply nested path', async function(assert) {
this.owner.register('template:components/foo/bar/baz/qux', hbs`hi rwjblue!`);

await render(hbs`
<Foo::Bar::Baz::Qux data-foo="bar"/>
`);

assert.dom('[data-foo="bar"]').exists();
});
});

module('dynamic component support', function() {
Expand Down

0 comments on commit e6a6582

Please sign in to comment.