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: add SolidJS support #2101

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

Azq2
Copy link

@Azq2 Azq2 commented Nov 29, 2024

Description

Basic SolidJS integration ported from @lingui/react
All tests passed.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Examples update

Checklist

  • I have read the CONTRIBUTING and CODE_OF_CONDUCT docs
  • I have added tests that prove my fix is effective or that my feature works
  • I have added the necessary documentation (if appropriate)

Testing

My package for testing: https://github.com/Azq2/js-lingui-solid

Copy link

vercel bot commented Nov 29, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
js-lingui ✅ Ready (Inspect) Visit Preview Nov 30, 2024 11:12am

Copy link

github-actions bot commented Nov 29, 2024

size-limit report 📦

Path Size
./packages/core/dist/index.mjs 3.09 KB (0%)
./packages/detect-locale/dist/index.mjs 723 B (0%)
./packages/react/dist/index.mjs 1.65 KB (0%)
./packages/remote-loader/dist/index.mjs 7.26 KB (0%)

@timofei-iatsenko
Copy link
Collaborator

Thanks for contribution, that looks interesting. Could you highlight what the key difference between react and solidjs integration?

@Azq2
Copy link
Author

Azq2 commented Nov 29, 2024

Key differences:

  • Different reactivity model.
  • VDOM vs real HTML nodes.

  1. Different useLingui() API
    React:

    const { _, i18n } = useLingui();
    
    useEffect(() => {
        console.log(i18n.locale)
    }, [i18n])
    
    return (
        <div>
    	    {_(msg`lang:`)} {i18n.locale}
        </div>
    );

    SolidJS:

    // i18n - reactive signal
    // defaultComponent - reactive signal
    // _ - wrapper around i18n()._(...) for reactivity
    
    const { _, i18n } = useLingui();
    
    createEffect(() => {
        console.log(i18n().locale)
    })
    
    return (
        <div>
    	    {_(msg`lang:`)} {i18n().locale}
        </div>
    );

    In all other respects, the lingui API is fully compatible between React and SolidJS.

  2. In SolidJS we work with real DOM nodes instead of VDOM nodes. But it's not a problem, formatElements work okay.

  3. I use vite instead of unbuild, beacuse unbuild can't solidjs code with JSX.
    And I don't know how to fix that. Maybe it is impossible.

  4. Different test API: render(<JSX>) vs render(() => <JSX>)


All tests are ported from React, except:

  • "should create two children with different keys" - SolidJS don't have key= analog.
  • "should render nested elements with asChild pattern" - impossible with SolidJS.
  • "should render class component as simple prop" - in SolidJS all components are functional.

This is not ported to SolidJS:

  • RSC server - I don't know analog in SolidJS

P.S. SolidJS and React code are still similar

diff -Naur packages/react packages/solid | colordiff

@timofei-iatsenko
Copy link
Collaborator

