Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbed0b0ee6 | ||
|
|
f3e3f6fbac | ||
|
|
154f4b15e2 |
@@ -303,6 +303,7 @@ module.exports = {
|
||||
ERROR,
|
||||
{isProductionUserAppCode: true},
|
||||
],
|
||||
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
|
||||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
},
|
||||
@@ -329,7 +330,6 @@ module.exports = {
|
||||
'packages/react-server-dom-esm/**/*.js',
|
||||
'packages/react-server-dom-webpack/**/*.js',
|
||||
'packages/react-server-dom-turbopack/**/*.js',
|
||||
'packages/react-server-dom-parcel/**/*.js',
|
||||
'packages/react-server-dom-fb/**/*.js',
|
||||
'packages/react-test-renderer/**/*.js',
|
||||
'packages/react-debug-tools/**/*.js',
|
||||
@@ -481,12 +481,6 @@ module.exports = {
|
||||
__turbopack_require__: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/react-server-dom-parcel/**/*.js'],
|
||||
globals: {
|
||||
parcelRequire: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/scheduler/**/*.js'],
|
||||
globals: {
|
||||
|
||||
17
.github/workflows/compiler_playground.yml
vendored
17
.github/workflows/compiler_playground.yml
vendored
@@ -6,10 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- compiler/**
|
||||
- .github/workflows/compiler_playground.yml
|
||||
- .github/workflows/compiler-playground.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
@@ -38,14 +38,15 @@ jobs:
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: yarn install compiler
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: compiler
|
||||
- name: yarn install playground
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- run: CI=true yarn test
|
||||
- run: ls -R test-results
|
||||
if: '!cancelled()'
|
||||
- run: yarn test
|
||||
- name: Archive test results
|
||||
if: '!cancelled()'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/apps/playground/test-results
|
||||
path: test-results
|
||||
|
||||
2
.github/workflows/compiler_rust.yml
vendored
2
.github/workflows/compiler_rust.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
- compiler/*.toml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
2
.github/workflows/compiler_typescript.yml
vendored
2
.github/workflows/compiler_typescript.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- .github/workflows/compiler_typescript.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
34
.github/workflows/runtime_build_and_test.yml
vendored
34
.github/workflows/runtime_build_and_test.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
- compiler/**
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
@@ -25,8 +25,6 @@ jobs:
|
||||
matrix: ${{ steps.set-matrix.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/github-script@v7
|
||||
id: set-matrix
|
||||
with:
|
||||
@@ -44,8 +42,6 @@ jobs:
|
||||
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -68,8 +64,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -94,8 +88,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -147,8 +139,6 @@ jobs:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -176,8 +166,6 @@ jobs:
|
||||
release_channel: [stable, experimental]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -254,8 +242,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -286,8 +272,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -333,8 +317,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -368,8 +350,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -400,8 +380,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -441,8 +419,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -504,8 +480,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -554,8 +528,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -590,8 +562,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -633,8 +603,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
# because this used to be called the "next" channel and some
|
||||
# downstream consumers might still expect that tag. We can remove this
|
||||
# after some time has elapsed and the change has been communicated.
|
||||
dist_tag: canary,next
|
||||
dist_tag: canary,next,rc
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: stable
|
||||
dist_tag: canary,next
|
||||
dist_tag: canary,next,rc
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
|
||||
21
.github/workflows/shared_discord_notify.yml
vendored
21
.github/workflows/shared_discord_notify.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: (Shared) Discord Notify
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
if: ${{ github.event.label.name == 'React Core Team' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v6.0.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: ${{ github.event.pull_request.user.login }}
|
||||
embed-author-url: ${{ github.event.pull_request.user.html_url }}
|
||||
embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
|
||||
embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
|
||||
embed-description: ${{ github.event.pull_request.body }}
|
||||
embed-url: ${{ github.event.pull_request.html_url }}
|
||||
2
.github/workflows/shared_lint.yml
vendored
2
.github/workflows/shared_lint.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
272
CHANGELOG.md
272
CHANGELOG.md
@@ -1,275 +1,3 @@
|
||||
## 19.0.0 (December 5, 2024)
|
||||
|
||||
Below is a list of all new features, APIs, deprecations, and breaking changes. Read [React 19 release post](https://react.dev/blog/2024/04/25/react-19) and [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more information.
|
||||
|
||||
> Note: To help make the upgrade to React 19 easier, we’ve published a react@18.3 release that is identical to 18.2 but adds warnings for deprecated APIs and other changes that are needed for React 19. We recommend upgrading to React 18.3.1 first to help identify any issues before upgrading to React 19.
|
||||
|
||||
### New Features
|
||||
|
||||
#### React
|
||||
|
||||
* Actions: `startTransition` can now accept async functions. Functions passed to `startTransition` are called “Actions”. A given Transition can include one or more Actions which update state in the background and update the UI with one commit. In addition to updating state, Actions can now perform side effects including async requests, and the Action will wait for the work to finish before finishing the Transition. This feature allows Transitions to include side effects like `fetch()` in the pending state, and provides support for error handling, and optimistic updates.
|
||||
* `useActionState`: is a new hook to order Actions inside of a Transition with access to the state of the action, and the pending state. It accepts a reducer that can call Actions, and the initial state used for first render. It also accepts an optional string that is used if the action is passed to a form `action` prop to support progressive enhancement in forms.
|
||||
* `useOptimistic`: is a new hook to update state while a Transition is in progress. It returns the state, and a set function that can be called inside a transition to “optimistically” update the state to expected final value immediately while the Transition completes in the background. When the transition finishes, the state is updated to the new value.
|
||||
* `use`: is a new API that allows reading resources in render. In React 19, `use` accepts a promise or Context. If provided a promise, `use` will suspend until a value is resolved. `use` can only be used in render but can be called conditionally.
|
||||
* `ref` as a prop: Refs can now be used as props, removing the need for `forwardRef`.
|
||||
* **Suspense sibling pre-warming**: When a component suspends, React will immediately commit the fallback of the nearest Suspense boundary, without waiting for the entire sibling tree to render. After the fallback commits, React will schedule another render for the suspended siblings to “pre-warm” lazy requests.
|
||||
|
||||
#### React DOM Client
|
||||
|
||||
* `<form> action` prop: Form Actions allow you to manage forms automatically and integrate with `useFormStatus`. When a `<form> action` succeeds, React will automatically reset the form for uncontrolled components. The form can be reset manually with the new `requestFormReset` API.
|
||||
* `<button> and <input> formAction` prop: Actions can be passed to the `formAction` prop to configure form submission behavior. This allows using different Actions depending on the input.
|
||||
* `useFormStatus`: is a new hook that provides the status of the parent `<form> action`, as if the form was a Context provider. The hook returns the values: `pending`, `data`, `method`, and `action`.
|
||||
* Support for Document Metadata: We’ve added support for rendering document metadata tags in components natively. React will automatically hoist them into the `<head>` section of the document.
|
||||
* Support for Stylesheets: React 19 will ensure stylesheets are inserted into the `<head>` on the client before revealing the content of a Suspense boundary that depends on that stylesheet.
|
||||
* Support for async scripts: Async scripts can be rendered anywhere in the component tree and React will handle ordering and deduplication.
|
||||
* Support for preloading resources: React 19 ships with `preinit`, `preload`, `prefetchDNS`, and `preconnect` APIs to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also be used to prefetch resources used by an anticipated navigation.
|
||||
|
||||
#### React DOM Server
|
||||
|
||||
* Added `prerender` and `prerenderToNodeStream` APIs for static site generation. They are designed to work with streaming environments like Node.js Streams and Web Streams. Unlike `renderToString`, they wait for data to load for HTML generation.
|
||||
|
||||
#### React Server Components
|
||||
|
||||
* RSC features such as directives, server components, and server functions are now stable. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a react-server export condition for use in frameworks that support the Full-stack React Architecture. The underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. See [docs](https://19.react.dev/reference/rsc/server-components) for how to support React Server Components.
|
||||
|
||||
### Deprecations
|
||||
|
||||
* Deprecated: `element.ref` access: React 19 supports ref as a prop, so we’re deprecating `element.ref` in favor of `element.props.ref`. Accessing will result in a warning.
|
||||
* `react-test-renderer`: In React 19, react-test-renderer logs a deprecation warning and has switched to concurrent rendering for web usage. We recommend migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://testing-library.com/docs/react-native-testing-library/intro)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
React 19 brings in a number of breaking changes, including the removals of long-deprecated APIs. We recommend first upgrading to `18.3.1`, where we've added additional deprecation warnings. Check out the [upgrade guide](https://19.react.dev/blog/2024/04/25/react-19-upgrade-guide) for more details and guidance on codemodding.
|
||||
|
||||
### React
|
||||
|
||||
* New JSX Transform is now required: We introduced [a new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) in 2020 to improve bundle size and use JSX without importing React. In React 19, we’re adding additional improvements like using ref as a prop and JSX speed improvements that require the new transform.
|
||||
* Errors in render are not re-thrown: Errors that are not caught by an Error Boundary are now reported to window.reportError. Errors that are caught by an Error Boundary are reported to console.error. We’ve introduced `onUncaughtError` and `onCaughtError` methods to `createRoot` and `hydrateRoot` to customize this error handling.
|
||||
* Removed: `propTypes`: Using `propTypes` will now be silently ignored. If required, we recommend migrating to TypeScript or another type-checking solution.
|
||||
* Removed: `defaultProps` for functions: ES6 default parameters can be used in place. Class components continue to support `defaultProps` since there is no ES6 alternative.
|
||||
* Removed: `contextTypes` and `getChildContext`: Legacy Context for class components has been removed in favor of the `contextType` API.
|
||||
* Removed: string refs: Any usage of string refs need to be migrated to ref callbacks.
|
||||
* Removed: Module pattern factories: A rarely used pattern that can be migrated to regular functions.
|
||||
* Removed: `React.createFactory`: Now that JSX is broadly supported, all `createFactory` usage can be migrated to JSX components.
|
||||
* Removed: `react-test-renderer/shallow`: This has been a re-export of [react-shallow-renderer](https://github.com/enzymejs/react-shallow-renderer) since React 18\. If needed, you can continue to use the third-party package directly. We recommend using [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://testing-library.com/docs/react-native-testing-library/intro) instead.
|
||||
|
||||
#### React DOM
|
||||
|
||||
* Removed: `react-dom/test-utils`: We’ve moved `act` from `react-dom/test-utils` to react. All other utilities have been removed.
|
||||
* Removed: `ReactDOM`.`render`, `ReactDOM`.`hydrate`: These have been removed in favor of the concurrent equivalents: `ReactDOM`.`createRoot` and `ReactDOM.hydrateRoot`.
|
||||
* Removed: `unmountComponentAtNode`: Removed in favor of `root.unmount()`.
|
||||
* Removed: `ReactDOM`.`findDOMNode`: You can replace `ReactDOM`.`findDOMNode` with DOM Refs.
|
||||
|
||||
### Notable Changes
|
||||
|
||||
#### React
|
||||
|
||||
* `<Context>` as a provider: You can now render `<Context>` as a provider instead of `<Context.Provider>`.
|
||||
* Cleanup functions for refs: When the component unmounts, React will call the cleanup function returned from the ref callback.
|
||||
* `useDeferredValue` initial value argument: When provided, `useDeferredValue` will return the initial value for the initial render of a component, then schedule a re-render in the background with the `deferredValue` returned.
|
||||
* Support for Custom Elements: React 19 now passes all tests on [Custom Elements Everywhere](https://custom-elements-everywhere.com/).
|
||||
* StrictMode changes: `useMemo` and `useCallback` will now reuse the memoized results from the first render, during the second render. Additionally, StrictMode will now double-invoke ref callback functions on initial mount.
|
||||
* UMD builds removed: To load React 19 with a script tag, we recommend using an ESM-based CDN such as [esm.sh](http://esm.sh).
|
||||
|
||||
#### React DOM
|
||||
|
||||
* Diffs for hydration errors: In the case of a mismatch, React 19 logs a single error with a diff of the mismatched content.
|
||||
* Compatibility with third-party scripts and extensions: React will now force a client re-render to fix up any mismatched content caused by elements inserted by third-party JS.
|
||||
|
||||
### TypeScript Changes
|
||||
|
||||
The most common changes can be codemodded with `npx types-react-codemod@latest preset-19 ./path-to-your-react-ts-files`.
|
||||
|
||||
* Removed deprecated TypeScript types:
|
||||
* `ReactChild` (replacement: `React.ReactElement | number | string)`
|
||||
* `ReactFragment` (replacement: `Iterable<React.ReactNode>`)
|
||||
* `ReactNodeArray` (replacement: `ReadonlyArray<React.ReactNode>`)
|
||||
* `ReactText` (replacement: `number | string`)
|
||||
* `VoidFunctionComponent` (replacement: `FunctionComponent`)
|
||||
* `VFC` (replacement: `FC`)
|
||||
* Moved to `prop-types`: `Requireable`, `ValidationMap`, `Validator`, `WeakValidationMap`
|
||||
* Moved to `create-react-class`: `ClassicComponentClass`, `ClassicComponent`, `ClassicElement`, `ComponentSpec`, `Mixin`, `ReactChildren`, `ReactHTML`, `ReactSVG`, `SFCFactory`
|
||||
* Disallow implicit return in refs: refs can now accept cleanup functions. When you return something else, we can’t tell if you intentionally returned something not meant to clean up or returned the wrong value. Implicit returns of anything but functions will now error.
|
||||
* Require initial argument to `useRef`: The initial argument is now required to match `useState`, `createContext` etc
|
||||
* Refs are mutable by default: Ref objects returned from `useRef()` are now always mutable instead of sometimes being immutable. This feature was too confusing for users and conflicted with legit cases where refs were managed by React and manually written to.
|
||||
* Strict `ReactElement` typing: The props of React elements now default to `unknown` instead of `any` if the element is typed as `ReactElement`
|
||||
* JSX namespace in TypeScript: The global `JSX` namespace is removed to improve interoperability with other libraries using JSX. Instead, the JSX namespace is available from the React package: `import { JSX } from 'react'`
|
||||
* Better `useReducer` typings: Most `useReducer` usage should not require explicit type arguments.
|
||||
For example,
|
||||
```diff
|
||||
-useReducer<React.Reducer<State, Action>>(reducer)
|
||||
+useReducer(reducer)
|
||||
```
|
||||
or
|
||||
```diff
|
||||
-useReducer<React.Reducer<State, Action>>(reducer)
|
||||
+useReducer<State, Action>(reducer)
|
||||
```
|
||||
|
||||
### All Changes
|
||||
|
||||
#### React
|
||||
|
||||
* Add support for async Actions ([\#26621](https://github.com/facebook/react/pull/26621), [\#26726](https://github.com/facebook/react/pull/26726), [\#28078](https://github.com/facebook/react/pull/28078), [\#28097](https://github.com/facebook/react/pull/28097), [\#29226](https://github.com/facebook/react/pull/29226), [\#29618](https://github.com/facebook/react/pull/29618), [\#29670](https://github.com/facebook/react/pull/29670), [\#26716](https://github.com/facebook/react/pull/26716) by [@acdlite](https://github.com/acdlite) and [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Add `useActionState()` hook to update state based on the result of a Form Action ([\#27270](https://github.com/facebook/react/pull/27270), [\#27278](https://github.com/facebook/react/pull/27278), [\#27309](https://github.com/facebook/react/pull/27309), [\#27302](https://github.com/facebook/react/pull/27302), [\#27307](https://github.com/facebook/react/pull/27307), [\#27366](https://github.com/facebook/react/pull/27366), [\#27370](https://github.com/facebook/react/pull/27370), [\#27321](https://github.com/facebook/react/pull/27321), [\#27374](https://github.com/facebook/react/pull/27374), [\#27372](https://github.com/facebook/react/pull/27372), [\#27397](https://github.com/facebook/react/pull/27397), [\#27399](https://github.com/facebook/react/pull/27399), [\#27460](https://github.com/facebook/react/pull/27460), [\#28557](https://github.com/facebook/react/pull/28557), [\#27570](https://github.com/facebook/react/pull/27570), [\#27571](https://github.com/facebook/react/pull/27571), [\#28631](https://github.com/facebook/react/pull/28631), [\#28788](https://github.com/facebook/react/pull/28788), [\#29694](https://github.com/facebook/react/pull/29694), [\#29695](https://github.com/facebook/react/pull/29695), [\#29694](https://github.com/facebook/react/pull/29694), [\#29665](https://github.com/facebook/react/pull/29665), [\#28232](https://github.com/facebook/react/pull/28232), [\#28319](https://github.com/facebook/react/pull/28319) by [@acdlite](https://github.com/acdlite), [@eps1lon](https://github.com/eps1lon), and [@rickhanlonii](https://github.com/rickhanlonii))
|
||||
* Add `use()` API to read resources in render ([\#25084](https://github.com/facebook/react/pull/25084), [\#25202](https://github.com/facebook/react/pull/25202), [\#25207](https://github.com/facebook/react/pull/25207), [\#25214](https://github.com/facebook/react/pull/25214), [\#25226](https://github.com/facebook/react/pull/25226), [\#25247](https://github.com/facebook/react/pull/25247), [\#25539](https://github.com/facebook/react/pull/25539), [\#25538](https://github.com/facebook/react/pull/25538), [\#25537](https://github.com/facebook/react/pull/25537), [\#25543](https://github.com/facebook/react/pull/25543), [\#25561](https://github.com/facebook/react/pull/25561), [\#25620](https://github.com/facebook/react/pull/25620), [\#25615](https://github.com/facebook/react/pull/25615), [\#25922](https://github.com/facebook/react/pull/25922), [\#25641](https://github.com/facebook/react/pull/25641), [\#25634](https://github.com/facebook/react/pull/25634), [\#26232](https://github.com/facebook/react/pull/26232), [\#26536](https://github.com/facebook/react/pull/26535), [\#26739](https://github.com/facebook/react/pull/26739), [\#28233](https://github.com/facebook/react/pull/28233) by [@acdlite](https://github.com/acdlite), [@MofeiZ](https://github.com/mofeiZ), [@sebmarkbage](https://github.com/sebmarkbage), [@sophiebits](https://github.com/sophiebits), [@eps1lon](https://github.com/eps1lon), and [@hansottowirtz](https://github.com/hansottowirtz))
|
||||
* Add `useOptimistic()` hook to display mutated state optimistically during an async mutation ([\#26740](https://github.com/facebook/react/pull/26740), [\#26772](https://github.com/facebook/react/pull/26772), [\#27277](https://github.com/facebook/react/pull/27277), [\#27453](https://github.com/facebook/react/pull/27453), [\#27454](https://github.com/facebook/react/pull/27454), [\#27936](https://github.com/facebook/react/pull/27936) by [@acdlite](https://github.com/acdlite))
|
||||
* Added an `initialValue` argument to `useDeferredValue()` hook ([\#27500](https://github.com/facebook/react/pull/27500), [\#27509](https://github.com/facebook/react/pull/27509), [\#27512](https://github.com/facebook/react/pull/27512), [\#27888](https://github.com/facebook/react/pull/27888), [\#27550](https://github.com/facebook/react/pull/27550) by [@acdlite](https://github.com/acdlite))
|
||||
* Support refs as props, warn on `element.ref` access ([\#28348](https://github.com/facebook/react/pull/28348), [\#28464](https://github.com/facebook/react/pull/28464), [\#28731](https://github.com/facebook/react/pull/28731) by [@acdlite](https://github.com/acdlite))
|
||||
* Support Custom Elements ([\#22184](https://github.com/facebook/react/pull/22184), [\#26524](https://github.com/facebook/react/pull/26524), [\#26523](https://github.com/facebook/react/pull/26523), [\#27511](https://github.com/facebook/react/pull/27511), [\#24541](https://github.com/facebook/react/pull/24541) by [@josepharhar](https://github.com/josepharhar), [@sebmarkbage](https://github.com/sebmarkbage), [@gnoff](https://github.com/gnoff) and [@eps1lon](https://github.com/eps1lon))
|
||||
* Add ref cleanup function ([\#25686](https://github.com/facebook/react/pull/25686), [\#28883](https://github.com/facebook/react/pull/28883), [\#28910](https://github.com/facebook/react/pull/28910) by [@sammy-SC](https://github.com/sammy-SC), [@jackpope](https://github.com/jackpope), and [@kassens](https://github.com/kassens))
|
||||
* Sibling pre-rendering replaced by sibling pre-warming ([\#26380](https://github.com/facebook/react/pull/26380), [\#26549](https://github.com/facebook/react/pull/26549), [\#30761](https://github.com/facebook/react/pull/30761), [\#30800](https://github.com/facebook/react/pull/30800), [\#30762](https://github.com/facebook/react/pull/30762), [\#30879](https://github.com/facebook/react/pull/30879), [\#30934](https://github.com/facebook/react/pull/30934), [\#30952](https://github.com/facebook/react/pull/30952), [\#31056](https://github.com/facebook/react/pull/31056), [\#31452](https://github.com/facebook/react/pull/31452) by [@sammy-SC](https://github.com/sammy-SC), [@acdlite](https://github.com/acdlite), [@gnoff](https://github.com/gnoff), [@jackpope](https://github.com/jackpope), [@rickhanlonii](https://github.com/rickhanlonii))
|
||||
* Don’t rethrow errors at the root ([\#28627](https://github.com/facebook/react/pull/28627), [\#28641](https://github.com/facebook/react/pull/28641) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Batch sync discrete, continuous, and default lanes ([\#25700](https://github.com/facebook/react/pull/25700) by [@tyao1](https://github.com/tyao1))
|
||||
* Switch `<Context>` to mean `<Context.Provider>` ([\#28226](https://github.com/facebook/react/pull/28226) by [@gaearon](https://github.com/gaearon))
|
||||
* Changes to *StrictMode*
|
||||
* Handle `info`, `group`, and `groupCollapsed` in *StrictMode* logging ([\#25172](https://github.com/facebook/react/pull/25172) by [@timneutkens](https://github.com/timneutkens))
|
||||
* Refs are now attached/detached/attached in *StrictMode* ([\#25049](https://github.com/facebook/react/pull/25049) by [@sammy-SC](https://github.com/sammy-SC))
|
||||
* Fix `useSyncExternalStore()` hydration in *StrictMode* ([\#26791](https://github.com/facebook/react/pull/26791) by [@sophiebits](https://github.com/sophiebits))
|
||||
* Always trigger `componentWillUnmount()` in *StrictMode* ([\#26842](https://github.com/facebook/react/pull/26842) by [@tyao1](https://github.com/tyao1))
|
||||
* Restore double invoking `useState()` and `useReducer()` initializer functions in *StrictMode* ([\#28248](https://github.com/facebook/react/pull/28248) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Reuse memoized result from first pass ([\#25583](https://github.com/facebook/react/pull/25583) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix `useId()` in *StrictMode* ([\#25713](https://github.com/facebook/react/pull/25713) by [@gnoff](https://github.com/gnoff))
|
||||
* Add component name to *StrictMode* error messages ([\#25718](https://github.com/facebook/react/pull/25718) by [@sammy-SC](https://github.com/sammy-SC))
|
||||
* Add support for rendering BigInt ([\#24580](https://github.com/facebook/react/pull/24580) by [@eps1lon](https://github.com/eps1lon))
|
||||
* `act()` no longer checks `shouldYield` which can be inaccurate in test environments ([\#26317](https://github.com/facebook/react/pull/26317) by [@acdlite](https://github.com/acdlite))
|
||||
* Warn when keys are spread with props ([\#25697](https://github.com/facebook/react/pull/25697), [\#26080](https://github.com/facebook/react/pull/26080) by [@sebmarkbage](https://github.com/sebmarkbage) and [@kassens](https://github.com/kassens))
|
||||
* Generate sourcemaps for production build artifacts ([\#26446](https://github.com/facebook/react/pull/26446) by [@markerikson](https://github.com/markerikson))
|
||||
* Improve stack diffing algorithm ([\#27132](https://github.com/facebook/react/pull/27132) by [@KarimP](https://github.com/KarimP))
|
||||
* Suspense throttling lowered from 500ms to 300ms ([\#26803](https://github.com/facebook/react/pull/26803) by [@acdlite](https://github.com/acdlite))
|
||||
* Lazily propagate context changes ([\#20890](https://github.com/facebook/react/pull/20890) by [@acdlite](https://github.com/acdlite) and [@gnoff](https://github.com/gnoff))
|
||||
* Immediately rerender pinged fiber ([\#25074](https://github.com/facebook/react/pull/25074) by [@acdlite](https://github.com/acdlite))
|
||||
* Move update scheduling to microtask ([\#26512](https://github.com/facebook/react/pull/26512) by [@acdlite](https://github.com/acdlite))
|
||||
* Consistently apply throttled retries ([\#26611](https://github.com/facebook/react/pull/26611), [\#26802](https://github.com/facebook/react/pull/26802) by [@acdlite](https://github.com/acdlite))
|
||||
* Suspend Thenable/Lazy if it's used in React.Children ([\#28284](https://github.com/facebook/react/pull/28284) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Detect infinite update loops caused by render phase updates ([\#26625](https://github.com/facebook/react/pull/26625) by [@acdlite](https://github.com/acdlite))
|
||||
* Update conditional hooks warning ([\#29626](https://github.com/facebook/react/pull/29626) by [@sophiebits](https://github.com/sophiebits))
|
||||
* Update error URLs to go to new docs ([\#27240](https://github.com/facebook/react/pull/27240) by [@rickhanlonii](https://github.com/rickhanlonii))
|
||||
* Rename the `react.element` symbol to `react.transitional.element` ([\#28813](https://github.com/facebook/react/pull/28813) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Fix crash when suspending in shell during `useSyncExternalStore()` re-render ([\#27199](https://github.com/facebook/react/pull/27199) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix incorrect “detected multiple renderers" error in tests ([\#22797](https://github.com/facebook/react/pull/22797) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Fix bug where effect cleanup may be called twice after bailout ([\#26561](https://github.com/facebook/react/pull/26561) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix suspending in shell during discrete update ([\#25495](https://github.com/facebook/react/pull/25495) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix memory leak after repeated setState bailouts ([\#25309](https://github.com/facebook/react/pull/25309) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix `useSyncExternalStore()` dropped update when state is dispatched in render phase ([\#25578](https://github.com/facebook/react/pull/25578) by [@pandaiolo](https://github.com/pandaiolo))
|
||||
* Fix logging when rendering a lazy fragment ([\#30372](https://github.com/facebook/react/pull/30372) by [@tom-sherman](https://github.com/tom-sherman))
|
||||
* Remove string refs ([\#25383](https://github.com/facebook/react/pull/25383), [\#28322](https://github.com/facebook/react/pull/28322) by [@eps1lon](https://github.com/eps1lon) and [@acdlite](https://github.com/acdlite))
|
||||
* Remove Legacy Context (\#30319 by [@kassens](https://github.com/kassens))
|
||||
* Remove `RefreshRuntime.findAffectedHostInstances` ([\#30538](https://github.com/facebook/react/pull/30538) by [@gaearon](https://github.com/gaearon))
|
||||
* Remove client caching from `cache()` API ([\#27977](https://github.com/facebook/react/pull/27977), [\#28250](https://github.com/facebook/react/pull/28250) by [@acdlite](https://github.com/acdlite) and [@gnoff](https://github.com/gnoff))
|
||||
* Remove `propTypes` ([\#28324](https://github.com/facebook/react/pull/28324), [\#28326](https://github.com/facebook/react/pull/28326) by [@gaearon](https://github.com/gaearon))
|
||||
* Remove `defaultProps` support, except for classes ([\#28733](https://github.com/facebook/react/pull/28733) by [@acdlite](https://github.com/acdlite))
|
||||
* Remove UMD builds ([\#28735](https://github.com/facebook/react/pull/28735) by [@gnoff](https://github.com/gnoff))
|
||||
* Remove delay for non-transition updates ([\#26597](https://github.com/facebook/react/pull/26597) by [@acdlite](https://github.com/acdlite))
|
||||
* Remove `createFactory` ([\#27798](https://github.com/facebook/react/pull/27798) by [@kassens](https://github.com/kassens))
|
||||
|
||||
#### React DOM
|
||||
|
||||
* Adds Form Actions to handle form submission ([\#26379](https://github.com/facebook/react/pull/26379), [\#26674](https://github.com/facebook/react/pull/26674), [\#26689](https://github.com/facebook/react/pull/26689), [\#26708](https://github.com/facebook/react/pull/26708), [\#26714](https://github.com/facebook/react/pull/26714), [\#26735](https://github.com/facebook/react/pull/26735), [\#26846](https://github.com/facebook/react/pull/26846), [\#27358](https://github.com/facebook/react/pull/27358), [\#28056](https://github.com/facebook/react/pull/28056) by [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), and [@jupapios](https://github.com/jupapios))
|
||||
* Add `useFormStatus()` hook to provide status information of the last form submission ([\#26719](https://github.com/facebook/react/pull/26719), [\#26722](https://github.com/facebook/react/pull/26722), [\#26788](https://github.com/facebook/react/pull/26788), [\#29019](https://github.com/facebook/react/pull/29019), [\#28728](https://github.com/facebook/react/pull/28728), [\#28413](https://github.com/facebook/react/pull/28413) by [@acdlite](https://github.com/acdlite) and [@eps1lon](https://github.com/eps1lon))
|
||||
* Support for Document Metadata. Adds `preinit`, `preinitModule`, `preconnect`, `prefetchDNS`, `preload`, and `preloadModule` APIs.
|
||||
* [\#25060](https://github.com/facebook/react/pull/25060), [\#25243](https://github.com/facebook/react/pull/25243), [\#25388](https://github.com/facebook/react/pull/25388), [\#25432](https://github.com/facebook/react/pull/25432), [\#25436](https://github.com/facebook/react/pull/25436), [\#25426](https://github.com/facebook/react/pull/25426), [\#25500](https://github.com/facebook/react/pull/25500), [\#25480](https://github.com/facebook/react/pull/25480), [\#25508](https://github.com/facebook/react/pull/25508), [\#25515](https://github.com/facebook/react/pull/25515), [\#25514](https://github.com/facebook/react/pull/25514), [\#25532](https://github.com/facebook/react/pull/25532), [\#25536](https://github.com/facebook/react/pull/25536), [\#25534](https://github.com/facebook/react/pull/25534), [\#25546](https://github.com/facebook/react/pull/25546), [\#25559](https://github.com/facebook/react/pull/25559), [\#25569](https://github.com/facebook/react/pull/25569), [\#25599](https://github.com/facebook/react/pull/25599), [\#25689](https://github.com/facebook/react/pull/25689), [\#26106](https://github.com/facebook/react/pull/26106), [\#26152](https://github.com/facebook/react/pull/26152), [\#26239](https://github.com/facebook/react/pull/26239), [\#26237](https://github.com/facebook/react/pull/26237), [\#26280](https://github.com/facebook/react/pull/26280), [\#26154](https://github.com/facebook/react/pull/26154), [\#26256](https://github.com/facebook/react/pull/26256), [\#26353](https://github.com/facebook/react/pull/26353), [\#26427](https://github.com/facebook/react/pull/26427), [\#26450](https://github.com/facebook/react/pull/26450), [\#26502](https://github.com/facebook/react/pull/26502), [\#26514](https://github.com/facebook/react/pull/26514), [\#26531](https://github.com/facebook/react/pull/26531), [\#26532](https://github.com/facebook/react/pull/26532), [\#26557](https://github.com/facebook/react/pull/26557), [\#26871](https://github.com/facebook/react/pull/26871), [\#26881](https://github.com/facebook/react/pull/26881), [\#26877](https://github.com/facebook/react/pull/26877), [\#26873](https://github.com/facebook/react/pull/26873), [\#26880](https://github.com/facebook/react/pull/26880), [\#26942](https://github.com/facebook/react/pull/26942), [\#26938](https://github.com/facebook/react/pull/26938), [\#26940](https://github.com/facebook/react/pull/26940), [\#26939](https://github.com/facebook/react/pull/26939), [\#27030](https://github.com/facebook/react/pull/27030), [\#27201](https://github.com/facebook/react/pull/27201), [\#27212](https://github.com/facebook/react/pull/27212), [\#27217](https://github.com/facebook/react/pull/27217), [\#27218](https://github.com/facebook/react/pull/27218), [\#27220](https://github.com/facebook/react/pull/27220), [\#27224](https://github.com/facebook/react/pull/27224), [\#27223](https://github.com/facebook/react/pull/27223), [\#27269](https://github.com/facebook/react/pull/27269), [\#27260](https://github.com/facebook/react/pull/27260), [\#27347](https://github.com/facebook/react/pull/27347), [\#27346](https://github.com/facebook/react/pull/27346), [\#27361](https://github.com/facebook/react/pull/27361), [\#27400](https://github.com/facebook/react/pull/27400), [\#27541](https://github.com/facebook/react/pull/27541), [\#27610](https://github.com/facebook/react/pull/27610), [\#28110](https://github.com/facebook/react/pull/28110), [\#29693](https://github.com/facebook/react/pull/29693), [\#29732](https://github.com/facebook/react/pull/29732), [\#29811](https://github.com/facebook/react/pull/29811), [\#27586](https://github.com/facebook/react/pull/27586), [\#28069](https://github.com/facebook/react/pull/28069) by [@gnoff](https://github.com/gnoff), [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), [@kassens](https://github.com/kassens), [@sokra](https://github.com/sokra), [@sweetliquid](https://github.com/sweetliquid)
|
||||
* Add `fetchPriority` to `<img>` and `<link>` ([\#25927](https://github.com/facebook/react/pull/25927) by [@styfle](https://github.com/styfle))
|
||||
* Add support for SVG `transformOrigin` prop ([\#26130](https://github.com/facebook/react/pull/26130) by [@arav-ind](https://github.com/arav-ind))
|
||||
* Add support for `onScrollEnd` event ([\#26789](https://github.com/facebook/react/pull/26789) by [@devongovett](https://github.com/devongovett))
|
||||
* Allow `<hr>` as child of `<select>` ([\#27632](https://github.com/facebook/react/pull/27632) by [@SouSingh](https://github.com/SouSingh))
|
||||
* Add support for Popover API ([\#27981](https://github.com/facebook/react/pull/27981) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Add support for `inert` ([\#24730](https://github.com/facebook/react/pull/24730) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Add support for `imageSizes` and `imageSrcSet` ([\#22550](https://github.com/facebook/react/pull/22550) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Synchronously flush transitions in popstate events ([\#26025](https://github.com/facebook/react/pull/26025), [\#27559](https://github.com/facebook/react/pull/27559), [\#27505](https://github.com/facebook/react/pull/27505), [\#30759](https://github.com/facebook/react/pull/30759) by [@tyao1](https://github.com/tyao1) and [@acdlite](https://github.com/acdlite))
|
||||
* `flushSync` exhausts queue even if something throws ([\#26366](https://github.com/facebook/react/pull/26366) by [@acdlite](https://github.com/acdlite))
|
||||
* Throw error if `react` and `react-dom` versions don’t match ([\#29236](https://github.com/facebook/react/pull/29236) by [@acdlite](https://github.com/acdlite))
|
||||
* Ensure `srcset` and `src` are assigned last on `<img>` instances ([\#30340](https://github.com/facebook/react/pull/30340) by [@gnoff](https://github.com/gnoff))
|
||||
* Javascript URLs are replaced with functions that throw errors ([\#26507](https://github.com/facebook/react/pull/26507), [\#29808](https://github.com/facebook/react/pull/29808) by [@sebmarkbage](https://github.com/sebmarkbage) and [@kassens](https://github.com/kassens))
|
||||
* Treat toggle and beforetoggle as discrete events ([\#29176](https://github.com/facebook/react/pull/29176) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Filter out empty `src` and `href` attributes (unless for `<a href=”” />`) ([\#18513](https://github.com/facebook/react/pull/18513), [\#28124](https://github.com/facebook/react/pull/28124) by [@bvaughn](https://github.com/bvaughn) and [@eps1lon](https://github.com/eps1lon))
|
||||
* Fix unitless `scale` style property ([\#25601](https://github.com/facebook/react/pull/25601) by [@JonnyBurger](https://github.com/JonnyBurger))
|
||||
* Fix `onChange` error message for controlled `<select>` ([\#27740](https://github.com/facebook/react/pull/27740) by [@Biki-das](https://github.com/Biki-das))
|
||||
* Fix focus restore in child windows after element reorder ([\#30951](https://github.com/facebook/react/pull/30951) by [@ling1726](https://github.com/ling1726))
|
||||
* Remove `render`, `hydrate`, `findDOMNode`, `unmountComponentAtNode`, `unstable_createEventHandle`, `unstable_renderSubtreeIntoContainer`, and `unstable_runWithPriority`. Move `createRoot` and `hydrateRoot` to `react-dom/client`. ([\#28271](https://github.com/facebook/react/pull/28271) by [@gnoff](https://github.com/gnoff))
|
||||
* Remove `test-utils` ([\#28541](https://github.com/facebook/react/pull/28541) by [@eps1lon](https://github.com/eps1lon))
|
||||
* Remove `unstable_flushControlled` ([\#26397](https://github.com/facebook/react/pull/26397) by [@kassens](https://github.com/kassens))
|
||||
* Remove legacy mode ([\#28468](https://github.com/facebook/react/pull/28468) by [@gnoff](https://github.com/gnoff))
|
||||
* Remove `renderToStaticNodeStream()` ([\#28873](https://github.com/facebook/react/pull/28873) by @gnoff)
|
||||
* Remove `unstable_renderSubtreeIntoContainer` ([\#29771](https://github.com/facebook/react/pull/29771) by [@kassens](https://github.com/kassens))
|
||||
|
||||
#### React DOM Server
|
||||
|
||||
* Stable release of React Server Components ([Many, many PRs](https://github.com/facebook/react/pulls?q=is%3Apr+is%3Aclosed+%5BFlight%5D+in%3Atitle+created%3A%3C2024-12-01+) by [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), [@gnoff](https://github.com/gnoff), [@sammy-SC](https://github.com/sammy-SC), [@gaearon](https://github.com/gaearon), [@sophiebits](https://github.com/sophiebits), [@unstubbable](https://github.com/unstubbable), [@lubieowoce](https://github.com/lubieowoce))
|
||||
* Support Server Actions ([\#26124](https://github.com/facebook/react/pull/26124), [\#26632](https://github.com/facebook/react/pull/26632), [\#27459](https://github.com/facebook/react/pull/27459) by [@sebmarkbage](https://github.com/sebmarkbage) and [@acdlite](https://github.com/acdlite))
|
||||
* Changes to SSR
|
||||
* Add external runtime which bootstraps hydration on the client for binary transparency ([\#25437](https://github.com/facebook/react/pull/25437), [\#26169](https://github.com/facebook/react/pull/26169), [\#25499](https://github.com/facebook/react/pull/25499) by [@MofeiZ](https://github.com/mofeiZ) and [@acdlite](https://github.com/acdlite))
|
||||
* Support subresource integrity for `bootstrapScripts` and `bootstrapModules` ([\#25104](https://github.com/facebook/react/pull/25104) by [@gnoff](https://github.com/gnoff))
|
||||
* Fix null bytes written at text chunk boundaries ([\#26228](https://github.com/facebook/react/pull/26228) by [@sophiebits](https://github.com/sophiebits))
|
||||
* Fix logic around attribute serialization ([\#26526](https://github.com/facebook/react/pull/26526) by [@gnoff](https://github.com/gnoff))
|
||||
* Fix precomputed chunk cleared on Node 18 ([\#25645](https://github.com/facebook/react/pull/25645) by [@feedthejim](https://github.com/feedthejim))
|
||||
* Optimize end tag chunks ([\#27522](https://github.com/facebook/react/pull/27522) by [@yujunjung](https://github.com/yujunjung))
|
||||
* Gracefully handle suspending in DOM configs ([\#26768](https://github.com/facebook/react/pull/26768) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Check for nullish values on ReactCustomFormAction ([\#26770](https://github.com/facebook/react/pull/26770) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Preload `bootstrapModules`, `bootstrapScripts`, and update priority queue ([\#26754](https://github.com/facebook/react/pull/26754), [\#26753](https://github.com/facebook/react/pull/26753), [\#27190](https://github.com/facebook/react/pull/27190), [\#27189](https://github.com/facebook/react/pull/27189) by [@gnoff](https://github.com/gnoff))
|
||||
* Client render the nearest child or parent suspense boundary if replay errors or is aborted ([\#27386](https://github.com/facebook/react/pull/27386) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Don't bail out of flushing if we still have pending root tasks ([\#27385](https://github.com/facebook/react/pull/27385) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Ensure Resumable State is Serializable ([\#27388](https://github.com/facebook/react/pull/27388) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Remove extra render pass when reverting to client render ([\#26445](https://github.com/facebook/react/pull/26445) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix unwinding context during selective hydration ([\#25876](https://github.com/facebook/react/pull/25876) by [@tyao1](https://github.com/tyao1))
|
||||
* Stop flowing and then abort if a stream is cancelled ([\#27405](https://github.com/facebook/react/pull/27405) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Pass cancellation reason to abort ([\#27536](https://github.com/facebook/react/pull/27536) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Add `onHeaders` entrypoint option ([\#27641](https://github.com/facebook/react/pull/27641), [\#27712](https://github.com/facebook/react/pull/27712) by [@gnoff](https://github.com/gnoff))
|
||||
* Escape `<style>` and `<script>` textContent to enable rendering inner content without dangerouslySetInnerHTML ([\#28870](https://github.com/facebook/react/pull/28870), [\#28871](https://github.com/facebook/react/pull/28871) by [@gnoff](https://github.com/gnoff))
|
||||
* Fallback to client replaying actions for Blob serialization ([\#28987](https://github.com/facebook/react/pull/28987) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Render Suspense fallback if boundary contains new stylesheet during sync update ([\#28965](https://github.com/facebook/react/pull/28965) by [@gnoff](https://github.com/gnoff))
|
||||
* Fix header length tracking (\#30327 by [@gnoff](https://github.com/gnoff))
|
||||
* Use `srcset` to trigger load event on mount (\#30351 by [@gnoff](https://github.com/gnoff))
|
||||
* Don't perform work when closing stream (\#30497 by [@gnoff](https://github.com/gnoff))
|
||||
* Allow aborting during render (\#30488, [\#30730](https://github.com/facebook/react/pull/30730) by [@gnoff](https://github.com/gnoff))
|
||||
* Start initial work immediately (\#31079 by [@gnoff](https://github.com/gnoff))
|
||||
* A transition flowing into a dehydrated boundary no longer suspends when showing fallback ([\#27230](https://github.com/facebook/react/pull/27230) by [@acdlite](https://github.com/acdlite))
|
||||
* Fix selective hydration triggers false update loop error ([\#27439](https://github.com/facebook/react/pull/27439) by [@acdlite](https://github.com/acdlite))
|
||||
* Warn for Child Iterator of all types but allow Generator Components ([\#28853](https://github.com/facebook/react/pull/28853) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Include regular stack trace in serialized errors ([\#28684](https://github.com/facebook/react/pull/28684), [\#28738](https://github.com/facebook/react/pull/28738) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Aborting early no longer infinitely suspends ([\#24751](https://github.com/facebook/react/pull/24751) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Fix hydration warning suppression in text comparisons ([\#24784](https://github.com/facebook/react/pull/24784) by [@gnoff](https://github.com/gnoff))
|
||||
* Changes to error handling in SSR
|
||||
* Add diffs to hydration warnings ([\#28502](https://github.com/facebook/react/pull/28502), [\#28512](https://github.com/facebook/react/pull/28512) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Make Error creation lazy ([\#24728](https://github.com/facebook/react/pull/24728) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Remove recoverable error when a sync update flows into a dehydrated boundary ([\#25692](https://github.com/facebook/react/pull/25692) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Don't "fix up" mismatched text content with suppressedHydrationWarning ([\#26391](https://github.com/facebook/react/pull/26391) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Fix component stacks in errors ([\#27456](https://github.com/facebook/react/pull/27456) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Add component stacks to `onError` ([\#27761](https://github.com/facebook/react/pull/27761), [\#27850](https://github.com/facebook/react/pull/27850) by [@gnoff](https://github.com/gnoff) and [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Throw hydration mismatch errors once ([\#28502](https://github.com/facebook/react/pull/28502) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Add Bun streaming server renderer ([\#25597](https://github.com/facebook/react/pull/25597) by [@colinhacks](https://github.com/colinhacks))
|
||||
* Add nonce support to bootstrap scripts ([\#26738](https://github.com/facebook/react/pull/26738) by [@danieltott](https://github.com/danieltott))
|
||||
* Add `crossorigin` support to bootstrap scripts ([\#26844](https://github.com/facebook/react/pull/26844) by [@HenriqueLimas](https://github.com/HenriqueLimas))
|
||||
* Support `nonce` and `fetchpriority` in preload links ([\#26826](https://github.com/facebook/react/pull/26826) by [@liuyenwei](https://github.com/liuyenwei))
|
||||
* Add `referrerPolicy` to `ReactDOM.preload()` ([\#27096](https://github.com/facebook/react/pull/27096) by [@styfle](https://github.com/styfle))
|
||||
* Add server condition for `react/jsx-dev-runtime` ([\#28921](https://github.com/facebook/react/pull/28921) by [@himself65](https://github.com/himself65))
|
||||
* Export version ([\#29596](https://github.com/facebook/react/pull/29596) by [@unstubbable](https://github.com/unstubbable))
|
||||
* Rename the secret export of Client and Server internals ([\#28786](https://github.com/facebook/react/pull/28786), [\#28789](https://github.com/facebook/react/pull/28789) by [@sebmarkbage](https://github.com/sebmarkbage))
|
||||
* Remove layout effect warning on server ([\#26395](https://github.com/facebook/react/pull/26395) by [@rickhanlonii](https://github.com/rickhanlonii))
|
||||
* Remove `errorInfo.digest` from `onRecoverableError` ([\#28222](https://github.com/facebook/react/pull/28222) by [@gnoff](https://github.com/gnoff))
|
||||
|
||||
#### ReactTestRenderer
|
||||
|
||||
* Add deprecation error to `react-test-renderer` on web ([\#27903](https://github.com/facebook/react/pull/27903), [\#28904](https://github.com/facebook/react/pull/28904) by [@jackpope](https://github.com/jackpope) and [@acdlite](https://github.com/acdlite))
|
||||
* Render with ConcurrentRoot on web ([\#28498](https://github.com/facebook/react/pull/28498) by [@jackpope](https://github.com/jackpope))
|
||||
* Remove `react-test-renderer/shallow` export ([\#25475](https://github.com/facebook/react/pull/25475), [\#28497](https://github.com/facebook/react/pull/28497) by [@sebmarkbage](https://github.com/sebmarkbage) and [@jackpope](https://github.com/jackpope))
|
||||
|
||||
#### React Reconciler
|
||||
|
||||
* Enable suspending commits without blocking render ([\#26398](https://github.com/facebook/react/pull/26398), [\#26427](https://github.com/facebook/react/pull/26427) by [@acdlite](https://github.com/acdlite))
|
||||
* Remove `prepareUpdate` ([\#26583](https://github.com/facebook/react/pull/26583), [\#27409](http://github.com/facebook/react/pull/27409) by [@sebmarkbage](https://github.com/sebmarkbage) and [@sophiebits](https://github.com/sophiebits))
|
||||
|
||||
#### React-Is
|
||||
|
||||
* Enable tree shaking ([\#27701](https://github.com/facebook/react/pull/27701) by [@markerikson](https://github.com/markerikson))
|
||||
* Remove `isConcurrentMode` and `isAsyncMode` methods ([\#28224](https://github.com/facebook/react/pull/28224) by @gaearon)
|
||||
|
||||
#### useSyncExternalStore
|
||||
|
||||
* Remove React internals access ([\#29868](https://github.com/facebook/react/pull/29868) by [@phryneas](https://github.com/phryneas))
|
||||
* Fix stale selectors keeping previous store references ([\#25969](https://github.com/facebook/react/pull/25968) by [@jellevoost](https://github.com/jellevoost))
|
||||
|
||||
## 18.3.1 (April 26, 2024)
|
||||
|
||||
- Export `act` from `react` [f1338f](https://github.com/facebook/react/commit/f1338f8080abd1386454a10bbf93d67bfe37ce85)
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
//
|
||||
// The @latest channel uses the version as-is, e.g.:
|
||||
//
|
||||
// 19.1.0
|
||||
// 19.0.1
|
||||
//
|
||||
// The @canary channel appends additional information, with the scheme
|
||||
// <version>-<label>-<commit_sha>, e.g.:
|
||||
//
|
||||
// 19.1.0-canary-a1c2d3e4
|
||||
// 19.0.1-canary-a1c2d3e4
|
||||
//
|
||||
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
|
||||
//
|
||||
// 0.0.0-experimental-241c4467e-20200129
|
||||
|
||||
const ReactVersion = '19.1.0';
|
||||
const ReactVersion = '19.0.1';
|
||||
|
||||
// The label used by the @canary channel. Represents the upcoming release's
|
||||
// stability. Most of the time, this will be "canary", but we may temporarily
|
||||
@@ -26,28 +26,27 @@ const ReactVersion = '19.1.0';
|
||||
//
|
||||
// It only affects the label used in the version string. To customize the
|
||||
// npm dist tags used during publish, refer to .github/workflows/runtime_prereleases_*.yml.
|
||||
const canaryChannelLabel = 'canary';
|
||||
const canaryChannelLabel = 'rc';
|
||||
|
||||
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
|
||||
// an RC version of the packages.
|
||||
const rcNumber = 0;
|
||||
const rcNumber = 1;
|
||||
|
||||
const stablePackages = {
|
||||
'eslint-plugin-react-hooks': '5.2.0',
|
||||
'jest-react': '0.17.0',
|
||||
'eslint-plugin-react-hooks': '5.1.0',
|
||||
'jest-react': '0.16.0',
|
||||
react: ReactVersion,
|
||||
'react-art': ReactVersion,
|
||||
'react-dom': ReactVersion,
|
||||
'react-server-dom-webpack': ReactVersion,
|
||||
'react-server-dom-turbopack': ReactVersion,
|
||||
'react-server-dom-parcel': ReactVersion,
|
||||
'react-is': ReactVersion,
|
||||
'react-reconciler': '0.32.0',
|
||||
'react-refresh': '0.17.0',
|
||||
'react-reconciler': '0.31.0',
|
||||
'react-refresh': '0.16.0',
|
||||
'react-test-renderer': ReactVersion,
|
||||
'use-subscription': '1.11.0',
|
||||
'use-sync-external-store': '1.5.0',
|
||||
scheduler: '0.26.0',
|
||||
'use-subscription': '1.10.0',
|
||||
'use-sync-external-store': '1.4.0',
|
||||
scheduler: '0.25.0',
|
||||
};
|
||||
|
||||
// These packages do not exist in the @canary or @latest channel, only
|
||||
|
||||
@@ -26,13 +26,6 @@ $ npm run dev
|
||||
$ yarn
|
||||
```
|
||||
|
||||
## Testing
|
||||
```sh
|
||||
# Install playwright browser binaries
|
||||
$ npx playwright install --with-deps
|
||||
# Run tests
|
||||
$ yarn test
|
||||
```
|
||||
## Deployment
|
||||
|
||||
This project has been deployed using Vercel. Vercel does the exact same thing as we would
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
export default function TestComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = <Button>{x}</Button>;
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
export default function MyApp() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>Hello World</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// @compilationMode(infer)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { c as _c } from "react/compiler-runtime"; //
|
||||
@compilationMode(all)
|
||||
function nonReactFn() {
|
||||
function MyApp() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = {};
|
||||
t0 = <div>Hello World</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
@@ -1,14 +0,0 @@
|
||||
function TestComponent(t0) {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = <Button>{x}</Button>;
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
"use memo";
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
export default function TestComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = <Button>{x}</Button>;
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
"use no memo";
|
||||
export default function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useFoo(propVal) {
|
||||
const $ = _c(2);
|
||||
const t0 = (propVal.baz: number);
|
||||
let t1;
|
||||
if ($[0] !== t0) {
|
||||
t1 = <div>{t0}</div>;
|
||||
$[0] = t0;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Foo() {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = foo();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const x = t0 as number;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <div>{x}</div>;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
"use no memo";
|
||||
function TestComponent({ x }) {
|
||||
"use memo";
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function TestComponent(t0) {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = <Button>{x}</Button>;
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
const TestComponent2 = (t0) => {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = <Button>{x}</Button>;
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
const TestComponent = function () {
|
||||
"use no memo";
|
||||
return <Button>{x}</Button>;
|
||||
};
|
||||
const TestComponent2 = ({ x }) => {
|
||||
"use no memo";
|
||||
return <Button>{x}</Button>;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
function TestComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = <Button>{x}</Button>;
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -7,180 +7,43 @@
|
||||
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {encodeStore, type Store} from '../../lib/stores';
|
||||
import {format} from 'prettier';
|
||||
|
||||
function formatPrint(data: Array<string>): Promise<string> {
|
||||
return format(data.join(''), {parser: 'babel'});
|
||||
}
|
||||
|
||||
const TEST_CASE_INPUTS = [
|
||||
{
|
||||
name: 'module-scope-use-memo',
|
||||
input: `
|
||||
'use memo';
|
||||
export default function TestComponent({ x }) {
|
||||
const STORE: Store = {
|
||||
source: `export default function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'module-scope-use-no-memo',
|
||||
input: `
|
||||
'use no memo';
|
||||
export default function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'use-memo',
|
||||
input: `
|
||||
function TestComponent({ x }) {
|
||||
'use memo';
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
const TestComponent2 = ({ x }) => {
|
||||
'use memo';
|
||||
return <Button>{x}</Button>;
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: 'use-no-memo',
|
||||
input: `
|
||||
const TestComponent = function() {
|
||||
'use no memo';
|
||||
return <Button>{x}</Button>;
|
||||
};
|
||||
const TestComponent2 = ({ x }) => {
|
||||
'use no memo';
|
||||
return <Button>{x}</Button>;
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: 'todo-function-scope-does-not-beat-module-scope',
|
||||
input: `
|
||||
'use no memo';
|
||||
function TestComponent({ x }) {
|
||||
'use memo';
|
||||
return <Button>{x}</Button>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'parse-typescript',
|
||||
input: `
|
||||
function Foo() {
|
||||
const x = foo() as number;
|
||||
return <div>{x}</div>;
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
{
|
||||
name: 'parse-flow',
|
||||
input: `
|
||||
// @flow
|
||||
function useFoo(propVal: {+baz: number}) {
|
||||
return <div>{(propVal.baz as number)}</div>;
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
{
|
||||
name: 'compilationMode-infer',
|
||||
input: `// @compilationMode(infer)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
{
|
||||
name: 'compilationMode-all',
|
||||
input: `// @compilationMode(all)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
const HASH = encodeStore(STORE);
|
||||
|
||||
test('editor should open successfully', async ({page}) => {
|
||||
await page.goto(`/`, {waitUntil: 'networkidle'});
|
||||
function concat(data: Array<string>): string {
|
||||
return data.join('');
|
||||
}
|
||||
|
||||
test('editor should compile successfully', async ({page}) => {
|
||||
await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/00-fresh-page.png',
|
||||
path: 'test-results/00-on-networkidle.png',
|
||||
});
|
||||
});
|
||||
|
||||
test('editor should compile from hash successfully', async ({page}) => {
|
||||
const store: Store = {
|
||||
source: `export default function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
`,
|
||||
};
|
||||
const hash = encodeStore(store);
|
||||
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
|
||||
|
||||
// User input from hash compiles
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/01-compiles-from-hash.png',
|
||||
path: 'test-results/01-show-js-before.png',
|
||||
});
|
||||
const text =
|
||||
const userInput =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
const output = await formatPrint(text);
|
||||
|
||||
expect(output).not.toEqual('');
|
||||
expect(output).toMatchSnapshot('01-user-output.txt');
|
||||
});
|
||||
|
||||
test('reset button works', async ({page}) => {
|
||||
const store: Store = {
|
||||
source: `export default function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
`,
|
||||
};
|
||||
const hash = encodeStore(store);
|
||||
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
|
||||
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
|
||||
|
||||
// Reset button works
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
await page.getByRole('button', {name: 'Reset'}).click();
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/02-reset-button-works.png',
|
||||
path: 'test-results/02-show-js-after.png',
|
||||
});
|
||||
const text =
|
||||
const defaultInput =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
const output = await formatPrint(text);
|
||||
|
||||
expect(output).not.toEqual('');
|
||||
expect(output).toMatchSnapshot('02-default-output.txt');
|
||||
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
|
||||
});
|
||||
|
||||
TEST_CASE_INPUTS.forEach((t, idx) =>
|
||||
test(`playground compiles: ${t.name}`, async ({page}) => {
|
||||
const store: Store = {
|
||||
source: t.input,
|
||||
};
|
||||
const hash = encodeStore(store);
|
||||
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: `test-results/03-0${idx}-${t.name}.png`,
|
||||
});
|
||||
|
||||
const text =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
let output: string;
|
||||
if (t.noFormat) {
|
||||
output = text.join('');
|
||||
} else {
|
||||
output = await formatPrint(text);
|
||||
}
|
||||
|
||||
expect(output).not.toEqual('');
|
||||
expect(output).toMatchSnapshot(`${t.name}-output.txt`);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {parse as babelParse, ParseResult} from '@babel/parser';
|
||||
import {parse as babelParse} from '@babel/parser';
|
||||
import * as HermesParser from 'hermes-parser';
|
||||
import traverse, {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import BabelPluginReactCompiler, {
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
Effect,
|
||||
ErrorSeverity,
|
||||
parseConfigPragmaForTests,
|
||||
ValueKind,
|
||||
runPlayground,
|
||||
type Hook,
|
||||
PluginOptions,
|
||||
CompilerPipelineValue,
|
||||
parsePluginOptions,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
|
||||
import clsx from 'clsx';
|
||||
import invariant from 'invariant';
|
||||
import {useSnackbar} from 'notistack';
|
||||
@@ -37,18 +37,13 @@ import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import Input from './Input';
|
||||
import {
|
||||
CompilerOutput,
|
||||
CompilerTransformOutput,
|
||||
default as Output,
|
||||
PrintedCompilerPipelineValue,
|
||||
} from './Output';
|
||||
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
|
||||
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
|
||||
function parseInput(
|
||||
input: string,
|
||||
language: 'flow' | 'typescript',
|
||||
): ParseResult<t.File> {
|
||||
function parseInput(input: string, language: 'flow' | 'typescript'): any {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
if (language === 'flow') {
|
||||
return HermesParser.parse(input, {
|
||||
@@ -61,35 +56,49 @@ function parseInput(
|
||||
return babelParse(input, {
|
||||
plugins: ['typescript', 'jsx'],
|
||||
sourceType: 'module',
|
||||
}) as ParseResult<t.File>;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function invokeCompiler(
|
||||
function parseFunctions(
|
||||
source: string,
|
||||
language: 'flow' | 'typescript',
|
||||
options: PluginOptions,
|
||||
): CompilerTransformOutput {
|
||||
const ast = parseInput(source, language);
|
||||
let result = transformFromAstSync(ast, source, {
|
||||
filename: '_playgroundFile.js',
|
||||
highlightCode: false,
|
||||
retainLines: true,
|
||||
plugins: [[BabelPluginReactCompiler, options]],
|
||||
ast: true,
|
||||
sourceType: 'module',
|
||||
configFile: false,
|
||||
sourceMaps: true,
|
||||
babelrc: false,
|
||||
});
|
||||
if (result?.ast == null || result?.code == null || result?.map == null) {
|
||||
throw new Error('Expected successful compilation');
|
||||
): Array<
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>
|
||||
> {
|
||||
const items: Array<
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>
|
||||
> = [];
|
||||
try {
|
||||
const ast = parseInput(source, language);
|
||||
traverse(ast, {
|
||||
FunctionDeclaration(nodePath) {
|
||||
items.push(nodePath);
|
||||
nodePath.skip();
|
||||
},
|
||||
ArrowFunctionExpression(nodePath) {
|
||||
items.push(nodePath);
|
||||
nodePath.skip();
|
||||
},
|
||||
FunctionExpression(nodePath) {
|
||||
items.push(nodePath);
|
||||
nodePath.skip();
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: String(e),
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
return {
|
||||
code: result.code,
|
||||
sourceMaps: result.map,
|
||||
language,
|
||||
};
|
||||
return items;
|
||||
}
|
||||
|
||||
const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
@@ -140,6 +149,37 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
],
|
||||
];
|
||||
|
||||
function isHookName(s: string): boolean {
|
||||
return /^use[A-Z0-9]/.test(s);
|
||||
}
|
||||
|
||||
function getReactFunctionType(id: t.Identifier | null): ReactFunctionType {
|
||||
if (id != null) {
|
||||
if (isHookName(id.name)) {
|
||||
return 'Hook';
|
||||
}
|
||||
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
if (isPascalCaseNameSpace.test(id.name)) {
|
||||
return 'Component';
|
||||
}
|
||||
}
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
function getFunctionIdentifier(
|
||||
fn:
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>,
|
||||
): t.Identifier | null {
|
||||
if (fn.isArrowFunctionExpression()) {
|
||||
return null;
|
||||
}
|
||||
const id = fn.get('id');
|
||||
return Array.isArray(id) === false && id.isIdentifier() ? id.node : null;
|
||||
}
|
||||
|
||||
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
|
||||
const error = new CompilerError();
|
||||
@@ -157,63 +197,81 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
} else {
|
||||
language = 'typescript';
|
||||
}
|
||||
let transformOutput;
|
||||
let count = 0;
|
||||
const withIdentifier = (id: t.Identifier | null): t.Identifier => {
|
||||
if (id != null && id.name != null) {
|
||||
return id;
|
||||
} else {
|
||||
return t.identifier(`anonymous_${count++}`);
|
||||
}
|
||||
};
|
||||
try {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
const pragma = source.substring(0, source.indexOf('\n'));
|
||||
const logIR = (result: CompilerPipelineValue): void => {
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
upsert({
|
||||
kind: 'hir',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
upsert({
|
||||
kind: 'reactive',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
upsert({
|
||||
kind: 'debug',
|
||||
fnName: null,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const _: never = result;
|
||||
throw new Error(`Unhandled result ${result}`);
|
||||
const config = parseConfigPragmaForTests(pragma);
|
||||
|
||||
for (const fn of parseFunctions(source, language)) {
|
||||
const id = withIdentifier(getFunctionIdentifier(fn));
|
||||
for (const result of runPlayground(
|
||||
fn,
|
||||
{
|
||||
...config,
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
getReactFunctionType(id),
|
||||
)) {
|
||||
const fnName = id.name;
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
upsert({
|
||||
kind: 'ast',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: {
|
||||
type: 'FunctionDeclaration',
|
||||
id: withIdentifier(result.value.id),
|
||||
async: result.value.async,
|
||||
generator: result.value.generator,
|
||||
body: result.value.body,
|
||||
params: result.value.params,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
upsert({
|
||||
kind: 'hir',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
upsert({
|
||||
kind: 'reactive',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
upsert({
|
||||
kind: 'debug',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const _: never = result;
|
||||
throw new Error(`Unhandled result ${result}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const parsedOptions = parseConfigPragmaForTests(pragma, {
|
||||
compilationMode: 'infer',
|
||||
});
|
||||
const opts: PluginOptions = parsePluginOptions({
|
||||
...parsedOptions,
|
||||
environment: {
|
||||
...parsedOptions.environment,
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
logger: {
|
||||
debugLogIRs: logIR,
|
||||
logEvent: () => {},
|
||||
},
|
||||
});
|
||||
transformOutput = invokeCompiler(source, language, opts);
|
||||
}
|
||||
} catch (err) {
|
||||
/**
|
||||
* error might be an invariant violation or other runtime error
|
||||
@@ -240,7 +298,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
if (error.hasErrors()) {
|
||||
return [{kind: 'err', results, error: error}, language];
|
||||
}
|
||||
return [{kind: 'ok', results, transformOutput}, language];
|
||||
return [{kind: 'ok', results}, language];
|
||||
}
|
||||
|
||||
export default function Editor(): JSX.Element {
|
||||
@@ -260,7 +318,7 @@ export default function Editor(): JSX.Element {
|
||||
} catch (e) {
|
||||
invariant(e instanceof Error, 'Only Error may be caught.');
|
||||
enqueueSnackbar(e.message, {
|
||||
variant: 'warning',
|
||||
variant: 'message',
|
||||
...createMessage(
|
||||
'Bad URL - fell back to the default Playground.',
|
||||
MessageLevel.Info,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
import {
|
||||
CodeIcon,
|
||||
DocumentAddIcon,
|
||||
@@ -19,12 +21,17 @@ import {memo, ReactNode, useEffect, useState} from 'react';
|
||||
import {type Store} from '../../lib/stores';
|
||||
import TabbedWindow from '../TabbedWindow';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
import {BabelFileResult} from '@babel/core';
|
||||
const MemoizedOutput = memo(Output);
|
||||
|
||||
export default MemoizedOutput;
|
||||
|
||||
export type PrintedCompilerPipelineValue =
|
||||
| {
|
||||
kind: 'ast';
|
||||
name: string;
|
||||
fnName: string | null;
|
||||
value: t.FunctionDeclaration;
|
||||
}
|
||||
| {
|
||||
kind: 'hir';
|
||||
name: string;
|
||||
@@ -34,17 +41,8 @@ export type PrintedCompilerPipelineValue =
|
||||
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
|
||||
| {kind: 'debug'; name: string; fnName: string | null; value: string};
|
||||
|
||||
export type CompilerTransformOutput = {
|
||||
code: string;
|
||||
sourceMaps: BabelFileResult['map'];
|
||||
language: 'flow' | 'typescript';
|
||||
};
|
||||
export type CompilerOutput =
|
||||
| {
|
||||
kind: 'ok';
|
||||
transformOutput: CompilerTransformOutput;
|
||||
results: Map<string, Array<PrintedCompilerPipelineValue>>;
|
||||
}
|
||||
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
|
||||
| {
|
||||
kind: 'err';
|
||||
results: Map<string, Array<PrintedCompilerPipelineValue>>;
|
||||
@@ -63,6 +61,7 @@ async function tabify(
|
||||
const tabs = new Map<string, React.ReactNode>();
|
||||
const reorderedTabs = new Map<string, React.ReactNode>();
|
||||
const concattedResults = new Map<string, string>();
|
||||
let topLevelFnDecls: Array<t.FunctionDeclaration> = [];
|
||||
// Concat all top level function declaration results into a single tab for each pass
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
for (const result of results) {
|
||||
@@ -88,6 +87,9 @@ async function tabify(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ast':
|
||||
topLevelFnDecls.push(result.value);
|
||||
break;
|
||||
case 'debug': {
|
||||
concattedResults.set(passName, result.value);
|
||||
break;
|
||||
@@ -112,17 +114,13 @@ async function tabify(
|
||||
lastPassOutput = text;
|
||||
}
|
||||
// Ensure that JS and the JS source map come first
|
||||
if (compilerOutput.kind === 'ok') {
|
||||
const {transformOutput} = compilerOutput;
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
transformOutput.code,
|
||||
JSON.stringify(transformOutput.sourceMaps),
|
||||
);
|
||||
const code = await prettier.format(transformOutput.code, {
|
||||
semi: true,
|
||||
parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
if (topLevelFnDecls.length > 0) {
|
||||
/**
|
||||
* Make a synthetic Program so we can have a single AST with all the top level
|
||||
* FunctionDeclarations
|
||||
*/
|
||||
const ast = t.program(topLevelFnDecls);
|
||||
const {code, sourceMapUrl} = await codegen(ast, source);
|
||||
reorderedTabs.set(
|
||||
'JS',
|
||||
<TextTabContent
|
||||
@@ -149,6 +147,27 @@ async function tabify(
|
||||
return reorderedTabs;
|
||||
}
|
||||
|
||||
async function codegen(
|
||||
ast: t.Program,
|
||||
source: string,
|
||||
): Promise<{code: any; sourceMapUrl: string | null}> {
|
||||
const generated = generate(
|
||||
ast,
|
||||
{sourceMaps: true, sourceFileName: 'input.js'},
|
||||
source,
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map),
|
||||
);
|
||||
const codegenOutput = await prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: 'babel',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
return {code: codegenOutput, sourceMapUrl};
|
||||
}
|
||||
|
||||
function utf16ToUTF8(s: string): string {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run watch\" \"yarn workspace react-compiler-runtime run watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
|
||||
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
|
||||
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
|
||||
"build": "yarn build:compiler && next build",
|
||||
"postbuild": "node ./scripts/downloadFonts.js",
|
||||
"preinstall": "cd ../.. && yarn install --frozen-lockfile",
|
||||
"postinstall": "./scripts/link-compiler.sh",
|
||||
"vercel-build": "yarn build",
|
||||
"start": "next start",
|
||||
|
||||
@@ -18,8 +18,6 @@ const baseURL = `http://localhost:${PORT}`;
|
||||
export default defineConfig({
|
||||
// Timeout per test
|
||||
timeout: 30 * 1000,
|
||||
// Run all tests in parallel.
|
||||
fullyParallel: true,
|
||||
// Test directory
|
||||
testDir: path.join(__dirname, '__tests__/e2e'),
|
||||
// If a test fails, retry it additional 2 times
|
||||
@@ -38,10 +36,6 @@ export default defineConfig({
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
|
||||
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
|
||||
// default 'list' when running locally
|
||||
reporter: process.env.CI ? 'github' : 'list',
|
||||
|
||||
use: {
|
||||
// Use baseURL so to make navigations relative.
|
||||
// More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url
|
||||
|
||||
@@ -15,34 +15,38 @@
|
||||
"start": "yarn workspace playground run start",
|
||||
"next": "yarn workspace playground run dev",
|
||||
"build": "yarn workspaces run build",
|
||||
"dev": "cd apps/playground && yarn dev",
|
||||
"dev": "echo 'DEPRECATED: use `cd apps/playground && yarn dev` instead!' && sleep 5 && cd apps/playground && yarn dev",
|
||||
"test": "yarn workspaces run test",
|
||||
"snap": "yarn workspace babel-plugin-react-compiler run snap",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
"npm:publish": "node scripts/release/publish"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^4.0.2",
|
||||
"react-is": "0.0.0-experimental-4beb1fd8-20241118"
|
||||
"fs-extra": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@tsconfig/strictest": "^2.0.5",
|
||||
"concurrently": "^7.4.0",
|
||||
"esbuild": "^0.24.2",
|
||||
"folder-hash": "^4.0.4",
|
||||
"npm-dts": "^1.3.13",
|
||||
"object-assign": "^4.1.1",
|
||||
"ora": "5.4.1",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-hermes-parser": "^0.26.0",
|
||||
"prettier-plugin-hermes-parser": "^0.23.0",
|
||||
"prompt-promise": "^1.0.3",
|
||||
"rimraf": "^5.0.10",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-banner2": "^1.2.3",
|
||||
"rollup-plugin-prettier": "^4.1.1",
|
||||
"typescript": "^5.4.3",
|
||||
"wait-on": "^7.2.0",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"rimraf": "5.0.10"
|
||||
"react-is": "19.0.0-beta-b498834eab-20240506"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
||||
|
||||
@@ -5,19 +5,17 @@
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"!*.tsbuildinfo"
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf dist && scripts/build.js",
|
||||
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
|
||||
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
|
||||
"jest": "yarn build && ts-node node_modules/.bin/jest",
|
||||
"snap": "node ../snap/dist/main.js",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
"snap:ci": "yarn snap:build && yarn snap",
|
||||
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
|
||||
"lint": "yarn eslint src",
|
||||
"watch": "scripts/build.js --watch"
|
||||
"lint": "yarn eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.19.0"
|
||||
@@ -43,13 +41,16 @@
|
||||
"babel-jest": "^29.0.3",
|
||||
"babel-plugin-fbt": "^1.0.0",
|
||||
"babel-plugin-fbt-runtime": "^1.0.0",
|
||||
"chalk": "4",
|
||||
"eslint": "^8.57.1",
|
||||
"glob": "^7.1.6",
|
||||
"invariant": "^2.2.4",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"pretty-format": "^24",
|
||||
"react": "0.0.0-experimental-4beb1fd8-20241118",
|
||||
"react-dom": "0.0.0-experimental-4beb1fd8-20241118",
|
||||
"react": "0.0.0-experimental-0bc30748-20241028",
|
||||
"react-dom": "0.0.0-experimental-0bc30748-20241028",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"zod": "^3.22.4",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import prettier from 'rollup-plugin-prettier';
|
||||
import banner2 from 'rollup-plugin-banner2';
|
||||
|
||||
const NO_INLINE = new Set(['@babel/types']);
|
||||
|
||||
const DEV_ROLLUP_CONFIG = {
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
exports: 'named',
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
compilerOptions: {
|
||||
noEmit: true,
|
||||
},
|
||||
}),
|
||||
json(),
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
resolveOnly: module => NO_INLINE.has(module) === false,
|
||||
rootDir: path.join(process.cwd(), '..'),
|
||||
}),
|
||||
commonjs(),
|
||||
terser({
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
compress: false,
|
||||
mangle: false,
|
||||
}),
|
||||
prettier(),
|
||||
banner2(
|
||||
() => `/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @lightSyntaxTransform
|
||||
* @noflow
|
||||
* @nolint
|
||||
* @preventMunge
|
||||
* @preserve-invariant-messages
|
||||
*/
|
||||
|
||||
"use no memo";
|
||||
`
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default DEV_ROLLUP_CONFIG;
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const esbuild = require('esbuild');
|
||||
const yargs = require('yargs');
|
||||
const path = require('path');
|
||||
|
||||
const argv = yargs(process.argv.slice(2))
|
||||
.options('w', {
|
||||
alias: 'watch',
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
})
|
||||
.parse();
|
||||
|
||||
const config = {
|
||||
entryPoints: [path.join(__dirname, '../src/index.ts')],
|
||||
outfile: path.join(__dirname, '../dist/index.js'),
|
||||
bundle: true,
|
||||
external: ['@babel/types'],
|
||||
format: 'cjs',
|
||||
platform: 'node',
|
||||
banner: {
|
||||
js: `/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @lightSyntaxTransform
|
||||
* @noflow
|
||||
* @nolint
|
||||
* @preventMunge
|
||||
* @preserve-invariant-messages
|
||||
*/
|
||||
|
||||
"use no memo";`,
|
||||
},
|
||||
};
|
||||
|
||||
async function main() {
|
||||
if (argv.w) {
|
||||
const ctx = await esbuild.context(config);
|
||||
await ctx.watch();
|
||||
console.log('watching for changes...');
|
||||
} else {
|
||||
await esbuild.build({
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -39,10 +39,7 @@ export default function BabelPluginReactCompiler(
|
||||
) {
|
||||
opts = injectReanimatedFlag(opts);
|
||||
}
|
||||
if (
|
||||
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
|
||||
isDev
|
||||
) {
|
||||
if (isDev) {
|
||||
opts = {
|
||||
...opts,
|
||||
environment: {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from '../HIR/Environment';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {CompilerPipelineValue} from './Pipeline';
|
||||
|
||||
const PanicThresholdOptionsSchema = z.enum([
|
||||
/*
|
||||
@@ -122,22 +121,7 @@ export type PluginOptions = {
|
||||
target: CompilerReactTarget;
|
||||
};
|
||||
|
||||
const CompilerReactTargetSchema = z.union([
|
||||
z.literal('17'),
|
||||
z.literal('18'),
|
||||
z.literal('19'),
|
||||
/**
|
||||
* Used exclusively for Meta apps which are guaranteed to have compatible
|
||||
* react runtime and compiler versions. Note that only the FB-internal bundles
|
||||
* re-export useMemoCache (see
|
||||
* https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70),
|
||||
* so this option is invalid / creates runtime errors for open-source users.
|
||||
*/
|
||||
z.object({
|
||||
kind: z.literal('donotuse_meta_internal'),
|
||||
runtimeModule: z.string().default('react'),
|
||||
}),
|
||||
]);
|
||||
const CompilerReactTargetSchema = z.enum(['17', '18', '19']);
|
||||
export type CompilerReactTarget = z.infer<typeof CompilerReactTargetSchema>;
|
||||
|
||||
const CompilationModeSchema = z.enum([
|
||||
@@ -210,7 +194,6 @@ export type LoggerEvent =
|
||||
|
||||
export type Logger = {
|
||||
logEvent: (filename: string | null, event: LoggerEvent) => void;
|
||||
debugLogIRs?: (value: CompilerPipelineValue) => void;
|
||||
};
|
||||
|
||||
export const defaultOptions: PluginOptions = {
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
inferReactivePlaces,
|
||||
inferReferenceEffects,
|
||||
inlineImmediatelyInvokedFunctionExpressions,
|
||||
inferEffectDependencies,
|
||||
} from '../Inference';
|
||||
import {
|
||||
constantPropagation,
|
||||
@@ -79,6 +78,13 @@ import {
|
||||
rewriteInstructionKindsBasedOnReassignment,
|
||||
} from '../SSA';
|
||||
import {inferTypes} from '../TypeInference';
|
||||
import {
|
||||
logCodegenFunction,
|
||||
logDebug,
|
||||
logHIRFunction,
|
||||
logReactiveFunction,
|
||||
} from '../Utils/logger';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
validateContextVariableLValues,
|
||||
validateHooksUsage,
|
||||
@@ -97,8 +103,6 @@ import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetSta
|
||||
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
|
||||
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
|
||||
import {outlineJSX} from '../Optimization/OutlineJsx';
|
||||
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
|
||||
import {transformFire} from '../Transform';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -106,7 +110,7 @@ export type CompilerPipelineValue =
|
||||
| {kind: 'reactive'; name: string; value: ReactiveFunction}
|
||||
| {kind: 'debug'; name: string; value: string};
|
||||
|
||||
function run(
|
||||
export function* run(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
@@ -116,7 +120,7 @@ function run(
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
): CodegenFunction {
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
const contextIdentifiers = findContextIdentifiers(func);
|
||||
const env = new Environment(
|
||||
func.scope,
|
||||
@@ -128,32 +132,30 @@ function run(
|
||||
code,
|
||||
useMemoCacheIdentifier,
|
||||
);
|
||||
env.logger?.debugLogIRs?.({
|
||||
yield log({
|
||||
kind: 'debug',
|
||||
name: 'EnvironmentConfig',
|
||||
value: prettyFormat(env.config),
|
||||
});
|
||||
return runWithEnvironment(func, env);
|
||||
const ast = yield* runWithEnvironment(func, env);
|
||||
return ast;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: this is split from run() to make `config` out of scope, so that all
|
||||
* access to feature flags has to be through the Environment for consistency.
|
||||
*/
|
||||
function runWithEnvironment(
|
||||
function* runWithEnvironment(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
env: Environment,
|
||||
): CodegenFunction {
|
||||
const log = (value: CompilerPipelineValue): void => {
|
||||
env.logger?.debugLogIRs?.(value);
|
||||
};
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
const hir = lower(func, env).unwrap();
|
||||
log({kind: 'hir', name: 'HIR', value: hir});
|
||||
yield log({kind: 'hir', name: 'HIR', value: hir});
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
validateContextVariableLValues(hir);
|
||||
validateUseMemo(hir);
|
||||
@@ -164,45 +166,40 @@ function runWithEnvironment(
|
||||
!env.config.enableChangeDetectionForDebugging
|
||||
) {
|
||||
dropManualMemoization(hir);
|
||||
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
yield log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
}
|
||||
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'InlineImmediatelyInvokedFunctionExpressions',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeConsecutiveBlocks(hir);
|
||||
log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
|
||||
enterSSA(hir);
|
||||
log({kind: 'hir', name: 'SSA', value: hir});
|
||||
yield log({kind: 'hir', name: 'SSA', value: hir});
|
||||
|
||||
eliminateRedundantPhi(hir);
|
||||
log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
|
||||
constantPropagation(hir);
|
||||
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
yield log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
|
||||
inferTypes(hir);
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
yield log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir);
|
||||
}
|
||||
|
||||
if (env.config.enableFire) {
|
||||
transformFire(hir);
|
||||
log({kind: 'hir', name: 'TransformFire', value: hir});
|
||||
}
|
||||
|
||||
if (env.config.validateNoCapitalizedCalls) {
|
||||
validateNoCapitalizedCalls(hir);
|
||||
}
|
||||
@@ -211,31 +208,28 @@ function runWithEnvironment(
|
||||
lowerContextAccess(hir, env.config.lowerContextAccess);
|
||||
}
|
||||
|
||||
optimizePropsMethodCalls(hir);
|
||||
log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
|
||||
|
||||
analyseFunctions(hir);
|
||||
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
inferReferenceEffects(hir);
|
||||
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
deadCodeElimination(hir);
|
||||
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
|
||||
if (env.config.enableInstructionReordering) {
|
||||
instructionReordering(hir);
|
||||
log({kind: 'hir', name: 'InstructionReordering', value: hir});
|
||||
yield log({kind: 'hir', name: 'InstructionReordering', value: hir});
|
||||
}
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
inferMutableRanges(hir);
|
||||
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
|
||||
yield log({kind: 'hir', name: 'InferMutableRanges', value: hir});
|
||||
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
@@ -258,27 +252,27 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
inferReactivePlaces(hir);
|
||||
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'RewriteInstructionKindsBasedOnReassignment',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
propagatePhiTypes(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'PropagatePhiTypes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
inferReactiveScopeVariables(hir);
|
||||
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
|
||||
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'MemoizeFbtAndMacroOperandsInSameScope',
|
||||
value: hir,
|
||||
@@ -290,39 +284,39 @@ function runWithEnvironment(
|
||||
|
||||
if (env.config.enableFunctionOutlining) {
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
}
|
||||
|
||||
alignMethodCallScopes(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'AlignMethodCallScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignObjectMethodScopes(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'AlignObjectMethodScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'PruneUnusedLabelsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'AlignReactiveScopesToBlockScopesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'MergeOverlappingReactiveScopesHIR',
|
||||
value: hir,
|
||||
@@ -330,7 +324,7 @@ function runWithEnvironment(
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'BuildReactiveScopeTerminalsHIR',
|
||||
value: hir,
|
||||
@@ -339,14 +333,14 @@ function runWithEnvironment(
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenReactiveLoopsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenScopesWithHooksOrUseHIR',
|
||||
value: hir,
|
||||
@@ -354,19 +348,15 @@ function runWithEnvironment(
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
assertTerminalPredsExist(hir);
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'PropagateScopeDependenciesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
if (env.config.inferEffectDependencies) {
|
||||
inferEffectDependencies(hir);
|
||||
}
|
||||
|
||||
if (env.config.inlineJsxTransform) {
|
||||
inlineJsxTransform(hir, env.config.inlineJsxTransform);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'inlineJsxTransform',
|
||||
value: hir,
|
||||
@@ -374,7 +364,7 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveFunction',
|
||||
value: reactiveFunction,
|
||||
@@ -383,7 +373,7 @@ function runWithEnvironment(
|
||||
assertWellFormedBreakTargets(reactiveFunction);
|
||||
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLabels',
|
||||
value: reactiveFunction,
|
||||
@@ -391,35 +381,35 @@ function runWithEnvironment(
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonEscapingScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonReactiveDependencies',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'MergeReactiveScopesThatInvalidateTogether',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneAlwaysInvalidatingScopes',
|
||||
value: reactiveFunction,
|
||||
@@ -427,7 +417,7 @@ function runWithEnvironment(
|
||||
|
||||
if (env.config.enableChangeDetectionForDebugging != null) {
|
||||
pruneInitializationDependencies(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneInitializationDependencies',
|
||||
value: reactiveFunction,
|
||||
@@ -435,49 +425,49 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PropagateEarlyReturns',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLValues',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PromoteUsedTemporaries',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'ExtractScopeDeclarationsFromDestructuring',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'StabilizeBlockIds',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
const uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'RenameVariables',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
log({
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneHoistedContexts',
|
||||
value: reactiveFunction,
|
||||
@@ -498,9 +488,9 @@ function runWithEnvironment(
|
||||
uniqueIdentifiers,
|
||||
fbtOperands,
|
||||
}).unwrap();
|
||||
log({kind: 'ast', name: 'Codegen', value: ast});
|
||||
yield log({kind: 'ast', name: 'Codegen', value: ast});
|
||||
for (const outlined of ast.outlined) {
|
||||
log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,7 +516,7 @@ export function compileFn(
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
): CodegenFunction {
|
||||
return run(
|
||||
let generator = run(
|
||||
func,
|
||||
config,
|
||||
fnType,
|
||||
@@ -535,4 +525,46 @@ export function compileFn(
|
||||
filename,
|
||||
code,
|
||||
);
|
||||
while (true) {
|
||||
const next = generator.next();
|
||||
if (next.done) {
|
||||
return next.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function log(value: CompilerPipelineValue): CompilerPipelineValue {
|
||||
switch (value.kind) {
|
||||
case 'ast': {
|
||||
logCodegenFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
logHIRFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
logReactiveFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
logDebug(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(value, 'Unexpected compilation kind');
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function* runPlayground(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
const ast = yield* run(func, config, fnType, '_c', null, null, null);
|
||||
return ast;
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ export type CompilerPass = {
|
||||
comments: Array<t.CommentBlock | t.CommentLine>;
|
||||
code: string | null;
|
||||
};
|
||||
export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
|
||||
const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
|
||||
export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']);
|
||||
|
||||
export function findDirectiveEnablingMemoization(
|
||||
function findDirectiveEnablingMemoization(
|
||||
directives: Array<t.Directive>,
|
||||
): Array<t.Directive> {
|
||||
return directives.filter(directive =>
|
||||
@@ -53,7 +53,7 @@ export function findDirectiveEnablingMemoization(
|
||||
);
|
||||
}
|
||||
|
||||
export function findDirectiveDisablingMemoization(
|
||||
function findDirectiveDisablingMemoization(
|
||||
directives: Array<t.Directive>,
|
||||
): Array<t.Directive> {
|
||||
return directives.filter(directive =>
|
||||
@@ -564,14 +564,6 @@ export function compileProgram(
|
||||
if (environment.enableChangeDetectionForDebugging != null) {
|
||||
externalFunctions.push(environment.enableChangeDetectionForDebugging);
|
||||
}
|
||||
|
||||
const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite);
|
||||
if (environment.enableFire && hasFireRewrite) {
|
||||
externalFunctions.push({
|
||||
source: getReactCompilerRuntimeModule(pass.opts),
|
||||
importSpecifierName: 'useFire',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, pass, null);
|
||||
return;
|
||||
@@ -1131,23 +1123,30 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
return errors.details.length > 0 ? errors : null;
|
||||
}
|
||||
|
||||
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
|
||||
if (opts.target === '19') {
|
||||
return 'react/compiler-runtime'; // from react namespace
|
||||
} else if (opts.target === '17' || opts.target === '18') {
|
||||
return 'react-compiler-runtime'; // npm package
|
||||
} else {
|
||||
CompilerError.invariant(
|
||||
opts.target != null &&
|
||||
opts.target.kind === 'donotuse_meta_internal' &&
|
||||
typeof opts.target.runtimeModule === 'string',
|
||||
{
|
||||
type ReactCompilerRuntimeModule =
|
||||
| 'react/compiler-runtime' // from react namespace
|
||||
| 'react-compiler-runtime'; // npm package
|
||||
function getReactCompilerRuntimeModule(
|
||||
opts: PluginOptions,
|
||||
): ReactCompilerRuntimeModule {
|
||||
let moduleName: ReactCompilerRuntimeModule | null = null;
|
||||
switch (opts.target) {
|
||||
case '17':
|
||||
case '18': {
|
||||
moduleName = 'react-compiler-runtime';
|
||||
break;
|
||||
}
|
||||
case '19': {
|
||||
moduleName = 'react/compiler-runtime';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(moduleName != null, {
|
||||
reason: 'Expected target to already be validated',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
return opts.target.runtimeModule;
|
||||
});
|
||||
}
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
@@ -1078,12 +1078,6 @@ function lowerStatement(
|
||||
const left = stmt.get('left');
|
||||
const leftLoc = left.node.loc ?? GeneratedSource;
|
||||
let test: Place;
|
||||
const advanceIterator = lowerValueToTemporary(builder, {
|
||||
kind: 'IteratorNext',
|
||||
loc: leftLoc,
|
||||
iterator: {...iterator},
|
||||
collection: {...value},
|
||||
});
|
||||
if (left.isVariableDeclaration()) {
|
||||
const declarations = left.get('declarations');
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
@@ -1093,6 +1087,12 @@ function lowerStatement(
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get('id');
|
||||
const advanceIterator = lowerValueToTemporary(builder, {
|
||||
kind: 'IteratorNext',
|
||||
loc: leftLoc,
|
||||
iterator: {...iterator},
|
||||
collection: {...value},
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
@@ -1103,19 +1103,13 @@ function lowerStatement(
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
CompilerError.invariant(left.isLVal(), {
|
||||
loc: leftLoc,
|
||||
reason: 'Expected ForOf init to be a variable declaration or lval',
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
InstructionKind.Reassign,
|
||||
left,
|
||||
advanceIterator,
|
||||
'Assignment',
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
return;
|
||||
}
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
@@ -1172,11 +1166,6 @@ function lowerStatement(
|
||||
const left = stmt.get('left');
|
||||
const leftLoc = left.node.loc ?? GeneratedSource;
|
||||
let test: Place;
|
||||
const nextPropertyTemp = lowerValueToTemporary(builder, {
|
||||
kind: 'NextPropertyOf',
|
||||
loc: leftLoc,
|
||||
value,
|
||||
});
|
||||
if (left.isVariableDeclaration()) {
|
||||
const declarations = left.get('declarations');
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
@@ -1186,6 +1175,11 @@ function lowerStatement(
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get('id');
|
||||
const nextPropertyTemp = lowerValueToTemporary(builder, {
|
||||
kind: 'NextPropertyOf',
|
||||
loc: leftLoc,
|
||||
value,
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
@@ -1196,19 +1190,13 @@ function lowerStatement(
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
CompilerError.invariant(left.isLVal(), {
|
||||
loc: leftLoc,
|
||||
reason: 'Expected ForIn init to be a variable declaration or lval',
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForInStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
InstructionKind.Reassign,
|
||||
left,
|
||||
nextPropertyTemp,
|
||||
'Assignment',
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
return;
|
||||
}
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
@@ -3198,13 +3186,7 @@ function lowerJsxMemberExpression(
|
||||
loc: object.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
const kind = getLoadKind(builder, object);
|
||||
objectPlace = lowerValueToTemporary(builder, {
|
||||
kind: kind,
|
||||
place: lowerIdentifier(builder, object),
|
||||
loc: exprPath.node.loc ?? GeneratedSource,
|
||||
});
|
||||
objectPlace = lowerIdentifier(builder, object);
|
||||
}
|
||||
const property = exprPath.get('property').node.name;
|
||||
return lowerValueToTemporary(builder, {
|
||||
|
||||
@@ -9,13 +9,7 @@ import * as t from '@babel/types';
|
||||
import {ZodError, z} from 'zod';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
CompilationMode,
|
||||
Logger,
|
||||
PanicThresholdOptions,
|
||||
parsePluginOptions,
|
||||
PluginOptions,
|
||||
} from '../Entrypoint';
|
||||
import {Logger} from '../Entrypoint';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
DEFAULT_GLOBALS,
|
||||
@@ -174,19 +168,11 @@ const EnvironmentConfigSchema = z.object({
|
||||
customMacros: z.nullable(z.array(MacroSchema)).default(null),
|
||||
|
||||
/**
|
||||
* Enable a check that resets the memoization cache when the source code of
|
||||
* the file changes. This is intended to support hot module reloading (HMR),
|
||||
* where the same runtime component instance will be reused across different
|
||||
* versions of the component source.
|
||||
*
|
||||
* When set to
|
||||
* - true: code for HMR support is always generated, regardless of NODE_ENV
|
||||
* or `globalThis.__DEV__`
|
||||
* - false: code for HMR support is not generated
|
||||
* - null: (default) code for HMR support is conditionally generated dependent
|
||||
* on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation.
|
||||
* Enable a check that resets the memoization cache when the source code of the file changes.
|
||||
* This is intended to support hot module reloading (HMR), where the same runtime component
|
||||
* instance will be reused across different versions of the component source.
|
||||
*/
|
||||
enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null),
|
||||
enableResetCacheOnSourceFileChanges: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enable using information from existing useMemo/useCallback to understand when a value is done
|
||||
@@ -245,54 +231,6 @@ const EnvironmentConfigSchema = z.object({
|
||||
*/
|
||||
enableUseTypeAnnotations: z.boolean().default(false),
|
||||
|
||||
enableFunctionDependencyRewrite: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* Enables inference of optional dependency chains. Without this flag
|
||||
* a property chain such as `props?.items?.foo` will infer as a dep on
|
||||
* just `props`. With this flag enabled, we'll infer that full path as
|
||||
* the dependency.
|
||||
*/
|
||||
enableOptionalDependencies: z.boolean().default(true),
|
||||
|
||||
enableFire: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables inference and auto-insertion of effect dependencies. Takes in an array of
|
||||
* configurable module and import pairs to allow for user-land experimentation. For example,
|
||||
* [
|
||||
* {
|
||||
* module: 'react',
|
||||
* imported: 'useEffect',
|
||||
* numRequiredArgs: 1,
|
||||
* },{
|
||||
* module: 'MyExperimentalEffectHooks',
|
||||
* imported: 'useExperimentalEffect',
|
||||
* numRequiredArgs: 2,
|
||||
* },
|
||||
* ]
|
||||
* would insert dependencies for calls of `useEffect` imported from `react` and calls of
|
||||
* useExperimentalEffect` from `MyExperimentalEffectHooks`.
|
||||
*
|
||||
* `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency
|
||||
* array to the end of the call. With the configuration above, we'd insert dependencies for
|
||||
* `useEffect` if it is only given a single argument and it would be appended to the argument list.
|
||||
*
|
||||
* numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies
|
||||
*
|
||||
* Still experimental.
|
||||
*/
|
||||
inferEffectDependencies: z
|
||||
.nullable(
|
||||
z.array(
|
||||
z.object({
|
||||
function: ExternalFunctionSchema,
|
||||
numRequiredArgs: z.number(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.default(null),
|
||||
|
||||
/**
|
||||
* Enables inlining ReactElement object literals in place of JSX
|
||||
* An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime
|
||||
@@ -661,37 +599,12 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
|
||||
source: 'react-compiler-runtime',
|
||||
importSpecifierName: 'useContext_withSelector',
|
||||
},
|
||||
inferEffectDependencies: [
|
||||
{
|
||||
function: {
|
||||
source: 'react',
|
||||
importSpecifierName: 'useEffect',
|
||||
},
|
||||
numRequiredArgs: 1,
|
||||
},
|
||||
{
|
||||
function: {
|
||||
source: 'shared-runtime',
|
||||
importSpecifierName: 'useSpecialEffect',
|
||||
},
|
||||
numRequiredArgs: 2,
|
||||
},
|
||||
{
|
||||
function: {
|
||||
source: 'useEffectWrapper',
|
||||
importSpecifierName: 'default',
|
||||
},
|
||||
numRequiredArgs: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* For snap test fixtures and playground only.
|
||||
*/
|
||||
function parseConfigPragmaEnvironmentForTest(
|
||||
pragma: string,
|
||||
): EnvironmentConfig {
|
||||
export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
|
||||
const maybeConfig: any = {};
|
||||
// Get the defaults to programmatically check for boolean properties
|
||||
const defaultConfig = EnvironmentConfigSchema.parse({});
|
||||
@@ -726,10 +639,7 @@ function parseConfigPragmaEnvironmentForTest(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
key !== 'enableResetCacheOnSourceFileChanges' &&
|
||||
typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean'
|
||||
) {
|
||||
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') {
|
||||
// skip parsing non-boolean properties
|
||||
continue;
|
||||
}
|
||||
@@ -739,15 +649,9 @@ function parseConfigPragmaEnvironmentForTest(
|
||||
maybeConfig[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
const config = EnvironmentConfigSchema.safeParse(maybeConfig);
|
||||
if (config.success) {
|
||||
/**
|
||||
* Unless explicitly enabled, do not insert HMR handling code
|
||||
* in test fixtures or playground to reduce visual noise.
|
||||
*/
|
||||
if (config.data.enableResetCacheOnSourceFileChanges == null) {
|
||||
config.data.enableResetCacheOnSourceFileChanges = false;
|
||||
}
|
||||
return config.data;
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
@@ -757,48 +661,6 @@ function parseConfigPragmaEnvironmentForTest(
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
export function parseConfigPragmaForTests(
|
||||
pragma: string,
|
||||
defaults: {
|
||||
compilationMode: CompilationMode;
|
||||
},
|
||||
): PluginOptions {
|
||||
const environment = parseConfigPragmaEnvironmentForTest(pragma);
|
||||
let compilationMode: CompilationMode = defaults.compilationMode;
|
||||
let panicThreshold: PanicThresholdOptions = 'all_errors';
|
||||
for (const token of pragma.split(' ')) {
|
||||
if (!token.startsWith('@')) {
|
||||
continue;
|
||||
}
|
||||
switch (token) {
|
||||
case '@compilationMode(annotation)': {
|
||||
compilationMode = 'annotation';
|
||||
break;
|
||||
}
|
||||
case '@compilationMode(infer)': {
|
||||
compilationMode = 'infer';
|
||||
break;
|
||||
}
|
||||
case '@compilationMode(all)': {
|
||||
compilationMode = 'all';
|
||||
break;
|
||||
}
|
||||
case '@compilationMode(syntax)': {
|
||||
compilationMode = 'syntax';
|
||||
break;
|
||||
}
|
||||
case '@panicThreshold(none)': {
|
||||
panicThreshold = 'none';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsePluginOptions({
|
||||
environment,
|
||||
compilationMode,
|
||||
panicThreshold,
|
||||
});
|
||||
}
|
||||
|
||||
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;
|
||||
|
||||
@@ -837,7 +699,6 @@ export class Environment {
|
||||
fnType: ReactFunctionType;
|
||||
useMemoCacheIdentifier: string;
|
||||
hasLoweredContextAccess: boolean;
|
||||
hasFireRewrite: boolean;
|
||||
|
||||
#contextIdentifiers: Set<t.Identifier>;
|
||||
#hoistedIdentifiers: Set<t.Identifier>;
|
||||
@@ -862,7 +723,6 @@ export class Environment {
|
||||
this.#shapes = new Map(DEFAULT_SHAPES);
|
||||
this.#globals = new Map(DEFAULT_GLOBALS);
|
||||
this.hasLoweredContextAccess = false;
|
||||
this.hasFireRewrite = false;
|
||||
|
||||
if (
|
||||
config.disableMemoizationForDebugging &&
|
||||
@@ -1225,5 +1085,3 @@ export function tryParseExternalFunction(
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
export const DEFAULT_EXPORT = 'default';
|
||||
|
||||
@@ -9,7 +9,6 @@ import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInFireId,
|
||||
BuiltInMixedReadonlyId,
|
||||
BuiltInUseActionStateId,
|
||||
BuiltInUseContextHookId,
|
||||
@@ -88,21 +87,6 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
|
||||
]);
|
||||
|
||||
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
[
|
||||
'Object',
|
||||
addObject(DEFAULT_SHAPES, 'Object', [
|
||||
[
|
||||
'keys',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]),
|
||||
],
|
||||
[
|
||||
'Array',
|
||||
addObject(DEFAULT_SHAPES, 'Array', [
|
||||
@@ -484,21 +468,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
BuiltInUseOperatorId,
|
||||
),
|
||||
],
|
||||
[
|
||||
'fire',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInFireId,
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
TYPED_GLOBALS.push(
|
||||
|
||||
@@ -840,11 +840,6 @@ export type LoadLocal = {
|
||||
place: Place;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
export type LoadContext = {
|
||||
kind: 'LoadContext';
|
||||
place: Place;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
/*
|
||||
* The value of a given instruction. Note that values are not recursive: complex
|
||||
@@ -857,7 +852,11 @@ export type LoadContext = {
|
||||
|
||||
export type InstructionValue =
|
||||
| LoadLocal
|
||||
| LoadContext
|
||||
| {
|
||||
kind: 'LoadContext';
|
||||
place: Place;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| {
|
||||
kind: 'DeclareLocal';
|
||||
lvalue: LValue;
|
||||
@@ -1645,10 +1644,6 @@ export function isArrayType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
|
||||
}
|
||||
|
||||
export function isPropsType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
|
||||
}
|
||||
|
||||
export function isRefValueType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue';
|
||||
}
|
||||
|
||||
@@ -213,7 +213,6 @@ export const BuiltInDispatchId = 'BuiltInDispatch';
|
||||
export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
|
||||
export const BuiltInUseTransitionId = 'BuiltInUseTransition';
|
||||
export const BuiltInStartTransitionId = 'BuiltInStartTransition';
|
||||
export const BuiltInFireId = 'BuiltInFire';
|
||||
|
||||
// ShapeRegistry with default definitions for built-ins.
|
||||
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
|
||||
|
||||
@@ -897,14 +897,6 @@ export function printSourceLocation(loc: SourceLocation): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function printSourceLocationLine(loc: SourceLocation): string {
|
||||
if (typeof loc === 'symbol') {
|
||||
return 'generated';
|
||||
} else {
|
||||
return `${loc.start.line}:${loc.end.line}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function printAliases(aliases: DisjointSet<Identifier>): string {
|
||||
const aliasSets = aliases.buildSets();
|
||||
|
||||
|
||||
@@ -17,11 +17,6 @@ import {
|
||||
areEqualPaths,
|
||||
IdentifierId,
|
||||
Terminal,
|
||||
InstructionValue,
|
||||
LoadContext,
|
||||
TInstruction,
|
||||
FunctionExpression,
|
||||
ObjectMethod,
|
||||
} from './HIR';
|
||||
import {
|
||||
collectHoistablePropertyLoads,
|
||||
@@ -228,25 +223,11 @@ export function collectTemporariesSidemap(
|
||||
fn,
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
return temporaries;
|
||||
}
|
||||
|
||||
function isLoadContextMutable(
|
||||
instrValue: InstructionValue,
|
||||
id: InstructionId,
|
||||
): instrValue is LoadContext {
|
||||
if (instrValue.kind === 'LoadContext') {
|
||||
CompilerError.invariant(instrValue.place.identifier.scope != null, {
|
||||
reason:
|
||||
'[PropagateScopeDependencies] Expected all context variables to be assigned a scope',
|
||||
loc: instrValue.loc,
|
||||
});
|
||||
return id >= instrValue.place.identifier.scope.range.end;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a
|
||||
* function and all nested functions.
|
||||
@@ -258,21 +239,17 @@ function collectTemporariesSidemapImpl(
|
||||
fn: HIRFunction,
|
||||
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
|
||||
temporaries: Map<IdentifierId, ReactiveScopeDependency>,
|
||||
innerFnContext: {instrId: InstructionId} | null,
|
||||
isInnerFn: boolean,
|
||||
): void {
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const {value, lvalue, id: origInstrId} of block.instructions) {
|
||||
const instrId =
|
||||
innerFnContext != null ? innerFnContext.instrId : origInstrId;
|
||||
for (const instr of block.instructions) {
|
||||
const {value, lvalue} = instr;
|
||||
const usedOutside = usedOutsideDeclaringScope.has(
|
||||
lvalue.identifier.declarationId,
|
||||
);
|
||||
|
||||
if (value.kind === 'PropertyLoad' && !usedOutside) {
|
||||
if (
|
||||
innerFnContext == null ||
|
||||
temporaries.has(value.object.identifier.id)
|
||||
) {
|
||||
if (!isInnerFn || temporaries.has(value.object.identifier.id)) {
|
||||
/**
|
||||
* All dependencies of a inner / nested function must have a base
|
||||
* identifier from the outermost component / hook. This is because the
|
||||
@@ -288,13 +265,13 @@ function collectTemporariesSidemapImpl(
|
||||
temporaries.set(lvalue.identifier.id, property);
|
||||
}
|
||||
} else if (
|
||||
(value.kind === 'LoadLocal' || isLoadContextMutable(value, instrId)) &&
|
||||
value.kind === 'LoadLocal' &&
|
||||
lvalue.identifier.name == null &&
|
||||
value.place.identifier.name !== null &&
|
||||
!usedOutside
|
||||
) {
|
||||
if (
|
||||
innerFnContext == null ||
|
||||
!isInnerFn ||
|
||||
fn.context.some(
|
||||
context => context.identifier.id === value.place.identifier.id,
|
||||
)
|
||||
@@ -312,7 +289,7 @@ function collectTemporariesSidemapImpl(
|
||||
value.loweredFunc.func,
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
innerFnContext ?? {instrId},
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -381,22 +358,19 @@ class Context {
|
||||
|
||||
#temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
|
||||
#temporariesUsedOutsideScope: ReadonlySet<DeclarationId>;
|
||||
#processedInstrsInOptional: ReadonlySet<Instruction | Terminal>;
|
||||
|
||||
/**
|
||||
* Tracks the traversal state. See Context.declare for explanation of why this
|
||||
* is needed.
|
||||
*/
|
||||
#innerFnContext: {outerInstrId: InstructionId} | null = null;
|
||||
inInnerFn: boolean = false;
|
||||
|
||||
constructor(
|
||||
temporariesUsedOutsideScope: ReadonlySet<DeclarationId>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
) {
|
||||
this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope;
|
||||
this.#temporaries = temporaries;
|
||||
this.#processedInstrsInOptional = processedInstrsInOptional;
|
||||
}
|
||||
|
||||
enterScope(scope: ReactiveScope): void {
|
||||
@@ -457,7 +431,7 @@ class Context {
|
||||
* by root identifier mutable ranges).
|
||||
*/
|
||||
declare(identifier: Identifier, decl: Decl): void {
|
||||
if (this.#innerFnContext != null) return;
|
||||
if (this.inInnerFn) return;
|
||||
if (!this.#declarations.has(identifier.declarationId)) {
|
||||
this.#declarations.set(identifier.declarationId, decl);
|
||||
}
|
||||
@@ -466,6 +440,14 @@ class Context {
|
||||
|
||||
// Checks if identifier is a valid dependency in the current scope
|
||||
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
|
||||
// ref.current access is not a valid dep
|
||||
if (
|
||||
isUseRefType(maybeDependency.identifier) &&
|
||||
maybeDependency.path.at(0)?.property === 'current'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ref value is not a valid dep
|
||||
if (isRefValueType(maybeDependency.identifier)) {
|
||||
return false;
|
||||
@@ -567,16 +549,6 @@ class Context {
|
||||
});
|
||||
}
|
||||
|
||||
// ref.current access is not a valid dep
|
||||
if (
|
||||
isUseRefType(maybeDependency.identifier) &&
|
||||
maybeDependency.path.at(0)?.property === 'current'
|
||||
) {
|
||||
maybeDependency = {
|
||||
identifier: maybeDependency.identifier,
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
if (this.#checkValidDependency(maybeDependency)) {
|
||||
this.#dependencies.value!.push(maybeDependency);
|
||||
}
|
||||
@@ -600,52 +572,22 @@ class Context {
|
||||
currentScope.reassignments.add(place.identifier);
|
||||
}
|
||||
}
|
||||
enterInnerFn<T>(
|
||||
innerFn: TInstruction<FunctionExpression> | TInstruction<ObjectMethod>,
|
||||
cb: () => T,
|
||||
): T {
|
||||
const prevContext = this.#innerFnContext;
|
||||
this.#innerFnContext = this.#innerFnContext ?? {outerInstrId: innerFn.id};
|
||||
const result = cb();
|
||||
this.#innerFnContext = prevContext;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip dependencies that are subexpressions of other dependencies. e.g. if a
|
||||
* dependency is tracked in the temporaries sidemap, it can be added at
|
||||
* site-of-use
|
||||
*/
|
||||
isDeferredDependency(
|
||||
instr:
|
||||
| {kind: HIRValue.Instruction; value: Instruction}
|
||||
| {kind: HIRValue.Terminal; value: Terminal},
|
||||
): boolean {
|
||||
return (
|
||||
this.#processedInstrsInOptional.has(instr.value) ||
|
||||
(instr.kind === HIRValue.Instruction &&
|
||||
this.#temporaries.has(instr.value.lvalue.identifier.id))
|
||||
);
|
||||
}
|
||||
}
|
||||
enum HIRValue {
|
||||
Instruction = 1,
|
||||
Terminal,
|
||||
}
|
||||
|
||||
function handleInstruction(instr: Instruction, context: Context): void {
|
||||
const {id, value, lvalue} = instr;
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
if (
|
||||
context.isDeferredDependency({kind: HIRValue.Instruction, value: instr})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (value.kind === 'PropertyLoad') {
|
||||
context.visitProperty(value.object, value.property, false);
|
||||
if (value.kind === 'LoadLocal') {
|
||||
if (
|
||||
value.place.identifier.name === null ||
|
||||
lvalue.identifier.name !== null ||
|
||||
context.isUsedOutsideDeclaringScope(lvalue)
|
||||
) {
|
||||
context.visitOperand(value.place);
|
||||
}
|
||||
} else if (value.kind === 'PropertyLoad') {
|
||||
if (context.isUsedOutsideDeclaringScope(lvalue)) {
|
||||
context.visitProperty(value.object, value.property, false);
|
||||
}
|
||||
} else if (value.kind === 'StoreLocal') {
|
||||
context.visitOperand(value.value);
|
||||
if (value.lvalue.kind === InstructionKind.Reassign) {
|
||||
@@ -688,6 +630,11 @@ function handleInstruction(instr: Instruction, context: Context): void {
|
||||
context.visitOperand(operand);
|
||||
}
|
||||
}
|
||||
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
|
||||
function collectDependencies(
|
||||
@@ -696,11 +643,7 @@ function collectDependencies(
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
|
||||
const context = new Context(
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
const context = new Context(usedOutsideDeclaringScope, temporaries);
|
||||
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === 'Identifier') {
|
||||
@@ -718,64 +661,35 @@ function collectDependencies(
|
||||
|
||||
const scopeTraversal = new ScopeBlockTraversal();
|
||||
|
||||
const handleFunction = (fn: HIRFunction): void => {
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
scopeTraversal.recordScopes(block);
|
||||
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
|
||||
if (scopeBlockInfo?.kind === 'begin') {
|
||||
context.enterScope(scopeBlockInfo.scope);
|
||||
} else if (scopeBlockInfo?.kind === 'end') {
|
||||
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned);
|
||||
}
|
||||
// Record referenced optional chains in phis
|
||||
for (const phi of block.phis) {
|
||||
for (const operand of phi.operands) {
|
||||
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
|
||||
if (maybeOptionalChain) {
|
||||
context.visitDependency(maybeOptionalChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
fn.env.config.enableFunctionDependencyRewrite &&
|
||||
(instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod')
|
||||
) {
|
||||
context.declare(instr.lvalue.identifier, {
|
||||
id: instr.id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
/**
|
||||
* Recursively visit the inner function to extract dependencies there
|
||||
*/
|
||||
const innerFn = instr.value.loweredFunc.func;
|
||||
context.enterInnerFn(
|
||||
instr as
|
||||
| TInstruction<FunctionExpression>
|
||||
| TInstruction<ObjectMethod>,
|
||||
() => {
|
||||
handleFunction(innerFn);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
}
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
scopeTraversal.recordScopes(block);
|
||||
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
|
||||
if (scopeBlockInfo?.kind === 'begin') {
|
||||
context.enterScope(scopeBlockInfo.scope);
|
||||
} else if (scopeBlockInfo?.kind === 'end') {
|
||||
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned);
|
||||
}
|
||||
|
||||
if (
|
||||
!context.isDeferredDependency({
|
||||
kind: HIRValue.Terminal,
|
||||
value: block.terminal,
|
||||
})
|
||||
) {
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
context.visitOperand(place);
|
||||
// Record referenced optional chains in phis
|
||||
for (const phi of block.phis) {
|
||||
for (const operand of phi.operands) {
|
||||
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
|
||||
if (maybeOptionalChain) {
|
||||
context.visitDependency(maybeOptionalChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const instr of block.instructions) {
|
||||
if (!processedInstrsInOptional.has(instr)) {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
}
|
||||
|
||||
handleFunction(fn);
|
||||
if (!processedInstrsInOptional.has(block.terminal)) {
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
context.visitOperand(place);
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.deps;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import {deadCodeElimination} from '../Optimization';
|
||||
import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
||||
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {inferMutableContextVariables} from './InferMutableContextVariables';
|
||||
import {inferMutableRanges} from './InferMutableRanges';
|
||||
import inferReferenceEffects from './InferReferenceEffects';
|
||||
@@ -111,11 +112,7 @@ function lower(func: HIRFunction): void {
|
||||
rewriteInstructionKindsBasedOnReassignment(func);
|
||||
inferReactiveScopeVariables(func);
|
||||
inferMutableContextVariables(func);
|
||||
func.env.logger?.debugLogIRs?.({
|
||||
kind: 'hir',
|
||||
name: 'AnalyseFunction (inner)',
|
||||
value: func,
|
||||
});
|
||||
logHIRFunction('AnalyseFunction (inner)', func);
|
||||
}
|
||||
|
||||
function infer(
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {
|
||||
ArrayExpression,
|
||||
Effect,
|
||||
Environment,
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
makeInstructionId,
|
||||
TInstruction,
|
||||
InstructionId,
|
||||
ScopeId,
|
||||
ReactiveScopeDependency,
|
||||
Place,
|
||||
ReactiveScopeDependencies,
|
||||
} from '../HIR';
|
||||
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
||||
import {
|
||||
createTemporaryPlace,
|
||||
fixScopeAndIdentifierRanges,
|
||||
markInstructionIds,
|
||||
} from '../HIR/HIRBuilder';
|
||||
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* Infers reactive dependencies captured by useEffect lambdas and adds them as
|
||||
* a second argument to the useEffect call if no dependency array is provided.
|
||||
*/
|
||||
export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
let hasRewrite = false;
|
||||
const fnExpressions = new Map<
|
||||
IdentifierId,
|
||||
TInstruction<FunctionExpression>
|
||||
>();
|
||||
|
||||
const autodepFnConfigs = new Map<string, Map<string, number>>();
|
||||
for (const effectTarget of fn.env.config.inferEffectDependencies!) {
|
||||
const moduleTargets = getOrInsertWith(
|
||||
autodepFnConfigs,
|
||||
effectTarget.function.source,
|
||||
() => new Map<string, number>(),
|
||||
);
|
||||
moduleTargets.set(
|
||||
effectTarget.function.importSpecifierName,
|
||||
effectTarget.numRequiredArgs,
|
||||
);
|
||||
}
|
||||
const autodepFnLoads = new Map<IdentifierId, number>();
|
||||
|
||||
const scopeInfos = new Map<
|
||||
ScopeId,
|
||||
{pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
|
||||
>();
|
||||
|
||||
const loadGlobals = new Set<IdentifierId>();
|
||||
|
||||
/**
|
||||
* When inserting LoadLocals, we need to retain the reactivity of the base
|
||||
* identifier, as later passes e.g. PruneNonReactiveDeps take the reactivity of
|
||||
* a base identifier as the "maximal" reactivity of all its references.
|
||||
* Concretely,
|
||||
* reactive(Identifier i) = Union_{reference of i}(reactive(reference))
|
||||
*/
|
||||
const reactiveIds = inferReactiveIdentifiers(fn);
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
if (
|
||||
block.terminal.kind === 'scope' ||
|
||||
block.terminal.kind === 'pruned-scope'
|
||||
) {
|
||||
const scopeBlock = fn.body.blocks.get(block.terminal.block)!;
|
||||
scopeInfos.set(block.terminal.scope.id, {
|
||||
pruned: block.terminal.kind === 'pruned-scope',
|
||||
deps: block.terminal.scope.dependencies,
|
||||
hasSingleInstr:
|
||||
scopeBlock.instructions.length === 1 &&
|
||||
scopeBlock.terminal.kind === 'goto' &&
|
||||
scopeBlock.terminal.block === block.terminal.fallthrough,
|
||||
});
|
||||
}
|
||||
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
|
||||
for (const instr of block.instructions) {
|
||||
const {value, lvalue} = instr;
|
||||
if (value.kind === 'FunctionExpression') {
|
||||
fnExpressions.set(
|
||||
lvalue.identifier.id,
|
||||
instr as TInstruction<FunctionExpression>,
|
||||
);
|
||||
} else if (value.kind === 'LoadGlobal') {
|
||||
loadGlobals.add(lvalue.identifier.id);
|
||||
|
||||
if (
|
||||
value.binding.kind === 'ImportSpecifier' ||
|
||||
value.binding.kind === 'ImportDefault'
|
||||
) {
|
||||
const moduleTargets = autodepFnConfigs.get(value.binding.module);
|
||||
if (moduleTargets != null) {
|
||||
const importSpecifierName =
|
||||
value.binding.kind === 'ImportSpecifier'
|
||||
? value.binding.imported
|
||||
: DEFAULT_EXPORT;
|
||||
const numRequiredArgs = moduleTargets.get(importSpecifierName);
|
||||
if (numRequiredArgs != null) {
|
||||
autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
/*
|
||||
* TODO: Handle method calls
|
||||
*/
|
||||
value.kind === 'CallExpression' &&
|
||||
autodepFnLoads.get(value.callee.identifier.id) === value.args.length &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const effectDeps: Array<Place> = [];
|
||||
const newInstructions: Array<Instruction> = [];
|
||||
const deps: ArrayExpression = {
|
||||
kind: 'ArrayExpression',
|
||||
elements: effectDeps,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
const depsPlace = createTemporaryPlace(fn.env, GeneratedSource);
|
||||
depsPlace.effect = Effect.Read;
|
||||
|
||||
const fnExpr = fnExpressions.get(value.args[0].identifier.id);
|
||||
if (fnExpr != null) {
|
||||
// We have a function expression, so we can infer its dependencies
|
||||
const scopeInfo =
|
||||
fnExpr.lvalue.identifier.scope != null
|
||||
? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
|
||||
: null;
|
||||
CompilerError.invariant(scopeInfo != null, {
|
||||
reason: 'Expected function expression scope to exist',
|
||||
loc: value.loc,
|
||||
});
|
||||
if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) {
|
||||
/**
|
||||
* TODO: retry pipeline that ensures effect function expressions
|
||||
* are placed into their own scope
|
||||
*/
|
||||
CompilerError.throwTodo({
|
||||
reason:
|
||||
'[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction',
|
||||
loc: fnExpr.loc,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: push dependencies to the effect deps array
|
||||
*
|
||||
* Note that it's invalid to prune non-reactive deps in this pass, see
|
||||
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
|
||||
* explanation.
|
||||
*/
|
||||
for (const dep of scopeInfo.deps) {
|
||||
const {place, instructions} = writeDependencyToInstructions(
|
||||
dep,
|
||||
reactiveIds.has(dep.identifier.id),
|
||||
fn.env,
|
||||
fnExpr.loc,
|
||||
);
|
||||
newInstructions.push(...instructions);
|
||||
effectDeps.push(place);
|
||||
}
|
||||
|
||||
newInstructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...depsPlace, effect: Effect.Mutate},
|
||||
value: deps,
|
||||
});
|
||||
|
||||
// Step 2: push the inferred deps array as an argument of the useEffect
|
||||
value.args.push({...depsPlace, effect: Effect.Freeze});
|
||||
rewriteInstrs.set(instr.id, newInstructions);
|
||||
} else if (loadGlobals.has(value.args[0].identifier.id)) {
|
||||
// Global functions have no reactive dependencies, so we can insert an empty array
|
||||
newInstructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...depsPlace, effect: Effect.Mutate},
|
||||
value: deps,
|
||||
});
|
||||
value.args.push({...depsPlace, effect: Effect.Freeze});
|
||||
rewriteInstrs.set(instr.id, newInstructions);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rewriteInstrs.size > 0) {
|
||||
hasRewrite = true;
|
||||
const newInstrs = [];
|
||||
for (const instr of block.instructions) {
|
||||
const newInstr = rewriteInstrs.get(instr.id);
|
||||
if (newInstr != null) {
|
||||
newInstrs.push(...newInstr, instr);
|
||||
} else {
|
||||
newInstrs.push(instr);
|
||||
}
|
||||
}
|
||||
block.instructions = newInstrs;
|
||||
}
|
||||
}
|
||||
if (hasRewrite) {
|
||||
// Renumber instructions and fix scope ranges
|
||||
markInstructionIds(fn.body);
|
||||
fixScopeAndIdentifierRanges(fn.body);
|
||||
}
|
||||
}
|
||||
|
||||
function writeDependencyToInstructions(
|
||||
dep: ReactiveScopeDependency,
|
||||
reactive: boolean,
|
||||
env: Environment,
|
||||
loc: SourceLocation,
|
||||
): {place: Place; instructions: Array<Instruction>} {
|
||||
const instructions: Array<Instruction> = [];
|
||||
let currValue = createTemporaryPlace(env, GeneratedSource);
|
||||
currValue.reactive = reactive;
|
||||
instructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...currValue, effect: Effect.Mutate},
|
||||
value: {
|
||||
kind: 'LoadLocal',
|
||||
place: {
|
||||
kind: 'Identifier',
|
||||
identifier: dep.identifier,
|
||||
effect: Effect.Capture,
|
||||
reactive,
|
||||
loc: loc,
|
||||
},
|
||||
loc: loc,
|
||||
},
|
||||
});
|
||||
for (const path of dep.path) {
|
||||
if (path.optional) {
|
||||
/**
|
||||
* TODO: instead of truncating optional paths, reuse
|
||||
* instructions from hoisted dependencies block(s)
|
||||
*/
|
||||
break;
|
||||
}
|
||||
if (path.property === 'current') {
|
||||
/*
|
||||
* Prune ref.current accesses. This may over-capture for non-ref values with
|
||||
* a current property, but that's fine.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
const nextValue = createTemporaryPlace(env, GeneratedSource);
|
||||
nextValue.reactive = reactive;
|
||||
instructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...nextValue, effect: Effect.Mutate},
|
||||
value: {
|
||||
kind: 'PropertyLoad',
|
||||
object: {...currValue, effect: Effect.Capture},
|
||||
property: path.property,
|
||||
loc: loc,
|
||||
},
|
||||
});
|
||||
currValue = nextValue;
|
||||
}
|
||||
currValue.effect = Effect.Freeze;
|
||||
return {place: currValue, instructions};
|
||||
}
|
||||
|
||||
function inferReactiveIdentifiers(fn: HIRFunction): Set<IdentifierId> {
|
||||
const reactiveIds: Set<IdentifierId> = new Set();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
/**
|
||||
* No need to traverse into nested functions as
|
||||
* 1. their effects are recorded in `LoweredFunction.dependencies`
|
||||
* 2. we don't mark `reactive` in these anyways
|
||||
*/
|
||||
for (const place of eachInstructionOperand(instr)) {
|
||||
if (place.reactive) {
|
||||
reactiveIds.add(place.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
if (place.reactive) {
|
||||
reactiveIds.add(place.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reactiveIds;
|
||||
}
|
||||
@@ -11,4 +11,3 @@ export {inferMutableRanges} from './InferMutableRanges';
|
||||
export {inferReactivePlaces} from './InferReactivePlaces';
|
||||
export {default as inferReferenceEffects} from './InferReferenceEffects';
|
||||
export {inlineImmediatelyInvokedFunctionExpressions} from './InlineImmediatelyInvokedFunctionExpressions';
|
||||
export {inferEffectDependencies} from './InferEffectDependencies';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ArrayPattern,
|
||||
BlockId,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
@@ -57,14 +58,6 @@ export function deadCodeElimination(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant propagation and DCE may have deleted or rewritten instructions
|
||||
* that reference context variables.
|
||||
*/
|
||||
retainWhere(fn.context, contextVar =>
|
||||
state.isIdOrNameUsed(contextVar.identifier),
|
||||
);
|
||||
}
|
||||
|
||||
class State {
|
||||
@@ -183,28 +176,29 @@ function rewriteInstruction(instr: Instruction, state: State): void {
|
||||
switch (instr.value.lvalue.pattern.kind) {
|
||||
case 'ArrayPattern': {
|
||||
/*
|
||||
* For arrays, we can prune items prior to the end by replacing
|
||||
* them with a hole. Items at the end can simply be dropped.
|
||||
* For arrays, we can only eliminate unused items from the end of the array,
|
||||
* so we iterate from the end and break once we find a used item. Note that
|
||||
* we already know at least one item is used, from the pruneableValue check.
|
||||
*/
|
||||
let lastEntryIndex = 0;
|
||||
const items = instr.value.lvalue.pattern.items;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
let nextItems: ArrayPattern['items'] | null = null;
|
||||
const originalItems = instr.value.lvalue.pattern.items;
|
||||
for (let i = originalItems.length - 1; i >= 0; i--) {
|
||||
const item = originalItems[i];
|
||||
if (item.kind === 'Identifier') {
|
||||
if (!state.isIdOrNameUsed(item.identifier)) {
|
||||
items[i] = {kind: 'Hole'};
|
||||
} else {
|
||||
lastEntryIndex = i;
|
||||
if (state.isIdOrNameUsed(item.identifier)) {
|
||||
nextItems = originalItems.slice(0, i + 1);
|
||||
break;
|
||||
}
|
||||
} else if (item.kind === 'Spread') {
|
||||
if (!state.isIdOrNameUsed(item.place.identifier)) {
|
||||
items[i] = {kind: 'Hole'};
|
||||
} else {
|
||||
lastEntryIndex = i;
|
||||
if (state.isIdOrNameUsed(item.place.identifier)) {
|
||||
nextItems = originalItems.slice(0, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
items.length = lastEntryIndex + 1;
|
||||
if (nextItems !== null) {
|
||||
instr.value.lvalue.pattern.items = nextItems;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ObjectPattern': {
|
||||
|
||||
@@ -546,56 +546,41 @@ function createPropsProperties(
|
||||
let refProperty: ObjectProperty | undefined;
|
||||
let keyProperty: ObjectProperty | undefined;
|
||||
const props: Array<ObjectProperty | SpreadPattern> = [];
|
||||
const jsxAttributesWithoutKey = propAttributes.filter(
|
||||
p => p.kind === 'JsxAttribute' && p.name !== 'key',
|
||||
const jsxAttributesWithoutKeyAndRef = propAttributes.filter(
|
||||
p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref',
|
||||
);
|
||||
const jsxSpreadAttributes = propAttributes.filter(
|
||||
p => p.kind === 'JsxSpreadAttribute',
|
||||
);
|
||||
const spreadPropsOnly =
|
||||
jsxAttributesWithoutKey.length === 0 && jsxSpreadAttributes.length === 1;
|
||||
jsxAttributesWithoutKeyAndRef.length === 0 &&
|
||||
jsxSpreadAttributes.length === 1;
|
||||
|
||||
propAttributes.forEach(prop => {
|
||||
switch (prop.kind) {
|
||||
case 'JsxAttribute': {
|
||||
switch (prop.name) {
|
||||
case 'key': {
|
||||
keyProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'key', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'ref': {
|
||||
/**
|
||||
* In the current JSX implementation, ref is both
|
||||
* a property on the element and a property on props.
|
||||
*/
|
||||
refProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'ref', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
const refPropProperty: ObjectProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'ref', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
props.push(refPropProperty);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const attributeProperty: ObjectProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: prop.name, kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
props.push(attributeProperty);
|
||||
}
|
||||
if (prop.name === 'ref') {
|
||||
refProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'ref', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
} else if (prop.name === 'key') {
|
||||
keyProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'key', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
} else {
|
||||
const attributeProperty: ObjectProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: prop.name, kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...prop.place},
|
||||
};
|
||||
props.push(attributeProperty);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {HIRFunction, isPropsType} from '../HIR';
|
||||
|
||||
/**
|
||||
* Converts method calls into regular calls where the receiver is the props object:
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* // INPUT
|
||||
* props.foo();
|
||||
*
|
||||
* // OUTPUT
|
||||
* const t0 = props.foo;
|
||||
* t0();
|
||||
* ```
|
||||
*
|
||||
* Counter example:
|
||||
*
|
||||
* Here the receiver is `props.foo`, not the props object, so we don't rewrite it:
|
||||
*
|
||||
* // INPUT
|
||||
* props.foo.bar();
|
||||
*
|
||||
* // OUTPUT
|
||||
* props.foo.bar();
|
||||
* ```
|
||||
*/
|
||||
export function optimizePropsMethodCalls(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (let i = 0; i < block.instructions.length; i++) {
|
||||
const instr = block.instructions[i]!;
|
||||
if (
|
||||
instr.value.kind === 'MethodCall' &&
|
||||
isPropsType(instr.value.receiver.identifier)
|
||||
) {
|
||||
instr.value = {
|
||||
kind: 'CallExpression',
|
||||
callee: instr.value.property,
|
||||
args: instr.value.args,
|
||||
loc: instr.value.loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,11 +103,6 @@ export type CodegenFunction = {
|
||||
* This is true if the compiler has the lowered useContext calls.
|
||||
*/
|
||||
hasLoweredContextAccess: boolean;
|
||||
|
||||
/**
|
||||
* This is true if the compiler has compiled a fire to a useFire call
|
||||
*/
|
||||
hasFireRewrite: boolean;
|
||||
};
|
||||
|
||||
export function codegenFunction(
|
||||
@@ -360,7 +355,6 @@ function codegenReactiveFunction(
|
||||
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
|
||||
outlined: [],
|
||||
hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
|
||||
hasFireRewrite: fn.env.hasFireRewrite,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1360,6 +1354,20 @@ function codegenForInit(
|
||||
init: ReactiveValue,
|
||||
): t.Expression | t.VariableDeclaration | null {
|
||||
if (init.kind === 'SequenceExpression') {
|
||||
for (const instr of init.instructions) {
|
||||
if (instr.value.kind === 'DeclareContext') {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support for loops where the index variable is a context variable`,
|
||||
loc: instr.loc,
|
||||
description:
|
||||
instr.value.lvalue.place.identifier.name != null
|
||||
? `\`${instr.value.lvalue.place.identifier.name.value}\` is a context variable`
|
||||
: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const body = codegenBlock(
|
||||
cx,
|
||||
init.instructions.map(instruction => ({
|
||||
@@ -1370,33 +1378,20 @@ function codegenForInit(
|
||||
const declarators: Array<t.VariableDeclarator> = [];
|
||||
let kind: 'let' | 'const' = 'const';
|
||||
body.forEach(instr => {
|
||||
let top: undefined | t.VariableDeclarator = undefined;
|
||||
if (
|
||||
instr.type === 'ExpressionStatement' &&
|
||||
instr.expression.type === 'AssignmentExpression' &&
|
||||
instr.expression.operator === '=' &&
|
||||
instr.expression.left.type === 'Identifier' &&
|
||||
(top = declarators.at(-1))?.id.type === 'Identifier' &&
|
||||
top?.id.name === instr.expression.left.name &&
|
||||
top?.init == null
|
||||
) {
|
||||
top.init = instr.expression.right;
|
||||
} else {
|
||||
CompilerError.invariant(
|
||||
instr.type === 'VariableDeclaration' &&
|
||||
(instr.kind === 'let' || instr.kind === 'const'),
|
||||
{
|
||||
reason: 'Expected a variable declaration',
|
||||
loc: init.loc,
|
||||
description: `Got ${instr.type}`,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
if (instr.kind === 'let') {
|
||||
kind = 'let';
|
||||
}
|
||||
declarators.push(...instr.declarations);
|
||||
CompilerError.invariant(
|
||||
instr.type === 'VariableDeclaration' &&
|
||||
(instr.kind === 'let' || instr.kind === 'const'),
|
||||
{
|
||||
reason: 'Expected a variable declaration',
|
||||
loc: init.loc,
|
||||
description: `Got ${instr.type}`,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
if (instr.kind === 'let') {
|
||||
kind = 'let';
|
||||
}
|
||||
declarators.push(...instr.declarations);
|
||||
});
|
||||
CompilerError.invariant(declarators.length > 0, {
|
||||
reason: 'Expected a variable declaration',
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
PrunedReactiveScopeBlock,
|
||||
ReactiveFunction,
|
||||
isPrimitiveType,
|
||||
isUseRefType,
|
||||
Identifier,
|
||||
} from '../HIR/HIR';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
@@ -52,21 +50,13 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
|
||||
this.traversePrunedScope(scopeBlock, state);
|
||||
|
||||
for (const [id, decl] of scopeBlock.scope.declarations) {
|
||||
if (
|
||||
!isPrimitiveType(decl.identifier) &&
|
||||
!isStableRefType(decl.identifier, state)
|
||||
) {
|
||||
if (!isPrimitiveType(decl.identifier)) {
|
||||
state.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function isStableRefType(
|
||||
identifier: Identifier,
|
||||
reactiveIdentifiers: Set<IdentifierId>,
|
||||
): boolean {
|
||||
return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a set of identifiers which are reactive, using the analysis previously performed
|
||||
* in `InferReactivePlaces`.
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
eachPatternOperand,
|
||||
} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
@@ -155,11 +156,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
|
||||
scope.range.end > maxInstruction + 1
|
||||
) {
|
||||
// Make it easier to debug why the error occurred
|
||||
fn.env.logger?.debugLogIRs?.({
|
||||
kind: 'hir',
|
||||
name: 'InferReactiveScopeVariables (invalid scope)',
|
||||
value: fn,
|
||||
});
|
||||
logHIRFunction('InferReactiveScopeVariables (invalid scope)', fn);
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Invalid mutable range for scope`,
|
||||
loc: GeneratedSource,
|
||||
|
||||
@@ -1,760 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetailOptions,
|
||||
ErrorSeverity,
|
||||
SourceLocation,
|
||||
} from '..';
|
||||
import {
|
||||
ArrayExpression,
|
||||
CallExpression,
|
||||
Effect,
|
||||
Environment,
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
isUseEffectHookType,
|
||||
LoadLocal,
|
||||
makeInstructionId,
|
||||
Place,
|
||||
promoteTemporary,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
|
||||
import {eachInstructionOperand} from '../HIR/visitors';
|
||||
import {printSourceLocationLine} from '../HIR/PrintHIR';
|
||||
|
||||
/*
|
||||
* TODO(jmbrown):
|
||||
* - traverse object methods
|
||||
* - method calls
|
||||
* - React.useEffect calls
|
||||
*/
|
||||
|
||||
const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`';
|
||||
|
||||
export function transformFire(fn: HIRFunction): void {
|
||||
const context = new Context(fn.env);
|
||||
replaceFireFunctions(fn, context);
|
||||
if (!context.hasErrors()) {
|
||||
ensureNoMoreFireUses(fn, context);
|
||||
}
|
||||
context.throwIfErrorsFound();
|
||||
}
|
||||
|
||||
function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
let hasRewrite = false;
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
|
||||
const deleteInstrs = new Set<InstructionId>();
|
||||
for (const instr of block.instructions) {
|
||||
const {value, lvalue} = instr;
|
||||
if (
|
||||
value.kind === 'CallExpression' &&
|
||||
isUseEffectHookType(value.callee.identifier) &&
|
||||
value.args.length > 0 &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const lambda = context.getFunctionExpression(
|
||||
value.args[0].identifier.id,
|
||||
);
|
||||
if (lambda != null) {
|
||||
const capturedCallees =
|
||||
visitFunctionExpressionAndPropagateFireDependencies(
|
||||
lambda,
|
||||
context,
|
||||
true,
|
||||
);
|
||||
|
||||
// Add useFire calls for all fire calls in found in the lambda
|
||||
const newInstrs = [];
|
||||
for (const [
|
||||
fireCalleePlace,
|
||||
fireCalleeInfo,
|
||||
] of capturedCallees.entries()) {
|
||||
if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
|
||||
context.addCalleeWithInsertedFire(fireCalleePlace);
|
||||
const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
|
||||
const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
|
||||
fn.env,
|
||||
fireCalleeInfo.capturedCalleeIdentifier,
|
||||
);
|
||||
const callUseFireInstr = makeCallUseFireInstruction(
|
||||
fn.env,
|
||||
loadUseFireInstr.lvalue,
|
||||
loadFireCalleeInstr.lvalue,
|
||||
);
|
||||
const storeUseFireInstr = makeStoreUseFireInstruction(
|
||||
fn.env,
|
||||
callUseFireInstr.lvalue,
|
||||
fireCalleeInfo.fireFunctionBinding,
|
||||
);
|
||||
newInstrs.push(
|
||||
loadUseFireInstr,
|
||||
loadFireCalleeInstr,
|
||||
callUseFireInstr,
|
||||
storeUseFireInstr,
|
||||
);
|
||||
|
||||
// We insert all of these instructions before the useEffect is loaded
|
||||
const loadUseEffectInstrId = context.getLoadGlobalInstrId(
|
||||
value.callee.identifier.id,
|
||||
);
|
||||
if (loadUseEffectInstrId == null) {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: '[InsertFire] No LoadGlobal found for useEffect call',
|
||||
suggestions: null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
rewriteInstrs.set(loadUseEffectInstrId, newInstrs);
|
||||
}
|
||||
}
|
||||
ensureNoRemainingCalleeCaptures(
|
||||
lambda.loweredFunc.func,
|
||||
context,
|
||||
capturedCallees,
|
||||
);
|
||||
|
||||
if (
|
||||
value.args.length > 1 &&
|
||||
value.args[1] != null &&
|
||||
value.args[1].kind === 'Identifier'
|
||||
) {
|
||||
const depArray = value.args[1];
|
||||
const depArrayExpression = context.getArrayExpression(
|
||||
depArray.identifier.id,
|
||||
);
|
||||
if (depArrayExpression != null) {
|
||||
for (const dependency of depArrayExpression.elements) {
|
||||
if (dependency.kind === 'Identifier') {
|
||||
const loadOfDependency = context.getLoadLocalInstr(
|
||||
dependency.identifier.id,
|
||||
);
|
||||
if (loadOfDependency != null) {
|
||||
const replacedDepArrayItem = capturedCallees.get(
|
||||
loadOfDependency.place.identifier.id,
|
||||
);
|
||||
if (replacedDepArrayItem != null) {
|
||||
loadOfDependency.place =
|
||||
replacedDepArrayItem.fireFunctionBinding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.pushError({
|
||||
loc: value.args[1].loc,
|
||||
description:
|
||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
} else if (value.args.length > 1 && value.args[1].kind === 'Spread') {
|
||||
context.pushError({
|
||||
loc: value.args[1].place.loc,
|
||||
description:
|
||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
value.kind === 'CallExpression' &&
|
||||
value.callee.identifier.type.kind === 'Function' &&
|
||||
value.callee.identifier.type.shapeId === BuiltInFireId &&
|
||||
context.inUseEffectLambda()
|
||||
) {
|
||||
/*
|
||||
* We found a fire(callExpr()) call. We remove the `fire()` call and replace the callExpr()
|
||||
* with a freshly generated fire function binding. We'll insert the useFire call before the
|
||||
* useEffect call, which happens in the CallExpression (useEffect) case above.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We only allow fire to be called with a CallExpression: `fire(f())`
|
||||
* TODO: add support for method calls: `fire(this.method())`
|
||||
*/
|
||||
if (value.args.length === 1 && value.args[0].kind === 'Identifier') {
|
||||
const callExpr = context.getCallExpression(
|
||||
value.args[0].identifier.id,
|
||||
);
|
||||
|
||||
if (callExpr != null) {
|
||||
const calleeId = callExpr.callee.identifier.id;
|
||||
const loadLocal = context.getLoadLocalInstr(calleeId);
|
||||
if (loadLocal == null) {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason:
|
||||
'[InsertFire] No loadLocal found for fire call argument',
|
||||
suggestions: null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const fireFunctionBinding =
|
||||
context.getOrGenerateFireFunctionBinding(
|
||||
loadLocal.place,
|
||||
value.loc,
|
||||
);
|
||||
|
||||
loadLocal.place = {...fireFunctionBinding};
|
||||
|
||||
// Delete the fire call expression
|
||||
deleteInstrs.add(instr.id);
|
||||
} else {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description:
|
||||
'`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let description: string =
|
||||
'fire() can only take in a single call expression as an argument';
|
||||
if (value.args.length === 0) {
|
||||
description += ' but received none';
|
||||
} else if (value.args.length > 1) {
|
||||
description += ' but received multiple arguments';
|
||||
} else if (value.args[0].kind === 'Spread') {
|
||||
description += ' but received a spread argument';
|
||||
}
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
} else if (value.kind === 'CallExpression') {
|
||||
context.addCallExpression(lvalue.identifier.id, value);
|
||||
} else if (
|
||||
value.kind === 'FunctionExpression' &&
|
||||
context.inUseEffectLambda()
|
||||
) {
|
||||
visitFunctionExpressionAndPropagateFireDependencies(
|
||||
value,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else if (value.kind === 'FunctionExpression') {
|
||||
context.addFunctionExpression(lvalue.identifier.id, value);
|
||||
} else if (value.kind === 'LoadLocal') {
|
||||
context.addLoadLocalInstr(lvalue.identifier.id, value);
|
||||
} else if (
|
||||
value.kind === 'LoadGlobal' &&
|
||||
value.binding.kind === 'ImportSpecifier' &&
|
||||
value.binding.module === 'react' &&
|
||||
value.binding.imported === 'fire' &&
|
||||
context.inUseEffectLambda()
|
||||
) {
|
||||
deleteInstrs.add(instr.id);
|
||||
} else if (value.kind === 'LoadGlobal') {
|
||||
context.addLoadGlobalInstrId(lvalue.identifier.id, instr.id);
|
||||
} else if (value.kind === 'ArrayExpression') {
|
||||
context.addArrayExpression(lvalue.identifier.id, value);
|
||||
}
|
||||
}
|
||||
block.instructions = rewriteInstructions(rewriteInstrs, block.instructions);
|
||||
block.instructions = deleteInstructions(deleteInstrs, block.instructions);
|
||||
|
||||
if (rewriteInstrs.size > 0 || deleteInstrs.size > 0) {
|
||||
hasRewrite = true;
|
||||
fn.env.hasFireRewrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRewrite) {
|
||||
markInstructionIds(fn.body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a function expression to find fire calls fire(foo()) and replaces them with
|
||||
* fireFoo().
|
||||
*
|
||||
* When a function captures a fire call we need to update its context to reflect the newly created
|
||||
* fire function bindings and update the LoadLocals referenced by the function's dependencies.
|
||||
*
|
||||
* @param isUseEffect is necessary so we can keep track of when we should additionally insert
|
||||
* useFire hooks calls.
|
||||
*/
|
||||
function visitFunctionExpressionAndPropagateFireDependencies(
|
||||
fnExpr: FunctionExpression,
|
||||
context: Context,
|
||||
enteringUseEffect: boolean,
|
||||
): FireCalleesToFireFunctionBinding {
|
||||
let withScope = enteringUseEffect
|
||||
? context.withUseEffectLambdaScope.bind(context)
|
||||
: context.withFunctionScope.bind(context);
|
||||
|
||||
const calleesCapturedByFnExpression = withScope(() =>
|
||||
replaceFireFunctions(fnExpr.loweredFunc.func, context),
|
||||
);
|
||||
|
||||
/*
|
||||
* Make a mapping from each dependency to the corresponding LoadLocal for it so that
|
||||
* we can replace the loaded place with the generated fire function binding
|
||||
*/
|
||||
const loadLocalsToDepLoads = new Map<IdentifierId, LoadLocal>();
|
||||
for (const dep of fnExpr.loweredFunc.dependencies) {
|
||||
const loadLocal = context.getLoadLocalInstr(dep.identifier.id);
|
||||
if (loadLocal != null) {
|
||||
loadLocalsToDepLoads.set(loadLocal.place.identifier.id, loadLocal);
|
||||
}
|
||||
}
|
||||
|
||||
const replacedCallees = new Map<IdentifierId, Place>();
|
||||
for (const [
|
||||
calleeIdentifierId,
|
||||
loadedFireFunctionBindingPlace,
|
||||
] of calleesCapturedByFnExpression.entries()) {
|
||||
/*
|
||||
* Given the ids of captured fire callees, look at the deps for loads of those identifiers
|
||||
* and replace them with the new fire function binding
|
||||
*/
|
||||
const loadLocal = loadLocalsToDepLoads.get(calleeIdentifierId);
|
||||
if (loadLocal == null) {
|
||||
context.pushError({
|
||||
loc: fnExpr.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason:
|
||||
'[InsertFire] No loadLocal found for fire call argument for lambda',
|
||||
suggestions: null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldPlaceId = loadLocal.place.identifier.id;
|
||||
loadLocal.place = {
|
||||
...loadedFireFunctionBindingPlace.fireFunctionBinding,
|
||||
};
|
||||
|
||||
replacedCallees.set(
|
||||
oldPlaceId,
|
||||
loadedFireFunctionBindingPlace.fireFunctionBinding,
|
||||
);
|
||||
}
|
||||
|
||||
// For each replaced callee, update the context of the function expression to track it
|
||||
for (
|
||||
let contextIdx = 0;
|
||||
contextIdx < fnExpr.loweredFunc.func.context.length;
|
||||
contextIdx++
|
||||
) {
|
||||
const contextItem = fnExpr.loweredFunc.func.context[contextIdx];
|
||||
const replacedCallee = replacedCallees.get(contextItem.identifier.id);
|
||||
if (replacedCallee != null) {
|
||||
fnExpr.loweredFunc.func.context[contextIdx] = replacedCallee;
|
||||
}
|
||||
}
|
||||
|
||||
context.mergeCalleesFromInnerScope(calleesCapturedByFnExpression);
|
||||
|
||||
return calleesCapturedByFnExpression;
|
||||
}
|
||||
|
||||
/*
|
||||
* eachInstructionOperand is not sufficient for our cases because:
|
||||
* 1. fire is a global, which will not appear
|
||||
* 2. The HIR may be malformed, so can't rely on function deps and must
|
||||
* traverse the whole function.
|
||||
*/
|
||||
function* eachReachablePlace(fn: HIRFunction): Iterable<Place> {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
yield* eachReachablePlace(instr.value.loweredFunc.func);
|
||||
} else {
|
||||
yield* eachInstructionOperand(instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureNoRemainingCalleeCaptures(
|
||||
fn: HIRFunction,
|
||||
context: Context,
|
||||
capturedCallees: FireCalleesToFireFunctionBinding,
|
||||
): void {
|
||||
for (const place of eachReachablePlace(fn)) {
|
||||
const calleeInfo = capturedCallees.get(place.identifier.id);
|
||||
if (calleeInfo != null) {
|
||||
const calleeName =
|
||||
calleeInfo.capturedCalleeIdentifier.name?.kind === 'named'
|
||||
? calleeInfo.capturedCalleeIdentifier.name.value
|
||||
: '<unknown>';
|
||||
context.pushError({
|
||||
loc: place.loc,
|
||||
description: `All uses of ${calleeName} must be either used with a fire() call in \
|
||||
this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \
|
||||
${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
|
||||
for (const place of eachReachablePlace(fn)) {
|
||||
if (
|
||||
place.identifier.type.kind === 'Function' &&
|
||||
place.identifier.type.shapeId === BuiltInFireId
|
||||
) {
|
||||
context.pushError({
|
||||
loc: place.identifier.loc,
|
||||
description: 'Cannot use `fire` outside of a useEffect function',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeLoadUseFireInstruction(env: Environment): Instruction {
|
||||
const useFirePlace = createTemporaryPlace(env, GeneratedSource);
|
||||
useFirePlace.effect = Effect.Read;
|
||||
useFirePlace.identifier.type = DefaultNonmutatingHook;
|
||||
const instrValue: InstructionValue = {
|
||||
kind: 'LoadGlobal',
|
||||
binding: {
|
||||
kind: 'ImportSpecifier',
|
||||
name: 'useFire',
|
||||
module: 'react',
|
||||
imported: 'useFire',
|
||||
},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: instrValue,
|
||||
lvalue: {...useFirePlace},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function makeLoadFireCalleeInstruction(
|
||||
env: Environment,
|
||||
fireCalleeIdentifier: Identifier,
|
||||
): Instruction {
|
||||
const loadedFireCallee = createTemporaryPlace(env, GeneratedSource);
|
||||
const fireCallee: Place = {
|
||||
kind: 'Identifier',
|
||||
identifier: fireCalleeIdentifier,
|
||||
reactive: false,
|
||||
effect: Effect.Unknown,
|
||||
loc: fireCalleeIdentifier.loc,
|
||||
};
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: {
|
||||
kind: 'LoadLocal',
|
||||
loc: GeneratedSource,
|
||||
place: {...fireCallee},
|
||||
},
|
||||
lvalue: {...loadedFireCallee},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function makeCallUseFireInstruction(
|
||||
env: Environment,
|
||||
useFirePlace: Place,
|
||||
argPlace: Place,
|
||||
): Instruction {
|
||||
const useFireCallResultPlace = createTemporaryPlace(env, GeneratedSource);
|
||||
useFireCallResultPlace.effect = Effect.Read;
|
||||
|
||||
const useFireCall: CallExpression = {
|
||||
kind: 'CallExpression',
|
||||
callee: {...useFirePlace},
|
||||
args: [argPlace],
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: useFireCall,
|
||||
lvalue: {...useFireCallResultPlace},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function makeStoreUseFireInstruction(
|
||||
env: Environment,
|
||||
useFireCallResultPlace: Place,
|
||||
fireFunctionBindingPlace: Place,
|
||||
): Instruction {
|
||||
promoteTemporary(fireFunctionBindingPlace.identifier);
|
||||
|
||||
const fireFunctionBindingLValuePlace = createTemporaryPlace(
|
||||
env,
|
||||
GeneratedSource,
|
||||
);
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: {
|
||||
kind: 'StoreLocal',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Const,
|
||||
place: {...fireFunctionBindingPlace},
|
||||
},
|
||||
value: {...useFireCallResultPlace},
|
||||
type: null,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
lvalue: fireFunctionBindingLValuePlace,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
type FireCalleesToFireFunctionBinding = Map<
|
||||
IdentifierId,
|
||||
{
|
||||
fireFunctionBinding: Place;
|
||||
capturedCalleeIdentifier: Identifier;
|
||||
fireLoc: SourceLocation;
|
||||
}
|
||||
>;
|
||||
|
||||
class Context {
|
||||
#env: Environment;
|
||||
|
||||
#errors: CompilerError = new CompilerError();
|
||||
|
||||
/*
|
||||
* Used to look up the call expression passed to a `fire(callExpr())`. Gives back
|
||||
* the `callExpr()`.
|
||||
*/
|
||||
#callExpressions = new Map<IdentifierId, CallExpression>();
|
||||
|
||||
/*
|
||||
* We keep track of function expressions so that we can traverse them when
|
||||
* we encounter a lambda passed to a useEffect call
|
||||
*/
|
||||
#functionExpressions = new Map<IdentifierId, FunctionExpression>();
|
||||
|
||||
/*
|
||||
* Mapping from lvalue ids to the LoadLocal for it. Allows us to replace dependency LoadLocals.
|
||||
*/
|
||||
#loadLocals = new Map<IdentifierId, LoadLocal>();
|
||||
|
||||
/*
|
||||
* Maps all of the fire callees found in a component/hook to the generated fire function places
|
||||
* we create for them. Allows us to reuse already-inserted useFire results
|
||||
*/
|
||||
#fireCalleesToFireFunctions: Map<IdentifierId, Place> = new Map();
|
||||
|
||||
/*
|
||||
* The callees for which we have already created fire bindings. Used to skip inserting a new
|
||||
* useFire call for a fire callee if one has already been created.
|
||||
*/
|
||||
#calleesWithInsertedFire = new Set<IdentifierId>();
|
||||
|
||||
/*
|
||||
* A mapping from fire callees to the created fire function bindings that are reachable from this
|
||||
* scope.
|
||||
*
|
||||
* We additionally keep track of the captured callee identifier so that we can properly reference
|
||||
* it in the place where we LoadLocal the callee as an argument to useFire.
|
||||
*/
|
||||
#capturedCalleeIdentifierIds: FireCalleesToFireFunctionBinding = new Map();
|
||||
|
||||
/*
|
||||
* We only transform fire calls if we're syntactically within a useEffect lambda (for now)
|
||||
*/
|
||||
#inUseEffectLambda = false;
|
||||
|
||||
/*
|
||||
* Mapping from useEffect callee identifier ids to the instruction id of the
|
||||
* load global instruction for the useEffect call. We use this to insert the
|
||||
* useFire calls before the useEffect call
|
||||
*/
|
||||
#loadGlobalInstructionIds = new Map<IdentifierId, InstructionId>();
|
||||
|
||||
constructor(env: Environment) {
|
||||
this.#env = env;
|
||||
}
|
||||
|
||||
/*
|
||||
* We keep track of array expressions so we can rewrite dependency arrays passed to useEffect
|
||||
* to use the fire functions
|
||||
*/
|
||||
#arrayExpressions = new Map<IdentifierId, ArrayExpression>();
|
||||
|
||||
pushError(error: CompilerErrorDetailOptions): void {
|
||||
this.#errors.push(error);
|
||||
}
|
||||
|
||||
withFunctionScope(fn: () => void): FireCalleesToFireFunctionBinding {
|
||||
fn();
|
||||
return this.#capturedCalleeIdentifierIds;
|
||||
}
|
||||
|
||||
withUseEffectLambdaScope(fn: () => void): FireCalleesToFireFunctionBinding {
|
||||
const capturedCalleeIdentifierIds = this.#capturedCalleeIdentifierIds;
|
||||
const inUseEffectLambda = this.#inUseEffectLambda;
|
||||
|
||||
this.#capturedCalleeIdentifierIds = new Map();
|
||||
this.#inUseEffectLambda = true;
|
||||
|
||||
const resultCapturedCalleeIdentifierIds = this.withFunctionScope(fn);
|
||||
|
||||
this.#capturedCalleeIdentifierIds = capturedCalleeIdentifierIds;
|
||||
this.#inUseEffectLambda = inUseEffectLambda;
|
||||
|
||||
return resultCapturedCalleeIdentifierIds;
|
||||
}
|
||||
|
||||
addCallExpression(id: IdentifierId, callExpr: CallExpression): void {
|
||||
this.#callExpressions.set(id, callExpr);
|
||||
}
|
||||
|
||||
getCallExpression(id: IdentifierId): CallExpression | undefined {
|
||||
return this.#callExpressions.get(id);
|
||||
}
|
||||
|
||||
addLoadLocalInstr(id: IdentifierId, loadLocal: LoadLocal): void {
|
||||
this.#loadLocals.set(id, loadLocal);
|
||||
}
|
||||
|
||||
getLoadLocalInstr(id: IdentifierId): LoadLocal | undefined {
|
||||
return this.#loadLocals.get(id);
|
||||
}
|
||||
getOrGenerateFireFunctionBinding(
|
||||
callee: Place,
|
||||
fireLoc: SourceLocation,
|
||||
): Place {
|
||||
const fireFunctionBinding = getOrInsertWith(
|
||||
this.#fireCalleesToFireFunctions,
|
||||
callee.identifier.id,
|
||||
() => createTemporaryPlace(this.#env, GeneratedSource),
|
||||
);
|
||||
|
||||
this.#capturedCalleeIdentifierIds.set(callee.identifier.id, {
|
||||
fireFunctionBinding,
|
||||
capturedCalleeIdentifier: callee.identifier,
|
||||
fireLoc,
|
||||
});
|
||||
|
||||
return fireFunctionBinding;
|
||||
}
|
||||
|
||||
mergeCalleesFromInnerScope(
|
||||
innerCallees: FireCalleesToFireFunctionBinding,
|
||||
): void {
|
||||
for (const [id, calleeInfo] of innerCallees.entries()) {
|
||||
this.#capturedCalleeIdentifierIds.set(id, calleeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
addCalleeWithInsertedFire(id: IdentifierId): void {
|
||||
this.#calleesWithInsertedFire.add(id);
|
||||
}
|
||||
|
||||
hasCalleeWithInsertedFire(id: IdentifierId): boolean {
|
||||
return this.#calleesWithInsertedFire.has(id);
|
||||
}
|
||||
|
||||
inUseEffectLambda(): boolean {
|
||||
return this.#inUseEffectLambda;
|
||||
}
|
||||
|
||||
addFunctionExpression(id: IdentifierId, fn: FunctionExpression): void {
|
||||
this.#functionExpressions.set(id, fn);
|
||||
}
|
||||
|
||||
getFunctionExpression(id: IdentifierId): FunctionExpression | undefined {
|
||||
return this.#functionExpressions.get(id);
|
||||
}
|
||||
|
||||
addLoadGlobalInstrId(id: IdentifierId, instrId: InstructionId): void {
|
||||
this.#loadGlobalInstructionIds.set(id, instrId);
|
||||
}
|
||||
|
||||
getLoadGlobalInstrId(id: IdentifierId): InstructionId | undefined {
|
||||
return this.#loadGlobalInstructionIds.get(id);
|
||||
}
|
||||
|
||||
addArrayExpression(id: IdentifierId, array: ArrayExpression): void {
|
||||
this.#arrayExpressions.set(id, array);
|
||||
}
|
||||
|
||||
getArrayExpression(id: IdentifierId): ArrayExpression | undefined {
|
||||
return this.#arrayExpressions.get(id);
|
||||
}
|
||||
|
||||
hasErrors(): boolean {
|
||||
return this.#errors.hasErrors();
|
||||
}
|
||||
|
||||
throwIfErrorsFound(): void {
|
||||
if (this.hasErrors()) throw this.#errors;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteInstructions(
|
||||
deleteInstrs: Set<InstructionId>,
|
||||
instructions: Array<Instruction>,
|
||||
): Array<Instruction> {
|
||||
if (deleteInstrs.size > 0) {
|
||||
const newInstrs = instructions.filter(instr => !deleteInstrs.has(instr.id));
|
||||
return newInstrs;
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
function rewriteInstructions(
|
||||
rewriteInstrs: Map<InstructionId, Array<Instruction>>,
|
||||
instructions: Array<Instruction>,
|
||||
): Array<Instruction> {
|
||||
if (rewriteInstrs.size > 0) {
|
||||
const newInstrs = [];
|
||||
for (const instr of instructions) {
|
||||
const newInstrsAtId = rewriteInstrs.get(instr.id);
|
||||
if (newInstrsAtId != null) {
|
||||
newInstrs.push(...newInstrsAtId, instr);
|
||||
} else {
|
||||
newInstrs.push(instr);
|
||||
}
|
||||
}
|
||||
|
||||
return newInstrs;
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
export {transformFire} from './TransformFire';
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
import chalk from 'chalk';
|
||||
import {HIR, HIRFunction, ReactiveFunction} from '../HIR/HIR';
|
||||
import {printFunctionWithOutlined, printHIR} from '../HIR/PrintHIR';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {printReactiveFunctionWithOutlined} from '../ReactiveScopes/PrintReactiveFunction';
|
||||
|
||||
let ENABLED: boolean = false;
|
||||
|
||||
let lastLogged: string;
|
||||
|
||||
export function toggleLogging(enabled: boolean): void {
|
||||
ENABLED = enabled;
|
||||
}
|
||||
|
||||
export function logDebug(step: string, value: string): void {
|
||||
if (ENABLED) {
|
||||
process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`);
|
||||
}
|
||||
}
|
||||
|
||||
export function logHIR(step: string, ir: HIR): void {
|
||||
if (ENABLED) {
|
||||
const printed = printHIR(ir);
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logCodegenFunction(step: string, fn: CodegenFunction): void {
|
||||
if (ENABLED) {
|
||||
let printed: string | null = null;
|
||||
try {
|
||||
const node = t.functionDeclaration(
|
||||
fn.id,
|
||||
fn.params,
|
||||
fn.body,
|
||||
fn.generator,
|
||||
fn.async,
|
||||
);
|
||||
const ast = generate(node);
|
||||
printed = ast.code;
|
||||
} catch (e) {
|
||||
let errMsg: string;
|
||||
if (
|
||||
typeof e === 'object' &&
|
||||
e != null &&
|
||||
'message' in e &&
|
||||
typeof e.message === 'string'
|
||||
) {
|
||||
errMsg = e.message.toString();
|
||||
} else {
|
||||
errMsg = '[empty]';
|
||||
}
|
||||
console.log('Error formatting AST: ' + errMsg);
|
||||
}
|
||||
if (printed === null) {
|
||||
return;
|
||||
}
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logHIRFunction(step: string, fn: HIRFunction): void {
|
||||
if (ENABLED) {
|
||||
const printed = printFunctionWithOutlined(fn);
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logReactiveFunction(step: string, fn: ReactiveFunction): void {
|
||||
if (ENABLED) {
|
||||
const printed = printReactiveFunctionWithOutlined(fn);
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function log(fn: () => string): void {
|
||||
if (ENABLED) {
|
||||
const message = fn();
|
||||
process.stdout.write(message.trim() + '\n\n');
|
||||
}
|
||||
}
|
||||
@@ -305,14 +305,6 @@ function validateNoRefAccessInRenderImpl(
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'TypeCastExpression': {
|
||||
env.set(
|
||||
instr.lvalue.identifier.id,
|
||||
env.get(instr.value.value.identifier.id) ??
|
||||
refTypeOfType(instr.lvalue),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'LoadContext':
|
||||
case 'LoadLocal': {
|
||||
env.set(
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
|
||||
function useArrayOfRef() {
|
||||
const ref = useRef(null);
|
||||
const callback = value => {
|
||||
ref.current = value;
|
||||
};
|
||||
return [callback] as const;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
useArrayOfRef();
|
||||
return 'ok';
|
||||
},
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
|
||||
function useArrayOfRef() {
|
||||
const $ = _c(1);
|
||||
const ref = useRef(null);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const callback = (value) => {
|
||||
ref.current = value;
|
||||
};
|
||||
|
||||
t0 = [callback];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0 as const;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
useArrayOfRef();
|
||||
return "ok";
|
||||
},
|
||||
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "ok"
|
||||
@@ -1,17 +0,0 @@
|
||||
import {useRef} from 'react';
|
||||
|
||||
function useArrayOfRef() {
|
||||
const ref = useRef(null);
|
||||
const callback = value => {
|
||||
ref.current = value;
|
||||
};
|
||||
return [callback] as const;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
useArrayOfRef();
|
||||
return 'ok';
|
||||
},
|
||||
params: [{}],
|
||||
};
|
||||
@@ -28,7 +28,7 @@ function Component() {
|
||||
t0 = () => {
|
||||
"worklet";
|
||||
|
||||
setCount(_temp);
|
||||
setCount((count_0) => count_0 + 1);
|
||||
};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
@@ -45,9 +45,6 @@ function Component() {
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _temp(count_0) {
|
||||
return count_0 + 1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {makeArray, mutate} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug repro:
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
|
||||
*
|
||||
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
|
||||
* aliasing `y` via `[y]`, we make an opaque call.
|
||||
*
|
||||
* Note that the bug here is that we don't infer that `a = makeArray(y)`
|
||||
* potentially captures a context variable into a local variable. As a result,
|
||||
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
|
||||
* currently inferring that this lambda captures `y` (for a potential later
|
||||
* mutation) and simply reads `x`.
|
||||
*
|
||||
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
|
||||
* used when we analyze CallExpressions.
|
||||
*/
|
||||
function Component({foo, bar}: {foo: number; bar: number}) {
|
||||
let x = {foo};
|
||||
let y: {bar: number; x?: {foo: number}} = {bar};
|
||||
const f0 = function () {
|
||||
let a = makeArray(y); // a = [y]
|
||||
let b = x;
|
||||
// this writes y.x = x
|
||||
a[0].x = b;
|
||||
};
|
||||
f0();
|
||||
mutate(y.x);
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 3, bar: 4}],
|
||||
sequentialRenders: [
|
||||
{foo: 3, bar: 4},
|
||||
{foo: 3, bar: 5},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { makeArray, mutate } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Bug repro:
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
|
||||
*
|
||||
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
|
||||
* aliasing `y` via `[y]`, we make an opaque call.
|
||||
*
|
||||
* Note that the bug here is that we don't infer that `a = makeArray(y)`
|
||||
* potentially captures a context variable into a local variable. As a result,
|
||||
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
|
||||
* currently inferring that this lambda captures `y` (for a potential later
|
||||
* mutation) and simply reads `x`.
|
||||
*
|
||||
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
|
||||
* used when we analyze CallExpressions.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { foo, bar } = t0;
|
||||
let t1;
|
||||
if ($[0] !== foo) {
|
||||
t1 = { foo };
|
||||
$[0] = foo;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
let y;
|
||||
if ($[2] !== bar || $[3] !== x) {
|
||||
y = { bar };
|
||||
const f0 = function () {
|
||||
const a = makeArray(y);
|
||||
const b = x;
|
||||
|
||||
a[0].x = b;
|
||||
};
|
||||
|
||||
f0();
|
||||
mutate(y.x);
|
||||
$[2] = bar;
|
||||
$[3] = x;
|
||||
$[4] = y;
|
||||
} else {
|
||||
y = $[4];
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ foo: 3, bar: 4 }],
|
||||
sequentialRenders: [
|
||||
{ foo: 3, bar: 4 },
|
||||
{ foo: 3, bar: 5 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import {makeArray, mutate} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug repro:
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
|
||||
*
|
||||
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
|
||||
* aliasing `y` via `[y]`, we make an opaque call.
|
||||
*
|
||||
* Note that the bug here is that we don't infer that `a = makeArray(y)`
|
||||
* potentially captures a context variable into a local variable. As a result,
|
||||
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
|
||||
* currently inferring that this lambda captures `y` (for a potential later
|
||||
* mutation) and simply reads `x`.
|
||||
*
|
||||
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
|
||||
* used when we analyze CallExpressions.
|
||||
*/
|
||||
function Component({foo, bar}: {foo: number; bar: number}) {
|
||||
let x = {foo};
|
||||
let y: {bar: number; x?: {foo: number}} = {bar};
|
||||
const f0 = function () {
|
||||
let a = makeArray(y); // a = [y]
|
||||
let b = x;
|
||||
// this writes y.x = x
|
||||
a[0].x = b;
|
||||
};
|
||||
f0();
|
||||
mutate(y.x);
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 3, bar: 4}],
|
||||
sequentialRenders: [
|
||||
{foo: 3, bar: 4},
|
||||
{foo: 3, bar: 5},
|
||||
],
|
||||
};
|
||||
@@ -58,16 +58,18 @@ function Foo(t0) {
|
||||
bar = $[1];
|
||||
result = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== bar || $[4] !== result) {
|
||||
t1 = <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
|
||||
$[3] = bar;
|
||||
$[4] = result;
|
||||
$[5] = t1;
|
||||
|
||||
const t1 = bar;
|
||||
let t2;
|
||||
if ($[3] !== result || $[4] !== t1) {
|
||||
t2 = <Stringify result={result} fn={t1} shouldInvokeFns={true} />;
|
||||
$[3] = result;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
t2 = $[5];
|
||||
}
|
||||
return t1;
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug: we're currently filtering out `ref.current` dependencies in
|
||||
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
|
||||
* Instead, we should always take a dependency on ref values (the outer box) as
|
||||
* they may be reactive. Pruning should be done in
|
||||
* `pruneNonReactiveDependencies`
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
function Component({cond}) {
|
||||
const ref1 = useRef(1);
|
||||
const ref2 = useRef(2);
|
||||
const ref = cond ? ref1 : ref2;
|
||||
const cb = () => ref.current;
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [{cond: true}, {cond: false}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Bug: we're currently filtering out `ref.current` dependencies in
|
||||
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
|
||||
* Instead, we should always take a dependency on ref values (the outer box) as
|
||||
* they may be reactive. Pruning should be done in
|
||||
* `pruneNonReactiveDependencies`
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(1);
|
||||
const { cond } = t0;
|
||||
const ref1 = useRef(1);
|
||||
const ref2 = useRef(2);
|
||||
const ref = cond ? ref1 : ref2;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const cb = () => ref.current;
|
||||
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [{ cond: true }, { cond: false }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {useRef} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug: we're currently filtering out `ref.current` dependencies in
|
||||
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
|
||||
* Instead, we should always take a dependency on ref values (the outer box) as
|
||||
* they may be reactive. Pruning should be done in
|
||||
* `pruneNonReactiveDependencies`
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
function Component({cond}) {
|
||||
const ref1 = useRef(1);
|
||||
const ref2 = useRef(2);
|
||||
const ref = cond ? ref1 : ref2;
|
||||
const cb = () => ref.current;
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [{cond: true}, {cond: false}],
|
||||
};
|
||||
@@ -1,114 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro for bug in our type inference system. We currently propagate inferred
|
||||
* types through control flow / potential type guards. Note that this is
|
||||
* inconsistent with both Flow and Typescript.
|
||||
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
|
||||
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* [2]
|
||||
* [3]
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* [2]
|
||||
* [2,3]
|
||||
*/
|
||||
function useFoo({cond, value}: {cond: boolean; value: number}) {
|
||||
const x = {value: cond ? CONST_NUMBER0 : []};
|
||||
mutate(x);
|
||||
|
||||
const xValue = x.value;
|
||||
let result;
|
||||
if (typeof xValue === 'number') {
|
||||
result = xValue + 1; // (1) here we infer xValue is a primitive
|
||||
} else {
|
||||
result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [
|
||||
{cond: false, value: 2},
|
||||
{cond: false, value: 3},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush, CONST_NUMBER0, mutate } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Repro for bug in our type inference system. We currently propagate inferred
|
||||
* types through control flow / potential type guards. Note that this is
|
||||
* inconsistent with both Flow and Typescript.
|
||||
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
|
||||
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* [2]
|
||||
* [3]
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* [2]
|
||||
* [2,3]
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const $ = _c(5);
|
||||
const { cond, value } = t0;
|
||||
let x;
|
||||
if ($[0] !== cond) {
|
||||
x = { value: cond ? CONST_NUMBER0 : [] };
|
||||
mutate(x);
|
||||
$[0] = cond;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
|
||||
const xValue = x.value;
|
||||
let result;
|
||||
if (typeof xValue === "number") {
|
||||
result = xValue + 1;
|
||||
} else {
|
||||
let t1;
|
||||
if ($[2] !== value || $[3] !== xValue) {
|
||||
t1 = arrayPush(xValue, value);
|
||||
$[2] = value;
|
||||
$[3] = xValue;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
result = t1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, value: 2 },
|
||||
{ cond: false, value: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro for bug in our type inference system. We currently propagate inferred
|
||||
* types through control flow / potential type guards. Note that this is
|
||||
* inconsistent with both Flow and Typescript.
|
||||
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
|
||||
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* [2]
|
||||
* [3]
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* [2]
|
||||
* [2,3]
|
||||
*/
|
||||
function useFoo({cond, value}: {cond: boolean; value: number}) {
|
||||
const x = {value: cond ? CONST_NUMBER0 : []};
|
||||
mutate(x);
|
||||
|
||||
const xValue = x.value;
|
||||
let result;
|
||||
if (typeof xValue === 'number') {
|
||||
result = xValue + 1; // (1) here we infer xValue is a primitive
|
||||
} else {
|
||||
result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [
|
||||
{cond: false, value: 2},
|
||||
{cond: false, value: 3},
|
||||
],
|
||||
};
|
||||
@@ -55,7 +55,11 @@ function getNativeLogFunction(level) {
|
||||
if (arguments.length === 1 && typeof arguments[0] === "string") {
|
||||
str = arguments[0];
|
||||
} else {
|
||||
str = Array.prototype.map.call(arguments, _temp).join(", ");
|
||||
str = Array.prototype.map
|
||||
.call(arguments, function (arg) {
|
||||
return inspect(arg, { depth: 10 });
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
const firstArg = arguments[0];
|
||||
@@ -88,9 +92,6 @@ function getNativeLogFunction(level) {
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp(arg) {
|
||||
return inspect(arg, { depth: 10 });
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -2,55 +2,39 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {mutate} from 'shared-runtime';
|
||||
|
||||
function Component({foo, bar}) {
|
||||
function component(foo, bar) {
|
||||
let x = {foo};
|
||||
let y = {bar};
|
||||
const f0 = function () {
|
||||
let a = [y];
|
||||
let a = {y};
|
||||
let b = x;
|
||||
// this writes y.x = x
|
||||
a[0].x = b;
|
||||
a.x = b;
|
||||
};
|
||||
f0();
|
||||
mutate(y.x);
|
||||
mutate(y);
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 3, bar: 4}],
|
||||
sequentialRenders: [
|
||||
{foo: 3, bar: 4},
|
||||
{foo: 3, bar: 5},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
function component(foo, bar) {
|
||||
const $ = _c(3);
|
||||
const { foo, bar } = t0;
|
||||
let y;
|
||||
if ($[0] !== bar || $[1] !== foo) {
|
||||
const x = { foo };
|
||||
y = { bar };
|
||||
const f0 = function () {
|
||||
const a = [y];
|
||||
const a = { y };
|
||||
const b = x;
|
||||
|
||||
a[0].x = b;
|
||||
a.x = b;
|
||||
};
|
||||
|
||||
f0();
|
||||
mutate(y.x);
|
||||
mutate(y);
|
||||
$[0] = bar;
|
||||
$[1] = foo;
|
||||
$[2] = y;
|
||||
@@ -60,17 +44,5 @@ function Component(t0) {
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ foo: 3, bar: 4 }],
|
||||
sequentialRenders: [
|
||||
{ foo: 3, bar: 4 },
|
||||
{ foo: 3, bar: 5 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
{"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
import {mutate} from 'shared-runtime';
|
||||
|
||||
function Component({foo, bar}) {
|
||||
function component(foo, bar) {
|
||||
let x = {foo};
|
||||
let y = {bar};
|
||||
const f0 = function () {
|
||||
let a = [y];
|
||||
let a = {y};
|
||||
let b = x;
|
||||
// this writes y.x = x
|
||||
a[0].x = b;
|
||||
a.x = b;
|
||||
};
|
||||
f0();
|
||||
mutate(y.x);
|
||||
mutate(y);
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 3, bar: 4}],
|
||||
sequentialRenders: [
|
||||
{foo: 3, bar: 4},
|
||||
{foo: 3, bar: 5},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -26,20 +26,29 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function component(a, b) {
|
||||
const $ = _c(2);
|
||||
const y = { b };
|
||||
const $ = _c(5);
|
||||
let t0;
|
||||
if ($[0] !== b) {
|
||||
t0 = { b };
|
||||
$[0] = b;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const y = t0;
|
||||
let z;
|
||||
if ($[0] !== a) {
|
||||
if ($[2] !== a || $[3] !== y) {
|
||||
z = { a };
|
||||
const x = function () {
|
||||
z.a = 2;
|
||||
};
|
||||
|
||||
x();
|
||||
$[0] = a;
|
||||
$[1] = z;
|
||||
$[2] = a;
|
||||
$[3] = y;
|
||||
$[4] = z;
|
||||
} else {
|
||||
z = $[1];
|
||||
z = $[4];
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
@@ -2,28 +2,21 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {mutate} from 'shared-runtime';
|
||||
|
||||
function Component({a, b}) {
|
||||
function component(a, b) {
|
||||
let z = {a};
|
||||
let y = {b: {b}};
|
||||
let y = {b};
|
||||
let x = function () {
|
||||
z.a = 2;
|
||||
mutate(y.b);
|
||||
console.log(y.b);
|
||||
};
|
||||
x();
|
||||
return [y, z];
|
||||
return z;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 2, b: 3}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 3},
|
||||
{a: 2, b: 3},
|
||||
{a: 4, b: 3},
|
||||
{a: 4, b: 5},
|
||||
],
|
||||
fn: component,
|
||||
params: ['TodoAdd'],
|
||||
isComponent: 'TodoAdd',
|
||||
};
|
||||
|
||||
```
|
||||
@@ -32,46 +25,32 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
function component(a, b) {
|
||||
const $ = _c(3);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
let z;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const z = { a };
|
||||
const y = { b: { b } };
|
||||
z = { a };
|
||||
const y = { b };
|
||||
const x = function () {
|
||||
z.a = 2;
|
||||
mutate(y.b);
|
||||
console.log(y.b);
|
||||
};
|
||||
|
||||
x();
|
||||
t1 = [y, z];
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = t1;
|
||||
$[2] = z;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
z = $[2];
|
||||
}
|
||||
return t1;
|
||||
return z;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: 2, b: 3 }],
|
||||
sequentialRenders: [
|
||||
{ a: 2, b: 3 },
|
||||
{ a: 2, b: 3 },
|
||||
{ a: 4, b: 3 },
|
||||
{ a: 4, b: 5 },
|
||||
],
|
||||
fn: component,
|
||||
params: ["TodoAdd"],
|
||||
isComponent: "TodoAdd",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}]
|
||||
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
|
||||
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
|
||||
[{"b":{"b":5,"wat0":"joe"}},{"a":2}]
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import {mutate} from 'shared-runtime';
|
||||
|
||||
function Component({a, b}) {
|
||||
function component(a, b) {
|
||||
let z = {a};
|
||||
let y = {b: {b}};
|
||||
let y = {b};
|
||||
let x = function () {
|
||||
z.a = 2;
|
||||
mutate(y.b);
|
||||
console.log(y.b);
|
||||
};
|
||||
x();
|
||||
return [y, z];
|
||||
return z;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 2, b: 3}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 3},
|
||||
{a: 2, b: 3},
|
||||
{a: 4, b: 3},
|
||||
{a: 4, b: 5},
|
||||
],
|
||||
fn: component,
|
||||
params: ['TodoAdd'],
|
||||
isComponent: 'TodoAdd',
|
||||
};
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component({a, b}) {
|
||||
let z = {a};
|
||||
let y = {b};
|
||||
let x = function () {
|
||||
z.a = 2;
|
||||
return Math.max(y.b, 0);
|
||||
};
|
||||
x();
|
||||
return z;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 2, b: 3}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 3},
|
||||
{a: 2, b: 3},
|
||||
{a: 4, b: 3},
|
||||
{a: 4, b: 5},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(t0) {
|
||||
const $ = _c(3);
|
||||
const { a, b } = t0;
|
||||
let z;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
z = { a };
|
||||
const y = { b };
|
||||
const x = function () {
|
||||
z.a = 2;
|
||||
return Math.max(y.b, 0);
|
||||
};
|
||||
|
||||
x();
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = z;
|
||||
} else {
|
||||
z = $[2];
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: 2, b: 3 }],
|
||||
sequentialRenders: [
|
||||
{ a: 2, b: 3 },
|
||||
{ a: 2, b: 3 },
|
||||
{ a: 4, b: 3 },
|
||||
{ a: 4, b: 5 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"a":2}
|
||||
{"a":2}
|
||||
{"a":2}
|
||||
{"a":2}
|
||||
@@ -1,21 +0,0 @@
|
||||
function Component({a, b}) {
|
||||
let z = {a};
|
||||
let y = {b};
|
||||
let x = function () {
|
||||
z.a = 2;
|
||||
return Math.max(y.b, 0);
|
||||
};
|
||||
x();
|
||||
return z;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 2, b: 3}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 3},
|
||||
{a: 2, b: 3},
|
||||
{a: 4, b: 3},
|
||||
{a: 4, b: 5},
|
||||
],
|
||||
};
|
||||
@@ -16,7 +16,7 @@ function component() {
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function component() {
|
||||
const $ = _c(1);
|
||||
const [, setX] = useState(0);
|
||||
const [x, setX] = useState(0);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const handler = (v) => setX(v);
|
||||
|
||||
@@ -43,15 +43,16 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = { x };
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = { x: t0 };
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -42,15 +42,16 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = <div>{x}</div>;
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <div>{t0}</div>;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -43,15 +43,16 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = { x };
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = { x: t0 };
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -42,15 +42,16 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = { x };
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = { x: t0 };
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<Stringify key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
4 | // NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
5 | // within a closure, the `onClick` handler of each item
|
||||
> 6 | for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
| ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6)
|
||||
7 | items.push(<Stringify key={i} onClick={() => data.set(i)} />);
|
||||
8 | }
|
||||
9 | return items;
|
||||
```
|
||||
|
||||
|
||||
@@ -4,20 +4,7 @@ function Component() {
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(<Stringify key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
return <>{items}</>;
|
||||
return items;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
};
|
||||
@@ -98,6 +98,12 @@ Todo: (BuildHIR::lowerExpression) Handle tagged template with interpolations (30
|
||||
|
||||
Todo: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value (34:34)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle Identifier inits in ForOfStatement (36:36)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle ArrayPattern inits in ForOfStatement (38:38)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle ObjectPattern inits in ForOfStatement (40:40)
|
||||
|
||||
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `MemberExpression` cannot be safely reordered (57:57)
|
||||
|
||||
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `BinaryExpression` cannot be safely reordered (53:53)
|
||||
|
||||
@@ -35,7 +35,8 @@ function Component(props) {
|
||||
}
|
||||
let d;
|
||||
if ($[2] !== props.c) {
|
||||
[, ...d] = props.c;
|
||||
const [c, ...t0] = props.c;
|
||||
d = t0;
|
||||
$[2] = props.c;
|
||||
$[3] = d;
|
||||
} else {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const data = useData();
|
||||
let t0;
|
||||
if ($[0] !== data) {
|
||||
const items = [];
|
||||
for (let i = MIN; i <= MAX; i = i + INCREMENT, i) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
|
||||
t0 = <>{items}</>;
|
||||
$[0] = data;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Map();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div></div><div></div><div></div><div></div>
|
||||
@@ -34,7 +34,7 @@ function Component() {
|
||||
t0 = function update() {
|
||||
"worklet";
|
||||
|
||||
setCount(_temp);
|
||||
setCount((count_0) => count_0 + 1);
|
||||
};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
@@ -51,9 +51,6 @@ function Component() {
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _temp(count_0) {
|
||||
return count_0 + 1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
|
||||
@@ -24,7 +24,7 @@ function Component(props) {
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const [, setValue] = useState(null);
|
||||
const [value, setValue] = useState(null);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = (e) => setValue((value_0) => value_0 + e.target.value);
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {print, useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
function CustomConfig({propVal}) {
|
||||
// Insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal]);
|
||||
// No insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import { print, useSpecialEffect } from "shared-runtime";
|
||||
|
||||
function CustomConfig(t0) {
|
||||
const $ = _c(7);
|
||||
const { propVal } = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== propVal) {
|
||||
t1 = () => print(propVal);
|
||||
t2 = [propVal];
|
||||
$[0] = propVal;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useSpecialEffect(t1, t2, [propVal]);
|
||||
let t3;
|
||||
let t4;
|
||||
let t5;
|
||||
if ($[3] !== propVal) {
|
||||
t3 = () => print(propVal);
|
||||
t4 = [propVal];
|
||||
t5 = [propVal];
|
||||
$[3] = propVal;
|
||||
$[4] = t3;
|
||||
$[5] = t4;
|
||||
$[6] = t5;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
t4 = $[5];
|
||||
t5 = $[6];
|
||||
}
|
||||
useSpecialEffect(t3, t4, t5);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,9 +0,0 @@
|
||||
// @inferEffectDependencies
|
||||
import {print, useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
function CustomConfig({propVal}) {
|
||||
// Insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal]);
|
||||
// No insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, useRef} from 'react';
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
const moduleNonReactive = 0;
|
||||
|
||||
function Component({foo, bar}) {
|
||||
const localNonreactive = 0;
|
||||
const ref = useRef(0);
|
||||
const localNonPrimitiveReactive = {
|
||||
foo,
|
||||
};
|
||||
const localNonPrimitiveNonreactive = {};
|
||||
useEffect(() => {
|
||||
console.log(foo);
|
||||
console.log(bar);
|
||||
console.log(moduleNonReactive);
|
||||
console.log(localNonreactive);
|
||||
console.log(globalValue);
|
||||
console.log(ref.current);
|
||||
console.log(localNonPrimitiveReactive);
|
||||
console.log(localNonPrimitiveNonreactive);
|
||||
});
|
||||
|
||||
// Optional chains and property accesses
|
||||
// TODO: we may be able to save bytes by omitting property accesses if the
|
||||
// object of the member expression is already included in the inferred deps
|
||||
useEffect(() => {
|
||||
console.log(bar?.baz);
|
||||
console.log(bar.qux);
|
||||
});
|
||||
|
||||
function f() {
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
|
||||
useEffectWrapper(() => {
|
||||
console.log(foo);
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import { useEffect, useRef } from "react";
|
||||
import useEffectWrapper from "useEffectWrapper";
|
||||
|
||||
const moduleNonReactive = 0;
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(14);
|
||||
const { foo, bar } = t0;
|
||||
|
||||
const ref = useRef(0);
|
||||
let t1;
|
||||
if ($[0] !== foo) {
|
||||
t1 = { foo };
|
||||
$[0] = foo;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const localNonPrimitiveReactive = t1;
|
||||
let t2;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = {};
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
const localNonPrimitiveNonreactive = t2;
|
||||
let t3;
|
||||
if ($[3] !== bar || $[4] !== foo || $[5] !== localNonPrimitiveReactive) {
|
||||
t3 = () => {
|
||||
console.log(foo);
|
||||
console.log(bar);
|
||||
console.log(moduleNonReactive);
|
||||
console.log(0);
|
||||
console.log(globalValue);
|
||||
console.log(ref.current);
|
||||
console.log(localNonPrimitiveReactive);
|
||||
console.log(localNonPrimitiveNonreactive);
|
||||
};
|
||||
$[3] = bar;
|
||||
$[4] = foo;
|
||||
$[5] = localNonPrimitiveReactive;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
useEffect(t3, [
|
||||
foo,
|
||||
bar,
|
||||
ref,
|
||||
localNonPrimitiveReactive,
|
||||
localNonPrimitiveNonreactive,
|
||||
]);
|
||||
let t4;
|
||||
if ($[7] !== bar.baz || $[8] !== bar.qux) {
|
||||
t4 = () => {
|
||||
console.log(bar?.baz);
|
||||
console.log(bar.qux);
|
||||
};
|
||||
$[7] = bar.baz;
|
||||
$[8] = bar.qux;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t4 = $[9];
|
||||
}
|
||||
useEffect(t4, [bar.baz, bar.qux]);
|
||||
let t5;
|
||||
if ($[10] !== foo) {
|
||||
t5 = function f() {
|
||||
console.log(foo);
|
||||
};
|
||||
$[10] = foo;
|
||||
$[11] = t5;
|
||||
} else {
|
||||
t5 = $[11];
|
||||
}
|
||||
const f = t5;
|
||||
|
||||
useEffect(f);
|
||||
let t6;
|
||||
if ($[12] !== foo) {
|
||||
t6 = () => {
|
||||
console.log(foo);
|
||||
};
|
||||
$[12] = foo;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
useEffectWrapper(t6, [foo]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,43 +0,0 @@
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, useRef} from 'react';
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
const moduleNonReactive = 0;
|
||||
|
||||
function Component({foo, bar}) {
|
||||
const localNonreactive = 0;
|
||||
const ref = useRef(0);
|
||||
const localNonPrimitiveReactive = {
|
||||
foo,
|
||||
};
|
||||
const localNonPrimitiveNonreactive = {};
|
||||
useEffect(() => {
|
||||
console.log(foo);
|
||||
console.log(bar);
|
||||
console.log(moduleNonReactive);
|
||||
console.log(localNonreactive);
|
||||
console.log(globalValue);
|
||||
console.log(ref.current);
|
||||
console.log(localNonPrimitiveReactive);
|
||||
console.log(localNonPrimitiveNonreactive);
|
||||
});
|
||||
|
||||
// Optional chains and property accesses
|
||||
// TODO: we may be able to save bytes by omitting property accesses if the
|
||||
// object of the member expression is already included in the inferred deps
|
||||
useEffect(() => {
|
||||
console.log(bar?.baz);
|
||||
console.log(bar.qux);
|
||||
});
|
||||
|
||||
function f() {
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
|
||||
useEffectWrapper(() => {
|
||||
console.log(foo);
|
||||
});
|
||||
}
|
||||
@@ -60,10 +60,6 @@ function ConditionalJsx({shouldWrap}) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function ComponentWithSpreadPropsAndRef({ref, ...other}) {
|
||||
return <Foo ref={ref} {...other} />;
|
||||
}
|
||||
|
||||
// TODO: Support value blocks
|
||||
function TernaryJsx({cond}) {
|
||||
return cond ? <div /> : null;
|
||||
@@ -95,7 +91,7 @@ function Parent(t0) {
|
||||
type: "div",
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: { ref: ref, children: children },
|
||||
props: { children: children },
|
||||
};
|
||||
}
|
||||
$[0] = children;
|
||||
@@ -184,7 +180,7 @@ function ParentAndRefAndKey(props) {
|
||||
type: Parent,
|
||||
ref: testRef,
|
||||
key: "testKey",
|
||||
props: { a: "a", b: { b: "b" }, c: C, ref: testRef },
|
||||
props: { a: "a", b: { b: "b" }, c: C },
|
||||
};
|
||||
}
|
||||
$[0] = t0;
|
||||
@@ -413,41 +409,6 @@ function ConditionalJsx(t0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function ComponentWithSpreadPropsAndRef(t0) {
|
||||
const $ = _c2(6);
|
||||
let other;
|
||||
let ref;
|
||||
if ($[0] !== t0) {
|
||||
({ ref, ...other } = t0);
|
||||
$[0] = t0;
|
||||
$[1] = other;
|
||||
$[2] = ref;
|
||||
} else {
|
||||
other = $[1];
|
||||
ref = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== other || $[4] !== ref) {
|
||||
if (DEV) {
|
||||
t1 = <Foo ref={ref} {...other} />;
|
||||
} else {
|
||||
t1 = {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Foo,
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: { ref: ref, ...other },
|
||||
};
|
||||
}
|
||||
$[3] = other;
|
||||
$[4] = ref;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
// TODO: Support value blocks
|
||||
function TernaryJsx(t0) {
|
||||
const $ = _c2(2);
|
||||
|
||||
@@ -56,10 +56,6 @@ function ConditionalJsx({shouldWrap}) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function ComponentWithSpreadPropsAndRef({ref, ...other}) {
|
||||
return <Foo ref={ref} {...other} />;
|
||||
}
|
||||
|
||||
// TODO: Support value blocks
|
||||
function TernaryJsx({cond}) {
|
||||
return cond ? <div /> : null;
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Throw} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Note: this is disabled in the evaluator due to different devmode errors.
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
|
||||
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
|
||||
*
|
||||
* Forget:
|
||||
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
|
||||
* logs: [
|
||||
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
|
||||
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
|
||||
* ]
|
||||
*/
|
||||
function useFoo() {
|
||||
const invalidTag = Throw;
|
||||
/**
|
||||
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
|
||||
* Throw). Note that the jsx transform turns this into a string tag:
|
||||
* `jsx("invalidTag"...
|
||||
*/
|
||||
return <invalidTag val={{val: 2}} />;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Throw } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Note: this is disabled in the evaluator due to different devmode errors.
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
|
||||
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
|
||||
*
|
||||
* Forget:
|
||||
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
|
||||
* logs: [
|
||||
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
|
||||
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
|
||||
* ]
|
||||
*/
|
||||
function useFoo() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <invalidTag val={{ val: 2 }} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import {Throw} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Note: this is disabled in the evaluator due to different devmode errors.
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
|
||||
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
|
||||
*
|
||||
* Forget:
|
||||
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
|
||||
* logs: [
|
||||
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
|
||||
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
|
||||
* ]
|
||||
*/
|
||||
function useFoo() {
|
||||
const invalidTag = Throw;
|
||||
/**
|
||||
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
|
||||
* Throw). Note that the jsx transform turns this into a string tag:
|
||||
* `jsx("invalidTag"...
|
||||
*/
|
||||
return <invalidTag val={{val: 2}} />;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
@@ -27,10 +27,11 @@ import * as SharedRuntime from "shared-runtime";
|
||||
function useFoo(t0) {
|
||||
const $ = _c(1);
|
||||
const { cond } = t0;
|
||||
const MyLocal = SharedRuntime;
|
||||
if (cond) {
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <SharedRuntime.Text value={4} />;
|
||||
t1 = <MyLocal.Text value={4} />;
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
|
||||
@@ -22,9 +22,10 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
function useFoo() {
|
||||
const $ = _c(1);
|
||||
const MyLocal = SharedRuntime;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <SharedRuntime.Text value={4} />;
|
||||
t0 = <MyLocal.Text value={4} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import * as SharedRuntime from 'shared-runtime';
|
||||
import {invoke} from 'shared-runtime';
|
||||
function useComponentFactory({name}) {
|
||||
const localVar = SharedRuntime;
|
||||
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
|
||||
return invoke(cb);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useComponentFactory,
|
||||
params: [{name: 'sathya'}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
import { invoke } from "shared-runtime";
|
||||
function useComponentFactory(t0) {
|
||||
const $ = _c(4);
|
||||
const { name } = t0;
|
||||
let t1;
|
||||
if ($[0] !== name) {
|
||||
t1 = () => (
|
||||
<SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>
|
||||
);
|
||||
$[0] = name;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const cb = t1;
|
||||
let t2;
|
||||
if ($[2] !== cb) {
|
||||
t2 = invoke(cb);
|
||||
$[2] = cb;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useComponentFactory,
|
||||
params: [{ name: "sathya" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as SharedRuntime from 'shared-runtime';
|
||||
import {invoke} from 'shared-runtime';
|
||||
function useComponentFactory({name}) {
|
||||
const localVar = SharedRuntime;
|
||||
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
|
||||
return invoke(cb);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useComponentFactory,
|
||||
params: [{name: 'sathya'}],
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user