glimmerjs/glimmer.js

glimmer-babel preset failing for typescript files

lifeart opened this issue · 10 comments

given:

import Component, { hbs } from '@glimmerx/component';

import HelloWorld from './components/HelloWorld.hbs';

export default class App extends Component {
    static template = hbs`
        <HelloWorld />
   `
}

error:

image

once I'm console.log(HelloWorld), everything works just fine, looks like plugin should count .hbs, .gbs extensions

given:

import Component, { hbs } from '@glimmerx/component';

import HelloWorld from './components/HelloWorld.hbs';
console.log(HelloWorld);
export default class App extends Component {
    static template = hbs`
        <HelloWorld />
   `
}

workaround before transform to get it working

 const imports = parseStaticImports(code).filter(e => {
        return e.moduleName.startsWith('@glimmerx/modifier') || !e.moduleName.startsWith("@");
      }).map((el) => [...el.namedImports.map(e => e.alias), el.defaultImport]).reduce((acc, items) => {
        return acc.concat(items);
      }, []);
      
      code = `
        ${code};
        //
        [${imports.map(e => `${e}`).join(',')}];
      `;

problem in this function: https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile/blob/master/index.js#L142

because imported template is not binded to the scope

// this implementation of getScope solves missing scope lookup

  function getScope(scope) {
    let names = Object.keys(scope.references);

    while (scope) {
      
      for (let binding in scope.bindings) {
        names.push(binding);
      }

      if (!scope.parent) {
        Object.keys(scope.references).forEach((ref) => {
          if (!names.includes(ref)) {
            names.push(ref);
          }
        });
      }

      scope = scope.parent;
    }

    return names;
  }

but not solve final problem

diff --git a/node_modules/babel-plugin-htmlbars-inline-precompile/index.js b/node_modules/babel-plugin-htmlbars-inline-precompile/index.js
index 700b3d6..29d9316 100644
--- a/node_modules/babel-plugin-htmlbars-inline-precompile/index.js
+++ b/node_modules/babel-plugin-htmlbars-inline-precompile/index.js
@@ -143,6 +143,14 @@ module.exports = function (babel) {
         names.push(binding);
       }
 
+      if (!scope.parent) {
+        Object.keys(scope.references).forEach((ref) => {
+          if (!names.includes(ref)) {
+            names.push(ref);
+          }
+        });
+      }
+
       scope = scope.parent;
     }
 

next issue we have:

on modifier import is removed after compilation, but scope referencing to it

input:

import Component, { hbs } from '@glimmerx/component';
import { on, action } from '@glimmerx/modifier';
export default class App extends Component {
    updateValue() { console.log(1) }
    static template = hbs`
        <input {{on 'input' this.updateValue}} />
    `;
}

compiled output:

import { setComponentTemplate as _setComponentTemplate } from "@glimmer/core";  
import { createTemplateFactory as _createTemplateFactory } from "@glimmer/core";
import Component from '@glimmerx/component';
export default class App extends Component {
  updateValue() {
    console.log(1);
  }

}

_setComponentTemplate(_createTemplateFactory(
/*

        <input {{on 'input' this.updateValue}} />

*/
{
  "id": "HHfcxqIY",
  "block": "[[[1,\"\\n        \"],[11,\"input\"],[4,[32,0],[\"input\",[30,0,[\"updateValue\"]]],null],[12],[13],[1,\"\\n    \"]],[],false,[]]",        
  "moduleName": "(unknown template module)",
  "scope": () => [on],
  "isStrictMode": true
}), App);

if I import { action, on } from @glimmerx/modifier;

I have only { action } after template compilation:

image

If I replace @glimmerx with @glimmer:

image

looks like issue in @babel/preset-typescript, it's removing unused imports, may be related issue: babel/babel#9723
// https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAMwhRUIjgciRTBuAKAIFMAPSWOAE2IQEMBXAG3gGMm6BnTuAMWQBGdKHADeAXyA

possible workaround: not use ts, or parse TS -> apply hbs transform -> print ts,

I believe that the TS removing the imports also prevents template imports to work in ember with TS. So, fixing support for TS would be the best approach instead of not having TS support at all.

@josemarluedke I have one workaround for vite, collecting all imports before ast transform and appending all imports to array, to prevent ts deleting it.

import { transformSync } from "@babel/core";

import babelGlimmerPreset from "@glimmerx/babel-preset";
import tsPreset from "@babel/preset-typescript";
import parseStaticImports from "parse-static-imports";

const templateFileRegex = /\.(hbs)$/;
const fixDelimiter = '// [will-be-removed]';

export default function vitePluginBabelImport(
  plgOptions
) {
  let viteConfig;
  return {
    name: 'vite:glimmerx',
    enforce: 'pre',
    configResolved(resolvedConfig) {
      viteConfig = resolvedConfig;
    },

    transform(rawCode, id) {
      let code = rawCode;
      if (templateFileRegex.test(id)) {
        code = `
          import { hbs } from '@glimmerx/component';
          export default hbs\`${rawCode.trim()}\`;
        `.trim();
      } else if (!id.endsWith('.ts') && !id.endsWith('.js')) {
        return;
      }
      
      const imports = parseStaticImports(code).filter(e => {
        return e.moduleName.startsWith('@glimmerx/') || !e.moduleName.startsWith("@");
      }).map((el) => [...el.namedImports.map(e => e.alias), el.defaultImport]).reduce((acc, items) => {
        return acc.concat(items);
      }, []);
      
      code = `
        ${code};
        ${fixDelimiter}
        [${imports.map(e => `${e}`).join(',')}];
      `;

      const result = transformSrcCode(code, id, plgOptions, viteConfig);

      return {
        code: result.split(fixDelimiter)[0].trim(),
        map: null,
      };
    },
  };
}


function transformSrcCode(code, fileName, plgOptions, viteConfig) {
    let result = transformSync(code, {
        sourceType: "module",
        babelrc: false,
        configFile: false,
        envName: viteConfig.mode,
        filename: fileName,
        presets: [tsPreset, function(api, opts) {
            return babelGlimmerPreset(api, {...opts, ...{
                isDebug: !viteConfig.isProduction
            }})
        }]
    });
    return result.code;
}

What needs resolving to make this preset work for TS?

@knownasilya with latest TS compiler, new flag introduced, to not delete "not used" imports. It should help (but i'm did not tryied)