Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli-lib): adding support for gts, gjs and hbs files. #4376

Closed
wants to merge 15 commits into from

Conversation

kiwiupover
Copy link
Contributor

@kiwiupover kiwiupover commented Mar 18, 2024

Use case

Engineering using glimmerjs files .gts and .gjs and handlebar files .hbs want to use formatjs for internationalization

Solution

Follow the Vue example and use glimmer tooling and ember-template-recast to manage code transformation.

Thanks to @mansona and @candunaj for the initial work.

package.json Outdated
@@ -86,6 +90,8 @@
"clsx": "2",
"commander": "8",
"core-js": "^3.6.5",
"ember-template-recast": "^6.1.4",
"ember-template-tag": "^2.3.16",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"ember-template-tag": "^2.3.16",
"content-tag": "^2.0.0",

content-tag is the official implementation of <template> parsing (and has types!)

See here: https://github.com/embroider-build/content-tag/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved!

"@types/estree": "^1.0.0",
"@types/fs-extra": "^9.0.1",
"@types/json-stable-stringify": "^1.0.32",
"@types/node": "14 || 16 || 17",
"chalk": "^4.0.0",
"commander": "8",
"ember-template-recast": "^6.1.4",
"ember-template-tag": "^2.3.16",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

scriptParseFn(transformedSource)

// extract template from transformed source to then run through hbs processor
const [templateSource] = parseTemplates(source, '')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gts can have multiple templates, so does this concatenate all of the different templates?

For example, looking at this type from content-tag: https://github.com/embroider-build/content-tag/?tab=readme-ov-file

We have:

import { Preprocessor } from 'content-tag';
let p = new Preprocessor();

// ...
let output = p.parse(source);

for (let parseResult of output) {
  parseHbsFile(parseResult.contents, filename, options);
}

import or from 'ember-truth-helpers/helpers/or';
import Component from '@glimmer/component';
import {service} from '@ember/service';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if a second component were added to this file, the implementation (as is) would break -- unless I misunderstanding something about how ember-template-tag works opposed to content-tag

@longlho
Copy link
Member

longlho commented Mar 27, 2024

overall this needs more typings

@@ -0,0 +1,31 @@
import {transform} from 'ember-template-recast'

function extractText(node: any, fileName: any, options: any) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do those params have real types?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they do,

import type { AST } from '@glimmer/syntax';