Thanks for details explanation, from the PR file listing (i haven't dug into this properly yet) it looks like many code duplication with React, is there something that could be extracted and reused? Maintaining few copies in the long run isn't a very pleasant experience.

@Azq2
Copy link
Author

Azq2 commented Nov 29, 2024

Thanks for details explanation, from the PR file listing (i haven't dug into this properly yet) it looks like many code duplication with React, is there something that could be extracted and reused? Maintaining few copies in the long run isn't a very pleasant experience.

I'm not sure. The reactive model and data types are dramatically different. It just looks similar, but the details are completely different.

@Azq2
Copy link
Author

Azq2 commented Nov 30, 2024

Done at this moment:

  • @lingui/solid
  • @lingui/solid tests
  • support for @lingui/solid in @lingui/babel-plugin-lingui-macro
  • support for @lingui/solid in @lingui/babel-plugin-extract-messages

What else is needed for merging PR? Documentation? Examples? CI?

@Azq2
Copy link
Author

Azq2 commented Nov 30, 2024

Also, macro plugin don't work with @lingui/solid/macro without this lines in config:

  runtimeConfigModule: {
    Trans: ["@lingui/solid", "Trans"],
    useLingui: ["@lingui/solid", "useLingui"],
  }

I think that it would be ideal to automatically determine the required package.
@lingui/<something>/macro -> @lingui/<something>

@Azq2
Copy link
Author

Azq2 commented Nov 30, 2024

Another question.
In React, we have state. The state is simple variables. And this works:

Hello {name}
// Hello {name}

But in SolidJS, we use signals. They are always function calls.

Hello {name()}
// Hello {0}

It's okay? Or can I change the logic of macros?

Better variant for SolidJS:

Hello {name()}
// Hello {name}

But... breaking change for other integrations. Of course, is not critical, working SolidJS integration is still cool even without named macros for signals.

@timofei-iatsenko
Copy link
Collaborator

I'm not sure. The reactive model and data types are dramatically different. It just looks similar, but the details are completely different.

I understand that inside these two frameworks are different. But some implementation details of a library are very similar. And because of the code duplication this will increase a maintenance burden for the maintainers.

So i'm really want to avoid this at any cost. Take for example the format function. Is there any difference from the react version? If it's almost the same, we can move it to some shared package and reuse between both.

The same is for other parts.

Note regarding macro. There is also a SWC version of it, is there any configurations where solid can be used with a SWC? if yes, then SWC version should also be updated.

Better variant for SolidJS:

Hello {name()}
// Hello {name}
But... breaking change for other integrations. Of course, is not critical, working SolidJS integration is still cool even without named macros for signals.

There is a PR for labeled placeholders, it might help in this case:

Hello {{name: name()}}

@Azq2
Copy link
Author

Azq2 commented Dec 2, 2024

https://gist.github.com/Azq2/fb6a5485242746a6e62318f1b959b728

  • macro/index.d.ts - different data types
  • macro/__typetests__/index.test-d.tsx - 90% is the same, but: different imports & API, data types and state vs signal() problem
  • src/format.ts - same part is only parser
  • src/I18nProvider.tsx - same only comments
  • src/TransNoContext.tsx - same logic and flow, but heavy difference
  • src/Trans.tsx - different data types
  • src/format.test.tsx - 90% is the same, but: different imports & API, data types
  • src/I18nProvider.test.tsx - 90% is the same, but: different imports & API, data types and state vs signal()
  • src/Trans.test.tsx - 90% is the same, but: different imports & API, data types and state vs signal()

  • The most similar part is tests and macro. This is theoretically (but hard) portable by shimming types and API.
    type LinguiNode = JSX.Element;
    // vs
    type LinguiNode = ReactNode;
  • The framework neutral part is only parser in format.ts.

@Azq2
Copy link
Author

Azq2 commented Jan 19, 2025

Another way - I can develop solidjs integration by myself, unofficially, in a separate package/organization.

But with official support in:

  • lingui babel extract plugin
  • lingui babel macro plugin
  • lingui vite plugin

This is required for normal work.

@Bubz43
Copy link

Bubz43 commented Jan 19, 2025

For what its worth, I've been using import { Trans } from @lingui/react/macro in a solidjs project, with the runtimeConfigModule in the lingui.config.ts set to a solidjs component similar to the one in this PR and everything works fine without further changes than that. Maybe something like @lingui/jsx/macro could be a thing and requires that the config has the runtimeConfigModule setup.

@Azq2
Copy link
Author

Azq2 commented Jan 19, 2025

I've been using import { Trans } from @lingui/react/macro in a solidjs project

@Bubz43 Can you show an example? I'll be surprised if this works with TS.

@Bubz43
Copy link

Bubz43 commented Jan 19, 2025

Apologies, I should have specified that by default the component's props don't work with TS autocomplete/typechecking (though the JSDoc comments still work) but it also doesn't have any errors for the common use case of <Trans>foo</Trans> (as I'm pretty sure it narrows them to any as the react module isn't installed).

To get actual TS support you can override @lingui/react/macro by adding a .d.ts file in the project (ideally the more complete one from this pr)

declare module "@lingui/react/macro" {
	import type { ParentComponent } from "solid-js";
	/**
	 * Trans is the basic macro for static messages,
	 * messages with variables, but also for messages with inline markup
	 *
	 * @example
	 * ```
	 * <Trans>Hello {username}. Read the <a href="/docs">docs</a>.</Trans>
	 * ```
	 * @example
	 * ```
	 * <Trans id="custom.id">Hello {username}.</Trans>
	 * ```
	 */
	export const Trans: ParentComponent<{
		id?: string;
		comment?: string;
		context?: string;
	}>;
}

I'm not suggesting this is reasonable for the average end user to do, just wanted to share in case it sparks an idea for an easier way forward that lowers the surface area for maintenance and/or for supporting solid-js from an external package.

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.

3 participants