cibernox / ember-cpm

ComputedProperty Macros for Ember
MIT License
276 stars 31 forks source link

Proposal: `template("hi ${name}")` #135

Closed GavinJoyce closed 8 years ago

GavinJoyce commented 8 years ago

As Ember.String.fmt is depreciated, it would be nice to define a CP macro with a similar syntax to ES6 Template Strings:

var teacher = Ember.Object.extend({
  name: 'Miguel',
  course: 'Javacript',
  catchPhrase: template('Hi, my name is ${name} and I will teach you ${course}')
}).create();

teacher.get('catchPhrase'); // 'Hi, my name is Miguel and I will teach you Javascript'

We'd parse the string to identify the computed property keys and build a function which would be optimized for generating the appropriate output

GavinJoyce commented 8 years ago

@cibernox I'd be happy to build this if you'd like to include it in ember-cpm. If not, I'll create an addon

rwjblue commented 8 years ago

I'm concerned (generally) that using the same syntax as template strings would lead to confusion. As a general user, I often use template strings interchangeably and in the example you gave if you had used back ticks instead of single quotes to surround the string it would have been evaluated at module parse time and not at runtime.

I think a better pattern would be:

import { computedString } from "ember-cpm";

// ... Snip ...

catchPhrase: computedString'Hi, my name is ${name} and I will teach you ${course}'

That template string function would expand to a CP (as you mentioned)...

PS: On iOS I can't type back ticks, please replace single quotes with backticks in my example above.

GavinJoyce commented 8 years ago

@rwjblue nice suggestion, :+1: on the computedString template string function.

GavinJoyce commented 8 years ago

@rwjblue I'm just reading up on tagged templates, it seems that:

fn`Hello ${you}! You're looking ${adjective} today!`

desugars into:

fn(["Hello ", "! You're looking ", " today!"], you, adjective);

meaning that we'll get runtime and jshint errors due to you and adjective not being defined. We also don't get access to the variable names, which we would need to build the CP dependant keys

Here's an example

Perhaps I'm missing something?

rwjblue commented 8 years ago

Look at ember-cli-htmlbars-inline-precompiler. It replaces the imported template string at build time. This technique should allow us to rewrite so that no special stuff is needed at runtime.

cibernox commented 8 years ago

I also think that the template precompiler is a nice trick that happens and build time. If it's doable I'd also prefer that.

GavinJoyce commented 8 years ago

Note to self, something like:

before:

import { computedString } from "ember-cpm";

var teacher = Ember.Object.extend({
  name: 'Miguel',
  course: {
    name: 'Javacript'
  },
  catchPhrase: computedString`Hi, my name is ${name} and I will teach you ${course.name}`
}).create();

after:

import { computedString } from "ember-cpm";

var teacher = Ember.Object.extend({
  name: 'Miguel',
  course: {
    name: 'Javacript'
  },
  catchPhrase: computedString('name', 'course.name', function() {
    var values = [
      this.get('name'), 
      this.get('course.name')
    ];

    return `Hi, my name is ${values[0]} and I will teach you ${values[1]}`;
  }
}).create();

or, without template strings:

import { computedString } from "ember-cpm";

var teacher = Ember.Object.extend({
  name: 'Miguel',
  course: {
    name: 'Javacript'
  },
  catchPhrase: computedString('name', 'course.name', function() {
    let templates = ['Hi, my name is ', this.get('name'), " and I will teach you ", this.get('course.name')];
    return templates.join();
  }
}).create();
GavinJoyce commented 8 years ago

I create a simple proof of concept addon, there are two notable files:

This works fine:

import Em from 'ember';

export default Em.Controller.extend({
  name: 'Gavin',
  greeting: computedString`hello ${name}` //this will be rewritten by the `BabelPluginSpike` in `/index.js`
});

image

One thing I'm not sure how to solve is the jshint warnings for the template string arguments though:

image

This seems to run before the babel plugin has had a chance to make its transform so don't think I can transform my way out of this (the transformed source should pass jshint without warning).

Any suggestions?

GavinJoyce commented 8 years ago

I built support for the string version of this in Intercom today, tests below. If we can't find a solution to the jshint issue, perhaps you would like to include this in ember-cpm? If not, I'll open source a focused addon

import Em from 'ember';
import Computed from 'embercom-computed-properties/computed';
import { module, test } from 'qunit';

module("Computed.templateString");

var Person = Em.Object.extend({
  name: 'Alex',
  age: 2,
  config: {
    path: '/home'
  },
  simple: Computed.templateString('this has no dependant keys, ok?'),
  greeting: Computed.templateString('Hello ${name}, you are ${age} years old'),
  nested: Computed.templateString('the path is ${config.path}, ok?'),
  multi: Computed.templateString('hi ${name}, bye ${name}')
});

test('A template with no properties', function(assert) {
  var person = Person.create();
  assert.equal(person.get('simple'), 'this has no dependant keys, ok?');
});

test('A template with two properties', function(assert) {
  var person = Person.create({ name: 'Alex', age: 2 });
  assert.equal(person.get('greeting'), 'Hello Alex, you are 2 years old');

  person.setProperties({ name: 'Ben', age: 1 });
  assert.equal(person.get('greeting'), 'Hello Ben, you are 1 years old');
});

test('A template with a nested property', function(assert) {
  var person = Person.create();
  assert.equal(person.get('nested'), 'the path is /home, ok?');

  person.set('config.path', '/garden');
  assert.equal(person.get('nested'), 'the path is /garden, ok?');
});

test('A template with a key used multiple times', function(assert) {
  var person = Person.create({ name: 'Ben' });
  assert.equal(person.get('multi'), 'hi Ben, bye Ben');

  person.set('name', 'Sarah');
  assert.equal(person.get('multi'), 'hi Sarah, bye Sarah');
});
GavinJoyce commented 8 years ago

I'll create a separate addon for this

cibernox commented 8 years ago

Thanks for the PR anyway. Will use the template strings?

GavinJoyce commented 8 years ago

Will use the template strings?

I don't think it's possible: https://github.com/cibernox/ember-cpm/issues/135#issuecomment-184807607

cibernox commented 8 years ago

It's a shame that jshint prevents that approach.

rwjblue commented 8 years ago

Agreed. Sorry for the extra work implementing only to find out it fails linting :(

GavinJoyce commented 8 years ago

I just released https://github.com/intercom/ember-computed-template-string