function extractText(node: AST.MustacheStatement | AST.SubExpression, ...) {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types have been resolved

@NullVoxPopuli
Copy link

atm, I'm having issues with ESM compatibility in this CJS project hmmm

@NullVoxPopuli
Copy link

NullVoxPopuli commented Apr 5, 2024

it looks like esbuild is what's used to compile, and it does support the situation we have

REPL here

so, something else is goofy in formatjs 🤔
The error:

✘ [ERROR] Could not resolve "./hbs_extractor.mts"

    ../../../../../../../../execroot/formatjs/bazel-out/k8-fastbuild/bin/packages/cli-lib/lib_esnext/src/extract.js:76:43:
      76 │         const { parseFile } = await import('./hbs_extractor.mts');
         ╵                                            ~~~~~~~~~~~~~~~~~~~~~

✘ [ERROR] Could not resolve "./gts_extractor.mts"

    ../../../../../../../../execroot/formatjs/bazel-out/k8-fastbuild/bin/packages/cli-lib/lib_esnext/src/extract.js:84:43:
      84 │         const { parseFile } = await import('./gts_extractor.mts');
         ╵                                            ~~~~~~~~~~~~~~~~~~~~~

it seems a combination of typescript, esbuild, buildtime and runtime are all in disagreement with eachother with what valid imports are

@NullVoxPopuli
Copy link

NullVoxPopuli commented Apr 5, 2024

this might be blocked on moving away from jest?

I'm running just the test command, and seeing:

❯   npx bazel run //packages/cli-lib:unit_test


  ● hbs_extractor

    Cannot find module '../../src/hbs_extractor.mjs' from 'tests/unit/hbs_extractor.test.ts'

      4 |
      5 | test('hbs_extractor', async function() {
    > 6 |   const { parseFile } = await import('../../src/hbs_extractor.mjs');
        |                               ^
      7 |   let messages: MessageDescriptor[] = []
      8 |   const fixturePath = join(__dirname, './fixtures/comp.hbs')
      9 |   parseFile(await readFile(fixturePath, 'utf8'), fixturePath, {

      at Resolver._throwModNotFoundError (../../node_modules/.aspect_rules_js/jest-resolve@29.5.0/node_modules/jest-resolve/build/resolver.js:427:11)
      at tests/unit/hbs_extractor.test.ts:6:31

@longlho
Copy link
Member

longlho commented Apr 6, 2024

we don't support .mts or .mjs. The interop is a pain to deal with

@NullVoxPopuli
Copy link

NullVoxPopuli commented Apr 6, 2024

It's pretty straight forward without jest, tbh. But i'll have another go at by pushing the module boundary into the functions, and make them all async (we previously had everything passing except for type checking, which is how things got to how they are now).

I'll see if i can get this repo's ts config, jest, and esbuild all in agreement that way! 🎉

Also, in a separate pr, i can try moving this repo to vitest for ya, if that's of interest?

@longlho
Copy link
Member

longlho commented Apr 6, 2024

Thanks for your contribution. Yeah I don't have a strong opinion on vitest vs jest, so if u wanna make a PR that'd be great as well

@longlho
Copy link
Member

longlho commented Apr 11, 2024

hmm getting vitest to work can be pretty hard, any reason you can't use just .ts instead of .mts?

@NullVoxPopuli
Copy link

NullVoxPopuli commented Apr 11, 2024

can't use just .ts instead of .mts?

I cannot due to type checking failing. We can't require or await import (for some reason) from cjs(ts), even though it's allowed generally, it's just type checking that is failing, iirc.

I discovered that this is exclusively a tsc problem tho. If i use vite for the build, as shown, everything is fine! 🎉
Same with vitest (in cli-lib) -- it's already working flawlessly!

The last step i have to figure out is how to have cli-lib's different tools integrated into bazel

@longlho
Copy link
Member

longlho commented Apr 11, 2024

what's failing typechecking? This PR now has tooling changes so I can't really tell from its logs

@longlho
Copy link
Member

longlho commented Apr 11, 2024

from all the commits you pushed which one doesn't have the tooling changes?

@NullVoxPopuli
Copy link

what's failing typechecking? This PR now has tooling changes so I can't really tell from its logs

If you revert the most recent commit, you'll see

@NullVoxPopuli
Copy link

I don't know what's going on with the build anymore, getting errors like this:
image

These are SWC, but there is also ESBuild and TSC in use. Why so many compilers? 😅

@longlho
Copy link
Member

longlho commented May 5, 2024

@NullVoxPopuli I've manually merged this in after a bunch of patches. I basically remove type: module from glimmer packages & content-tag so things would work. mjs overall is a mess and the ecosystem's not ready for it. If you wanna add integration tests to make sure parsing hbs/gts works that'd be great too.

@longlho longlho closed this May 5, 2024
@longlho
Copy link
Member

longlho commented May 5, 2024

I've added the integration tests in fd1e347#diff-0ead01fee706ec365313e79e65339cfa742bf05b2f05587d1e30815195fed00b
looks like hbs works but not gts/gjs

@longlho
Copy link
Member

longlho commented May 5, 2024

looks like packaging issue Error: ENOENT: no such file or directory, open '/private/var/tmp/_bazel_long.ho/a2216e06a77a9425f6dcd5fd195f64d5/execroot/formatjs/bazel-out/darwin_arm64-fastbuild/bin/packages/cli/bin/content_tag_bg.wasm'

@longlho
Copy link
Member

longlho commented May 5, 2024

ok latest version should fix this. Integration tests also passed. Releasing that now.

@NullVoxPopuli
Copy link

NullVoxPopuli commented May 5, 2024

Thanks for solving everything! Sorry I couldn't figure it out (i had full esm and cjs compat easily if it weren't for jest (vitest 'just works')). the ESM/CJS compat I've don is much simpler, but is also only using one cohesive build tool 😅
maybe I just didn't spend enough time with it, or am not smart enough. haha.

You are my hero, today @longlho <3

@longlho
Copy link
Member

longlho commented May 5, 2024

No worries. I know you contribute upstream so if there're things to make it easier to not have to patch on my side that'd be great :)

@kiwiupover
Copy link
Contributor Author

@longlho thanks heaps for your work getting .gts, .gjs and .hbs into formatjs. you have made the ember community happy, mate. Cheers.

@longlho
Copy link
Member

longlho commented May 5, 2024

What linter do you guys use? Extractor is only 1 piece but we also have linter integration for react/vue as well.

@NullVoxPopuli
Copy link

NullVoxPopuli commented May 5, 2024

do you mean translations linter? (I'm a newbie to the tooling side of this space (translations), fwiw 😅 )
for js/ts/etc linting, we use eslint and prettier

@longlho
Copy link
Member

longlho commented May 5, 2024

What's the eslint plugin for those extensions & do they use the same parser?
For capabilities you can look at existing formatjs linter at formatjs.io

@NullVoxPopuli
Copy link

NullVoxPopuli commented May 5, 2024

ah! yes!

For ESLint we use:

which uses:

which takes our gjs/gts parser, content-tag:

And stitches together ESTree AST and uses our @glimmer/syntax parser

and prefixes those AST nodes with Glimmer
So we have GlimmerElementNode, GlimmerAttrNode, etc

We still need to do the same for jscodeshift -- using a custom parser that is actually two parsers -- but that is pending on some cleanup work I need to get back to here: embroider-build/content-tag#75
Which enables eslint, prettier, jscodeshift and any other plugin/ecosystem to have better control over offsets and such with the gjs/gts <-> pure JS transformation.

For Prettier, all we do use this plugin:

which also uses content-tag for parsing, but because prettier already supported formatting glimmer, we use invoke prettier on each <template> block, handle the indentation offset per line, and stitch the file back together.

All of this is pretty similar to how Svelte and Vue work, afaik -- but also afaik, we're the only ones more eagerly integrating ESM into our CJS tooling (and fairly seamlessly!)


  • content-tag supports both ESM and CJS (it is cross-compiled)
  • @glimmer/syntax supports both ESM and CJS (it is cross-compiled)
  • ESLint plugins are CJS
  • Prettier plugins are ESM
  • Our template-lint tool and its plugins are ESM

@longlho
Copy link
Member

longlho commented May 5, 2024

Do you guys use ember-intl or formatjs directly? I was trying to get ember-intl merged into this monorepo but that doesn't pan out. That package is pretty old as well so thinking of just forking it.

@NullVoxPopuli
Copy link

NullVoxPopuli commented May 5, 2024

the community makes a lot of use of ember-intl, and my employer does as well, but we were thinking about convincing (or really making a solid case for) ember-intl to be a build-time plugin rather than more runtime focused as it is now -- at work, we don't do much special in our product code to author translations -- we extract all the strings and such at build time, and only do manual / translating if we have tokens/numbers/data/etc involved that end up splicing up the same translation string. This makes for a very good default experience where the source code is just authored in english, and we don't maintain translation files or anything.

To do this, we use formatjs directly in our build time code.

@longlho
Copy link
Member

longlho commented May 5, 2024

Can you describe that pipeline in a bit more details?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants