js-lingui icon indicating copy to clipboard operation
js-lingui copied to clipboard

SWC plugin

Open renchap opened this issue 3 years ago β€’ 3 comments

Is your feature request related to a problem? Please describe. I want to switch our projects from Babel to SWC, a Rust-based JS transpiler. SWC is used by Next.js unless you opt-out from it because you rely on Babel.

Describe proposed solution SWC has a brand new plugin system, which should be able to handle the transformations needed by Lingui's macros.

This is not yet stable / well documented, but some plugins are available here: https://github.com/swc-project/plugins (for Styled Components for example).

Those plugins are compiled to WASM, so they can be written in several languages (including AssemblyScript, a Typescript derivative compiling to WASM)

A first step would probably be to continue using Babel for extracting and use this plugin only to transform macros, but in the long term being able to use SWC for extracting could bring a much faster extract and heavily reduce dependencies needed for extraction (swc + swc-lingui and thats pretty much all).

Describe alternatives you've considered

  • Porting Babel macros to a SWC plugin: not sure this can easily be done, this has been discussed a bit in https://github.com/kentcdodds/babel-plugin-macros/issues/144 but this would probably be a new project. Also babel-macros is frozen as the author no longer uses it
  • Using another transpiling tool, like esbuild: there is no support for esbuild + Lingui either (and esbuild does not have a plugin support)
  • Switching away from Lingui: I really like Lingui, I would prefer not :)
  • Keep using Babel: swc (and other modern compilers) are much faster, it would really be better to be able to switch away!

Additional context Add any other context or screenshots about the feature request here.

renchap avatar Apr 05 '22 14:04 renchap

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 10 '22 18:06 stale[bot]

This issue is getting a lot of πŸ‘, dont close it!

renchap avatar Jun 10 '22 18:06 renchap

Best post here, swc, ts-jest, both aren't supported by lingui, and our code base growing, our unit test gets poor performances, and the question to remove lingui become more and more obvious.

This is a must for big projects, otherwise lingui will never stay long in any on them.

kopax-polyconseil avatar Jul 26 '22 16:07 kopax-polyconseil

Any update on this one?

bojanbass avatar Sep 23 '22 12:09 bojanbass

Nothing yet we can't do right now, there's no way we can replicate the macro's thing on swc (at least as far as I know, not a rust expert here) But of course, any help or info will be highly appreciated :)

semoal avatar Sep 24 '22 17:09 semoal

Our project which is used by millions of users now remove lingui translation for nothing, twice time lost. My advice, don't use macro and even more experimental lingui for your app.

kopax-polyconseil avatar Sep 24 '22 19:09 kopax-polyconseil

Hi! Is there any progress or any plans for the future? We have codebase build on Lingui but right now we are upgrading to React 18 + Next 13 and we don't want to stay on Babel. For now we are okay with @lingui/react syntax but we would like to use lingui extract but its also not possible without Babel.

Is there any chance? πŸš€

sitole avatar Nov 14 '22 20:11 sitole

Yes I'm already moving pieces together thanks to a similar project that implemented Macros on SWC, I'm already working on something suitable for Lingui... I'll open-source it soon.

semoal avatar Nov 15 '22 09:11 semoal

@semoal do you mean that you’ve started implementing a plugin in rust for swc?

I can help at least testing it and maybe writing it. I guess that you need writing a visitor, right?

shouze avatar Nov 16 '22 02:11 shouze

Iam also ready and happy to test in on our codebase πŸ”₯

sitole avatar Nov 16 '22 11:11 sitole

Hey! I found some solution that enables Babel for Lingui export but Next.js app can be still SWC-only. You can just add custom Babel configuration into .linguirc file and remove your Babel configuration file in app.

