ember-cli / ember-template-imports

Template import support in Ember!
MIT License
74 stars 39 forks source link

Proxying components causes template to not render #243

Open evoactivity opened 2 months ago

evoactivity commented 2 months ago

With a co-located component it's possible to proxy the component and everything works as it should.

import Component from '@glimmer/component';

class MyComponent extends Component {
  world = 'World';
}

const ProxiedComponent = new Proxy(MyComponent, {});

export default ProxiedComponent;

When using a template tag, the template fails to render, no errors are logged.

import Component from "@glimmer/component";

class MyComponent extends Component {
  world = "World";
  <template>
    Hello {{this.world}}
  </template>
}

const ProxiedComponent = new Proxy(MyComponent, {});

export default ProxiedComponent;

In the generated code for the colocated component the setComponentTemplate is applied to the proxy.

export default setComponentTemplate(TEMPLATE, ProxiedComponent);

This doesn't appear to be the case for a template tag component.

static {
    setComponentTemplate(createTemplateFactory(
    /*

        <div class={{this.classList}}>
          <h1>Tests {{this.count}}</h1>
          <button type="button" {{on "click" this.handleClick}}>Click me</button>
        </div>

    */
    {
      "id": "Hg3FL3Xg",
      "block": "[[[1,\"\\n    \"],[10,0],[15,0,[30,0,[\"classList\"]]],[12],[1,\"\\n      \"],[10,\"h1\"],[12],[1,\"Tests \"],[1,[30,0,[\"count\"]]],[13],[1,\"\\n      \"],[11,\"button\"],[24,4,\"button\"],[4,[32,0,[\"on\"]],[\"click\",[30,0,[\"handleClick\"]]],null],[12],[1,\"Click me\"],[13],[1,\"\\n    \"],[13],[1,\"\\n  \"]],[],false,[]]",
      "moduleName": "/Users/liam/Work/ember-vite/my-fancy-app/app/components/my-component.gjs",
      "scope": () => [template__imports__],
      "isStrictMode": true
    }), this);
  }

Reproduction repo https://github.com/evoactivity/ember-proxy-template-tag/

NullVoxPopuli commented 2 months ago

Is the template supposed to have access to things on the proxy?

Which 'this' should refer to in a static initializer block?

evoactivity commented 2 months ago

Is the template supposed to have access to things on the proxy?

Not really, but I don't think it matters all that much.

Which 'this' should refer to in a static initializer block?

The static block is part of the component definition.

// this example is not from my repro repo, but it's easier to grab 
// the compiled source for a component from a vite enabled app

import { tracked } from "/node_modules/.vite/deps/@glimmer_tracking.js?v=d293af4a";
import { job, thing, that } from "/app/components/styles.module.css";
import { on } from "/node_modules/.vite/deps/ember-source_@ember_modifier_index__js.js?v=d293af4a";
import { setComponentTemplate } from "/node_modules/.vite/deps/ember-source_@ember_component_index__js.js?v=d293af4a";
import { createTemplateFactory } from "/node_modules/.vite/deps/ember-source_@ember_template-factory_index__js.js?v=d293af4a";

let template__imports__ = null;
template__imports__ = new class _Imports {
  static {
    dt7948.g(this.prototype, "on", [tracked], function () {
      return on;
    });
  }
  #on = (dt7948.i(this, "on"), void 0);
}()
const concat = (...args1) => args1.join(" ");
let MyComponent = class MyComponent extends Component {
  static {
    dt7948.g(this.prototype, "count", [tracked], function () {
      return 0;
    });
  }
  #count = (dt7948.i(this, "count"), void 0);
  classList = concat(job, thing, that);
  thing = 2;
  handleClick = () => {
    this.count += 1;
    this.thing += 1;
  };
  static {
    setComponentTemplate(createTemplateFactory(
    /*

        <div class={{this.classList}}>
          <h1>Tests {{this.count}}</h1>
          <button type="button" {{on "click" this.handleClick}}>Click me</button>
        </div>

    */
    {
      "id": "Hg3FL3Xg",
      "block": "[[[1,\"\\n    \"],[10,0],[15,0,[30,0,[\"classList\"]]],[12],[1,\"\\n      \"],[10,\"h1\"],[12],[1,\"Tests \"],[1,[30,0,[\"count\"]]],[13],[1,\"\\n      \"],[11,\"button\"],[24,4,\"button\"],[4,[32,0,[\"on\"]],[\"click\",[30,0,[\"handleClick\"]]],null],[12],[1,\"Click me\"],[13],[1,\"\\n    \"],[13],[1,\"\\n  \"]],[],false,[]]",
      "moduleName": "/Users/liam/Work/ember-vite/my-fancy-app/app/components/my-component.gjs",
      "scope": () => [template__imports__],
      "isStrictMode": true
    }), this);
  }
};

const ___ProxyClass = new Proxy(MyComponent, {
  // my stuff here
});
export default ___ProxyClass;
NullVoxPopuli commented 2 months ago

It looks like you need to proxy or set the prototype on your resulting proxied thing.

Tho, why proxy at all?

evoactivity commented 2 months ago

Because proxying will be the most ergonamic way (that I can think of) to augment state persistence for components with hot module replacement. Can simply inject the proxy at the end of the file as part of the build process without needing to rewrite the component definition, just need to rewrite the default export.

NullVoxPopuli commented 2 months ago

Ah i see, that's a reasonable low level thing to want these for.

So, i think we may just need to tweak the object returned from proxy so that the prototype is the same as the class.

And then, if you're storing state in the proxy, i need to think on that for a bit

evoactivity commented 2 months ago

State has to be stored externally to the module itself, since HMR will rebuild the whole thing and reload it will all get thrown away if stored locally.

You can see my current implementation here, it's just defined in the component file whilst I poke around to see what works, before I attempt to do anything babel/vite transform related.