From e350e288d4a8aba7eb7a55652f3b17d055b21159 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:13:16 -0400 Subject: [PATCH 1/3] Add more testing and usage information --- README.md | 67 +++++++++++++++++++++++++++++++++++---------- src/render-react.ts | 34 +++++++++++++++++++++-- 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 83d066a..5c951b3 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,59 @@ pnpm add @universal-ember/react ## Usage -Because Ember's tests are the same syntax and style as app code, usage examples in real app code match the tests. Some samples: +Import the react component into an ember component and render it. + +```gjs +import { makeRenderable } from '@universal-ember/react'; +import { HelloWorld } from './hello-world.tsx'; + +const Hello = makeRenderable(HelloWorld); + + +``` + +Note that react components should be defined using jsx or tsx. Using jsx/tsx syntax in js/ts is confusing[^and-incorrect] and should be avoided. + +[^and-incorrect]: and incorrect -- the impact of JSX and TSX being supported in JS and TS files without the `x` extension has wreaked tons of havoc on the broader JavaScript ecosystem. + +### Accessing the owner + +```jsx +import { getOwner } from '@ember/owner'; + +function MyReactComponent(props) { + let owner = getOwner(props); + let store = owner.lookup('service:store'); + + return <> + ... do something with the store ... + ; +} + +``` + +## Testing + +testing with React components is a bit harder than with native ember components, because react testing doesn't have any sort of test-waiter system. The test-waiter system is something library-devs use to make testing easier for app developers, so that app develpers never need to worry about `waitUntil`-style timing. + +That said, all `@ember/test-helpers` should still work with React subtrees. +Just the same, `testing-library` works well across both frameworks. + +But, in React, it's very important to minimize the number of effects use, preferring data derivation, and only using effects as a last resort. + + +### Examples + + +These examples come from this library's own test suite. ```gjs import { Greet, HelloWorld } from './hello-world.tsx'; @@ -40,19 +92,6 @@ module('makeRenderable', function (hooks) { }); ``` -Note that react components should be defined using jsx or tsx. Using jsx/tsx syntax in js/ts is confusing[^and-incorrect] and should be avoided. - -[^and-incorrect]: and incorrect -- the impact of JSX and TSX being supported in JS and TS files without the `x` extension has wreaked tons of havoc on the broader JavaScript ecosystem. - -## Testing - -testing with React components is a bit harder than with native ember components, because react testing doesn't have any sort of test-waiter system. The test-waiter system is something library-devs use to make testing easier for app developers, so that app develpers never need to worry about `waitUntil`-style timing. - -That said, all `@ember/test-helpers` should still work with React subtrees. -Just the same, `testing-library` works well across both frameworks. - -But, in React, it's very important to minimize the number of effects use, preferring data derivation, and only using effects as a last resort. - ## Contributing diff --git a/src/render-react.ts b/src/render-react.ts index 2dd85ce..378a769 100644 --- a/src/render-react.ts +++ b/src/render-react.ts @@ -3,9 +3,11 @@ import { waitForPromise } from '@ember/test-waiters'; import { setComponentTemplate } from '@ember/component'; import Modifier from 'ember-modifier'; import type { ComponentLike } from '@glint/template'; -import React from 'react'; +import React, { act } from 'react'; import { createRoot } from 'react-dom/client'; import Component from '@glimmer/component'; +import { macroCondition, isTesting } from '@embroider/macros'; +import { setOwner, getOwner } from '@ember/owner'; interface Signature { Args: { @@ -22,7 +24,14 @@ export function makeRenderable< >(ReactComponent: ReactComponentType): ComponentLike> { class ReactRoot extends Component> { get props() { - return this.args.props ? { ...this.args.props } : { ...this.args }; + const owner = getOwner(this); + const props = this.args.props ? { ...this.args.props } : { ...this.args }; + + if (owner) { + setOwner(props, owner); + } + + return props; } } return setComponentTemplate( @@ -53,8 +62,27 @@ class mount extends Modifier<{ ) { this.#root ||= createRoot(element); - this.#root.render(React.createElement(component as any, props)); + const toRender = React.createElement(component as any, props); + /** + * Subsequent re-renders will diff and replace contents as needed. + */ + if (macroCondition(isTesting())) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (window as any).IS_REACT_ACT_ENVIRONMENT = true; + act(() => { + this.#root?.render(toRender); + }); + } else { + this.#root.render(toRender); + } + /** + * For ember's test waiter system. + * We don't know how long a react component will take to render, + * but it often doesn't finish synchronously. + * + * Waiting until the next animation frame before test executions continues. + */ waitForPromise( (async () => { await new Promise((resolve) => { From ee54105e591698c2455d4f84fb93838610393df5 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:15:51 -0400 Subject: [PATCH 2/3] Updates to testing --- .try.mjs | 51 ------------------------------------------------- README.md | 2 +- vite.config.mjs | 7 ++----- 3 files changed, 3 insertions(+), 57 deletions(-) diff --git a/.try.mjs b/.try.mjs index 40bf32c..800056c 100644 --- a/.try.mjs +++ b/.try.mjs @@ -1,56 +1,5 @@ -// When building your addon for older Ember versions you need to have the required files -const compatFiles = { - 'ember-cli-build.js': `const EmberApp = require('ember-cli/lib/broccoli/ember-app'); -const { compatBuild } = require('@embroider/compat'); -module.exports = async function (defaults) { - const { buildOnce } = await import('@embroider/vite'); - let app = new EmberApp(defaults); - return compatBuild(app, buildOnce); -};`, - 'config/optional-features.json': JSON.stringify({ - 'application-template-wrapper': false, - 'default-async-observers': true, - 'jquery-integration': false, - 'template-only-glimmer-components': true, - 'no-implicit-route-model': true, - }), -}; - -const compatDeps = { - '@embroider/compat': '^4.0.3', - 'ember-cli': '^5.12.0', - 'ember-auto-import': '^2.10.0', - '@ember/optional-features': '^2.2.0', -}; - export default { scenarios: [ - { - name: 'ember-lts-5.8', - npm: { - devDependencies: { - 'ember-source': '~5.8.0', - ...compatDeps, - }, - }, - env: { - ENABLE_COMPAT_BUILD: true, - }, - files: compatFiles, - }, - { - name: 'ember-lts-5.12', - npm: { - devDependencies: { - 'ember-source': '~5.12.0', - ...compatDeps, - }, - }, - env: { - ENABLE_COMPAT_BUILD: true, - }, - files: compatFiles, - }, { name: `ember-lts-6.4`, npm: { diff --git a/README.md b/README.md index 5c951b3..f0fa52a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ React integration for Ember with reactive updating. ## Compatibility -- Ember.js v5.8 or above +- Ember.js v6.3 or above, not that earlier won't work. But this repo isn't testing prior to 6.3 ## Installation diff --git a/vite.config.mjs b/vite.config.mjs index 4d4e957..8800d93 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -1,15 +1,11 @@ import { defineConfig } from 'vite'; -import { extensions, ember, classicEmberSupport } from '@embroider/vite'; +import { extensions, ember } from '@embroider/vite'; import { babel } from '@rollup/plugin-babel'; import react from '@vitejs/plugin-react'; -// For scenario testing -const isCompat = Boolean(process.env.ENABLE_COMPAT_BUILD); - export default defineConfig({ plugins: [ - ...(isCompat ? [classicEmberSupport()] : []), ember(), react(), babel({ @@ -18,6 +14,7 @@ export default defineConfig({ }), ], build: { + minify: false, rollupOptions: { input: { tests: 'tests/index.html', From d8325968aec57e181028c19b16d531b5f0af79f8 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:17:34 -0400 Subject: [PATCH 3/3] Fix tests --- vite.config.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vite.config.mjs b/vite.config.mjs index 8800d93..2cf607a 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -1,4 +1,4 @@ -import { defineConfig } from 'vite'; +import { defineConfig, splitVendorChunkPlugin } from 'vite'; import { extensions, ember } from '@embroider/vite'; import { babel } from '@rollup/plugin-babel'; @@ -6,6 +6,7 @@ import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [ + splitVendorChunkPlugin(), ember(), react(), babel({