More info in documentation (https://lingui.js.org/ref/conf.html#id3) For me works just Next.js babel configuration:

"extractBabelOptions": {
    "presets": ["next/babel"]
}

sitole avatar Nov 16 '22 14:11 sitole

@sitole Are you using @lingui/macro?

I've tried but I'm getting some error in the @lingui/macro stack

image

armandabric avatar Nov 16 '22 15:11 armandabric

@sitole Are you using @lingui/macro?

I've tried but I'm getting some error in the @lingui/macro stack

image

Yea, this is just for non-macro users. But you can still use <Trans> from @lingui/react also with i18n from @lingui/core. But Sam just debugging possible issues. It's not solution ready for production. 😁

For example with this and Babel configuration i'm not able to export TypeScript translations for example

const propertyName = i18n._(`super-code-in-typescript-code`)

sitole avatar Nov 16 '22 16:11 sitole

This would help us a lot. Anyway how can I help?

landsman avatar Nov 30 '22 13:11 landsman

Well, don't know how it's going by @semoal. I've tried to contact him but no luck. So I started my own development of SWC plugin. It's now on very early stages and it's moving quite slow because i'm not a professional rust developer (but i do have a lot of expertise in writing transforms and working with AST so it helps)

Currently, i have only this tests passing:

test!(
    Default::default(),
    |_| TransformVisitor,
    substitution_in_tpl_literal,
    // input
     r#"
     t`Refresh inbox`
     t`Refresh ${foo} inbox ${bar}`
     t`Refresh ${foo.bar} inbox ${bar}`
     t`Refresh ${expr()}`
     "#,
    // output after transform
    r#"
    i18n._("Refresh inbox", {})
    i18n._("Refresh {foo} inbox {bar}", {foo, bar})
    i18n._("Refresh {0} inbox {bar}", {0: foo.bar, bar})
    i18n._("Refresh {0}", {0: expr()})
    "#
);

test!(
    Default::default(),
    |_| TransformVisitor,
    custom_i18n_passed,
    // input
     r#"
     t(custom_i18n)`Refresh inbox`
     t(custom_i18n)`Refresh ${foo} inbox ${bar}`
     t(custom_i18n)`Refresh ${foo.bar} inbox ${bar}`
     t(custom_i18n)`Refresh ${expr()}`
     "#,
    // output after transform
    r#"
    custom_i18n._("Refresh inbox", {})
    custom_i18n._("Refresh {foo} inbox {bar}", {foo, bar})
    custom_i18n._("Refresh {0} inbox {bar}", {0: foo.bar, bar})
    custom_i18n._("Refresh {0}", {0: expr()})
    "#
);

Which is personally for me is a big WIN because showing potential and that rest is just a matter of time.

If someone has more experience in rust, and would like to help, i'm looking for a rust mentorship or at least code review of what i've written.

timofei-iatsenko avatar Dec 15 '22 14:12 timofei-iatsenko

Very nice work @thekip! Do you have open a pull request somewhere, or any versioned source code that you can share? I'm willing to help if I can.

shouze avatar Dec 15 '22 15:12 shouze

This is amazing!

Could you share a repository? I think SWC author / Vercel employees might want to help you on this as this is one of the most wanted plugins for Next.js users

renchap avatar Dec 15 '22 15:12 renchap

https://github.com/thekip/swc-lingui-plugin

As mentioned this is not production ready and very far even from 0.0.1 stage.

Future plans:

I'm going to implement a bare minimum of a macro (JSX transformation is a must). Probably not all edge cases or event functionalities would work in that, so only the most useful and important (JSX transformation, plural) and then will ask help from community to implement the rest.

timofei-iatsenko avatar Dec 15 '22 15:12 timofei-iatsenko

I would recommend you to keep a list of things to do in the README, so people can help implementing some of them. Also split in into what you want for a first version, and what is next.

You should also open some issues on topics you are not sure you handled correctly, questions to be answered…

I dont give any promises as I am very busy atm but I will watch the repo and have a look asap!

renchap avatar Dec 15 '22 15:12 renchap

@renchap good suggestions. I've added a list of tasks to the repo, and will create couple issues with questions which i don't know how to solve properly.

Meanwhile, i've implemented basic transformation for ICU calls (plurals, select, etc), it's still far from first launch but implementation went much quicker than before.

timofei-iatsenko avatar Dec 15 '22 22:12 timofei-iatsenko

Started implementing JSX transformation and extended list of tasks in readme.

Also checked original macro's test suite, well... there so much cases...

The current macro implementation allows the user to use too much different syntax. From my point of view it's better to keep implementation simple and provide users with opinionated way how to write code instead of implementing all possible ways how user can write the code (the js with jsx is so flexible)

For example, user can write:

<Trans>{'My Text'}</Trans>

Instead of simple:

<Trans>My Text</Trans>

This is supported in current babel macro. From AST point of view this children prop is an expression, not a JSXString, so to support it in Rust implement should have a complicated heuristic to parse what inside expression and should we use it as message or not.

timofei-iatsenko avatar Dec 16 '22 12:12 timofei-iatsenko

Latest updates. I've implemented jsx transformations with interpolations and imports replacement, so now this test passes:

  
import { Trans } from "@lingui/macro";
const t = <Trans>
  Hello <strong>World!</strong><br />
<p>
  My name is <a href="/about">{" "}
  <em>{name}</em></a>
</p>
</Trans>

// // ↓ ↓ ↓ ↓ ↓ ↓
import { Trans } from "@lingui/core";
const t = <Trans id={"Hello <0>World!</0><1/><2>My name is <3> <4>{name}</4></3></2>"} values={{
  name: name,
}} components={{
  0: <strong />,
    1: <br />,
    2: <p />,
    3: <a href="/about" />,
    4: <em />
}} />;

It was the hardest part, so rest is should be much simpler.

timofei-iatsenko avatar Dec 20 '22 13:12 timofei-iatsenko

For those how are tracking progress on the plugin. I've completely stuck trying to implementing recursive parsing of ICU / Trans expressions in the JSX. I'm stuck implementing usual simple data-structures in Rust's needed to implement this recursive feature. Rust is very different when it comes to data structures and some popular approaches just don't work, and i could not come how to make it in Rust-way.

By recursive JSX i mean something like this:

<Trans>
  Hello <strong>World!</strong><br />
  You have <Plural value={5} one={"# message"} few={"# messages"}>
</Trans>

timofei-iatsenko avatar Jan 03 '23 10:01 timofei-iatsenko

ping @hywan I know that you master Rust like nobody, maybe can you point @thekip to some solution? :thinking:

shouze avatar Jan 03 '23 10:01 shouze

I moved on from the dead point. Reorganized code to do stuff in two phases - collecting, then emitting. This way, rust allowed me to do so. Have no idea how fast implementation would be, i'm more concentrated on greening as many tests from original test suite as possible.

So this

<Trans>
  Hello <strong>World!</strong><br />
  You have <Plural value={5} one={"# message"} few={"# messages"}>
</Trans>

And even this

<Trans>
  Hello <strong>World!</strong><br />
  You have <Plural value={5} one={<Trans># message</Trans>} few={<Trans># messages</Trans>}>
</Trans>

With any level of depth is supported. Now i'm focused on refactoring t macro to make it work the same way as JSX

timofei-iatsenko avatar Jan 04 '23 11:01 timofei-iatsenko

Hey :-),

How can I help?

Hywan avatar Jan 05 '23 10:01 Hywan

@Hywan It would be great if you could do a code review and give some advices on https://github.com/thekip/swc-lingui-plugin

I'm new to rust so all suggestions would be appreciated.

timofei-iatsenko avatar Jan 05 '23 13:01 timofei-iatsenko

Meanwhile, you guys can review this ticket related to macro

https://github.com/lingui/js-lingui/issues/1324 https://github.com/lingui/js-lingui/issues/1323 https://github.com/lingui/js-lingui/issues/1320

You might have more experience with the lib and prove me wrong.

timofei-iatsenko avatar Jan 06 '23 12:01 timofei-iatsenko

Created a first pre-release. Tested a plugin on my mid-size project and it works.
https://github.com/thekip/swc-lingui-plugin/releases/tag/0.0.1

When we agreed on details with @andrii-bodnar i will move the repo under lingui organization and make a proper npm release within @lingui npm scope. As for now, don't want to mess up things and create another trashy package on npm.

timofei-iatsenko avatar Jan 06 '23 21:01 timofei-iatsenko

@Martin005 why a "breaking change" label appeared here? This plugin doesn't introduce any breaking changes.

timofei-iatsenko avatar Jan 09 '23 12:01 timofei-iatsenko