ember-cli icon indicating copy to clipboard operation
ember-cli copied to clipboard

Add a failing test for the TO gts + signature scenario

Open Windvis opened this issue 9 months ago • 15 comments

While working on the strict component blueprint support PR, I encountered this issue which seems like a bug in the type removing functionality of ember-cli.

This adds a failing test which makes it easier to debug.

It seems to be related to the "implicit export default" feature of .gts. If I change the code to this then the output no longer includes the export {} line.

Input:

import type { TOC } from '@ember/component/template-only';

export interface FooSignature {
  // The arguments accepted by the component
  Args: {};
  // Any blocks yielded by the component
  Blocks: {
    default: []
  };
  // The element to which \`...attributes\` is applied in the component template
  Element: null;
}

+export default <template>
-<template>
  {{yield}}
</template> satisfies TOC<FooSignature>;

Output:

+export default <template>
-<template>
  {{yield}}
</template>

I also tried updating content-tag to v3.1.2, since that includes a fix very similar to this issue, but it doesn't solve this one it seems.

Windvis avatar Mar 22 '25 09:03 Windvis

Can you try upgrading babel-plugin-ember-template-compilation?

NullVoxPopuli avatar Mar 22 '25 12:03 NullVoxPopuli

Can you try upgrading babel-plugin-ember-template-compilation?

AFAIK, babel-plugin-ember-template-compilation isn't used by the type removal feature in ember-cli. pnpm why babel-plugin-ember-template-compilation doesn't show it either.

Windvis avatar Mar 22 '25 13:03 Windvis

It's babel-remove-types that adds the export {};

This is the code that's passed into it, after the template tag is replaced with a placeholder. (Babel playground link)

import type { TOC } from '@ember/component/template-only';

export interface FooSignature {
  // The arguments accepted by the component
  Args: {};
  // Any blocks yielded by the component
  Blocks: {
    default: []
  };
  // The element to which `...attributes` is applied in the component template
  Element: null;
}

__TEMPLATE_TAG_0__ satisfies TOC<FooSignature>;

Which results in this code:

__TEMPLATE_TAG_0__;
export {};

Which is probably done for this reason: https://www.typescriptlang.org/docs/handbook/2/modules.html#non-modules

If you have a file that doesn’t currently have any imports or exports, but you want to be treated as a module, add the line export {};

So I think we need to add more information to content-tag's .parse return value, about which content tag depends on the implicit default export, so we can add more logic here to deal with that scenario? We can then prepend it with export default before passing it over to babel-remove-types. Maybe babel-remove-types has an option to not output this export {} line though.

Windvis avatar Mar 22 '25 14:03 Windvis

tl;dr: content-tag needs to be at least 3.1.2, else you hit the missing export default bug


content-tag emits the export default for plain templates, but not ones wrapped in a satisfies node:

https://stackblitz.com/edit/stackblitz-starters-4fpuuz1c?file=index.js,package.json

import { template as template_ee404ce5d1a14c47b1acf1c491050df9 } from "@ember/template-compiler";
import type { TOC } from '@ember/component/template-only';
template_ee404ce5d1a14c47b1acf1c491050df9(`
  
  `, {
    eval () {
        return eval(arguments[0]);
    }
}) satisfies TOC<{
}>;

vs

import { template as template_245e215fd1a84f21bb99d45f1734555c } from "@ember/template-compiler";
export default template_245e215fd1a84f21bb99d45f1734555c(`
  
  `, {
    eval () {
        return eval(arguments[0]);
    }
});

BUT!

If I upgrade content-tag to 3.1.2, it`s fixed:

import { template as template_fd9b2463e5f141cfb5666b64daa1f11a } from "@ember/template-compiler";
import type { TOC } from '@ember/component/template-only';
export default template_fd9b2463e5f141cfb5666b64daa1f11a(`
  
  `, {
    eval () {
        return eval(arguments[0]);
    }
}) satisfies TOC<{
}>;

NullVoxPopuli avatar Mar 22 '25 15:03 NullVoxPopuli

tl;dr: content-tag needs to be at least 3.1.2, else you hit the missing export default bug

This PR literally includes the 3.1.2 version bump and it didn't fix the issue :sweat_smile: .

3.1.2 fixed it when transforming .gts code, but here we are using the .parse method to then do something with the result and pass it to babel-remove-types. So we need to handle the implicit export default here as well, before handig it off to babel-remove-types.

Windvis avatar Mar 22 '25 15:03 Windvis

This PR literally includes the 3.1.2 version bump and it didn't fix the issue 😅 .

apologies. I got too excited :facepalm:

NullVoxPopuli avatar Mar 22 '25 15:03 NullVoxPopuli

Gonna see if content-tag-utils handles this

NullVoxPopuli avatar Mar 22 '25 15:03 NullVoxPopuli

So, it seems that because content-tag-utils operates on the tag range, there is no issue: https://github.com/NullVoxPopuli/content-tag-utils/pull/13/files#diff-04471e09412bbfa48026ce0732d9ce4efd6dea7514e80516d583cbe2a11d0233R44

NullVoxPopuli avatar Mar 22 '25 15:03 NullVoxPopuli

but here we are using the .parse method to then do something with the result

Where does this happen?

NullVoxPopuli avatar Mar 22 '25 15:03 NullVoxPopuli

So we need to handle the implicit export default here as well, before handig it off to babel-remove-types.

I don't think this is true -- and parse can't provide this information, because it's the node within the satisfies type node.

I think in order to remove the satisfies type node, removeTypes or the stripping code needs to match on the satisfies (outer) node first, since it wraps the template tag node?

NullVoxPopuli avatar Mar 22 '25 15:03 NullVoxPopuli

Where does this happen?

Here: https://github.com/ember-cli/ember-cli/blob/487ae48c517597aad20970f1471b6883f8b8fa7c/lib/models/blueprint.js#L539-L576

Windvis avatar Mar 22 '25 15:03 Windvis

Maybe content-tag-utils should have an unprocess method that converts plain js/ts back to gjs/gts.

That way strip-babel-types can operate on plain js, and we don't need parse

NullVoxPopuli avatar Mar 22 '25 16:03 NullVoxPopuli

Maybe content-tag-utils should have an unprocess method that converts plain js/ts back to gjs/gts.

That way strip-babel-types can operate on plain js, and we don't need parse

If it's possible that would indeed be ideal, but I'm not sure if it is :smile: (if we want to keep the same formatting, implicit export defaults, ...). Still, Worth looking into I guess, this does indeed feel like something that shouldn't be present in ember-cli.

Semi-off-topic but I am starting to wonder if the whole "remove types from blueprint files" was the way to go though. I think things would be a lot simpler if we simply maintained a couple of extra .(g)js blueprint files and didn't have to deal with these kind of issues at all..

Windvis avatar Mar 22 '25 16:03 Windvis

unprocess

here it is, a way to go from js/ts back to gjs/gts

https://github.com/NullVoxPopuli/content-tag-utils/pull/15

NullVoxPopuli avatar Mar 23 '25 21:03 NullVoxPopuli

So now the code would be:

let ts = p.process(gts);
let js = stripTypes(ts);
let gjs = unprocess(js);

NullVoxPopuli avatar Mar 23 '25 22:03 NullVoxPopuli