Repository: preactjs/preact Branch: main Commit: 21dd6d04c1a9 Files: 295 Total size: 1.3 MB Directory structure: gitextract_o9kdplb8/ ├── .editorconfig ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── benchmarks.yml │ ├── build-test.yml │ ├── ci.yml │ ├── pr-reporter.yml │ ├── release.yml │ ├── run-bench.yml │ ├── single-bench.yml │ └── size.yml ├── .gitignore ├── .gitmodules ├── .husky/ │ └── pre-commit ├── .oxlintrc.json ├── .prettierignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── biome.json ├── compat/ │ ├── LICENSE │ ├── client.d.ts │ ├── client.js │ ├── client.mjs │ ├── jsx-dev-runtime.js │ ├── jsx-dev-runtime.mjs │ ├── jsx-runtime.js │ ├── jsx-runtime.mjs │ ├── mangle.json │ ├── package.json │ ├── scheduler.d.ts │ ├── scheduler.js │ ├── scheduler.mjs │ ├── server.browser.js │ ├── server.d.ts │ ├── server.js │ ├── server.mjs │ ├── src/ │ │ ├── Children.js │ │ ├── PureComponent.js │ │ ├── forwardRef.js │ │ ├── hooks.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── internal.d.ts │ │ ├── memo.js │ │ ├── portals.js │ │ ├── render.js │ │ ├── suspense.d.ts │ │ ├── suspense.js │ │ └── util.js │ ├── test/ │ │ ├── browser/ │ │ │ ├── Children.test.jsx │ │ │ ├── PureComponent.test.jsx │ │ │ ├── cloneElement.test.jsx │ │ │ ├── compat.options.test.jsx │ │ │ ├── component.test.jsx │ │ │ ├── componentDidCatch.test.jsx │ │ │ ├── context.test.jsx │ │ │ ├── createElement.test.jsx │ │ │ ├── createFactory.test.jsx │ │ │ ├── events.test.jsx │ │ │ ├── exports.test.js │ │ │ ├── findDOMNode.test.jsx │ │ │ ├── forwardRef.test.jsx │ │ │ ├── hooks.test.jsx │ │ │ ├── hydrate.test.jsx │ │ │ ├── isFragment.test.js │ │ │ ├── isMemo.test.jsx │ │ │ ├── isValidElement.test.js │ │ │ ├── memo.test.jsx │ │ │ ├── portals.test.jsx │ │ │ ├── render.test.jsx │ │ │ ├── scheduler.test.js │ │ │ ├── select.test.jsx │ │ │ ├── suspense-hydration.test.jsx │ │ │ ├── suspense-utils.js │ │ │ ├── suspense.test.jsx │ │ │ ├── svg.test.jsx │ │ │ ├── testUtils.js │ │ │ ├── textarea.test.jsx │ │ │ ├── unmountComponentAtNode.test.jsx │ │ │ ├── unstable_batchedUpdates.test.js │ │ │ └── useSyncExternalStore.test.jsx │ │ └── ts/ │ │ ├── forward-ref.tsx │ │ ├── index.tsx │ │ ├── lazy.tsx │ │ ├── memo.tsx │ │ ├── react-default.tsx │ │ ├── react-star.tsx │ │ ├── scheduler.ts │ │ ├── suspense.tsx │ │ ├── tsconfig.json │ │ └── utils.ts │ ├── test-utils.js │ └── test-utils.mjs ├── config/ │ └── compat-entries.js ├── debug/ │ ├── LICENSE │ ├── mangle.json │ ├── package.json │ ├── src/ │ │ ├── check-props.js │ │ ├── component-stack.js │ │ ├── constants.js │ │ ├── debug.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── internal.d.ts │ │ └── util.js │ └── test/ │ └── browser/ │ ├── component-stack-2.test.jsx │ ├── component-stack.test.jsx │ ├── debug-compat.test.jsx │ ├── debug-hooks.test.jsx │ ├── debug-suspense.test.jsx │ ├── debug.options.test.jsx │ ├── debug.test.jsx │ ├── fakeDevTools.js │ ├── prop-types.test.js │ ├── serializeVNode.test.jsx │ └── validateHookArgs.test.jsx ├── demo/ │ ├── contenteditable.jsx │ ├── context.jsx │ ├── devtools.jsx │ ├── fragments.jsx │ ├── index.html │ ├── index.jsx │ ├── key_bug.jsx │ ├── list.jsx │ ├── logger.jsx │ ├── mobx.jsx │ ├── nested-suspense/ │ │ ├── addnewcomponent.jsx │ │ ├── component-container.jsx │ │ ├── dropzone.jsx │ │ ├── editor.jsx │ │ ├── index.jsx │ │ └── subcomponent.jsx │ ├── old.js.bak │ ├── package.json │ ├── people/ │ │ ├── Readme.md │ │ ├── index.tsx │ │ ├── profile.tsx │ │ ├── router.tsx │ │ ├── store.ts │ │ └── styles/ │ │ ├── animations.scss │ │ ├── app.scss │ │ ├── avatar.scss │ │ ├── button.scss │ │ ├── index.scss │ │ └── profile.scss │ ├── preact.jsx │ ├── profiler.jsx │ ├── pythagoras/ │ │ ├── index.jsx │ │ └── pythagoras.jsx │ ├── redux-toolkit.jsx │ ├── redux.jsx │ ├── reduxUpdate.jsx │ ├── reorder.jsx │ ├── spiral.jsx │ ├── stateOrderBug.jsx │ ├── style.css │ ├── style.scss │ ├── styled-components.jsx │ ├── suspense-router/ │ │ ├── bye.jsx │ │ ├── hello.jsx │ │ ├── index.jsx │ │ └── simple-router.jsx │ ├── suspense.jsx │ ├── textFields.jsx │ ├── todo.jsx │ ├── tsconfig.json │ ├── vite.config.js │ └── zustand.jsx ├── devtools/ │ ├── LICENSE │ ├── mangle.json │ ├── package.json │ ├── src/ │ │ ├── devtools.js │ │ ├── index.d.ts │ │ └── index.js │ └── test/ │ └── browser/ │ └── addHookName.test.jsx ├── hooks/ │ ├── LICENSE │ ├── mangle.json │ ├── package.json │ ├── src/ │ │ ├── index.d.ts │ │ ├── index.js │ │ └── internal.d.ts │ └── test/ │ ├── _util/ │ │ └── useEffectUtil.js │ └── browser/ │ ├── combinations.test.jsx │ ├── componentDidCatch.test.jsx │ ├── errorBoundary.test.jsx │ ├── hooks.options.test.jsx │ ├── useCallback.test.jsx │ ├── useContext.test.jsx │ ├── useDebugValue.test.jsx │ ├── useEffect.test.jsx │ ├── useEffectAssertions.jsx │ ├── useId.test.jsx │ ├── useImperativeHandle.test.jsx │ ├── useLayoutEffect.test.jsx │ ├── useMemo.test.jsx │ ├── useReducer.test.jsx │ ├── useRef.test.jsx │ └── useState.test.jsx ├── jsconfig-lint.json ├── jsconfig.json ├── jsx-runtime/ │ ├── LICENSE │ ├── mangle.json │ ├── package.json │ ├── src/ │ │ ├── index.d.ts │ │ ├── index.js │ │ └── utils.js │ └── test/ │ └── browser/ │ └── jsx-runtime.test.js ├── mangle.json ├── package.json ├── scripts/ │ └── release/ │ ├── create-gh-release.js │ ├── publish.mjs │ └── upload-gh-asset.js ├── src/ │ ├── clone-element.js │ ├── component.js │ ├── constants.js │ ├── create-context.js │ ├── create-element.js │ ├── diff/ │ │ ├── catch-error.js │ │ ├── children.js │ │ ├── index.js │ │ └── props.js │ ├── dom.d.ts │ ├── index.d.ts │ ├── index.js │ ├── internal.d.ts │ ├── jsx.d.ts │ ├── options.js │ ├── render.js │ └── util.js ├── test/ │ ├── _util/ │ │ ├── dom.js │ │ ├── helpers.jsx │ │ ├── logCall.js │ │ └── optionSpies.js │ ├── browser/ │ │ ├── cloneElement.test.jsx │ │ ├── components.test.jsx │ │ ├── context.test.jsx │ │ ├── createContext.test.jsx │ │ ├── customBuiltInElements.test.jsx │ │ ├── events.test.jsx │ │ ├── focus.test.jsx │ │ ├── fragments.test.jsx │ │ ├── getDomSibling.test.jsx │ │ ├── hydrate.test.jsx │ │ ├── isValidElement.test.js │ │ ├── keys.test.jsx │ │ ├── lifecycles/ │ │ │ ├── componentDidCatch.test.jsx │ │ │ ├── componentDidMount.test.jsx │ │ │ ├── componentDidUpdate.test.jsx │ │ │ ├── componentWillMount.test.jsx │ │ │ ├── componentWillReceiveProps.test.jsx │ │ │ ├── componentWillUnmount.test.jsx │ │ │ ├── componentWillUpdate.test.jsx │ │ │ ├── getDerivedStateFromError.test.jsx │ │ │ ├── getDerivedStateFromProps.test.jsx │ │ │ ├── getSnapshotBeforeUpdate.test.jsx │ │ │ ├── lifecycle.test.jsx │ │ │ └── shouldComponentUpdate.test.jsx │ │ ├── mathml.test.jsx │ │ ├── placeholders.test.jsx │ │ ├── refs.test.jsx │ │ ├── render.test.jsx │ │ ├── select.test.jsx │ │ ├── spec.test.jsx │ │ ├── style.test.jsx │ │ ├── svg.test.jsx │ │ └── toChildArray.test.jsx │ ├── fixtures/ │ │ └── preact.js │ ├── node/ │ │ └── index.test.js │ ├── shared/ │ │ ├── createContext.test.jsx │ │ ├── createElement.test.jsx │ │ ├── exports.test.js │ │ ├── isValidElement.test.js │ │ └── isValidElementTests.jsx │ └── ts/ │ ├── Component.test.tsx │ ├── VNode.test.tsx │ ├── custom-elements.tsx │ ├── dom-attributes.test-d.tsx │ ├── hoc.test.tsx │ ├── jsx-namespace.test-d.tsx │ ├── package.json │ ├── preact-global.test-d.tsx │ ├── preact.tsx │ ├── refs.tsx │ └── tsconfig.json ├── test-utils/ │ ├── package.json │ ├── src/ │ │ ├── index.d.ts │ │ └── index.js │ └── test/ │ └── shared/ │ ├── act.test.jsx │ └── rerender.test.jsx ├── types/ │ └── weak-key.d.ts ├── vitest.config.mjs └── vitest.setup.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{*.json,.*rc,*.yml}] indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@preactjs.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- - [ ] Check if updating to the latest Preact version resolves the issue **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** If possible, please provide a link to a StackBlitz/CodeSandbox/Codepen project or a GitHub repository that demonstrates the issue. You can use the following template on StackBlitz to get started: https://stackblitz.com/edit/create-preact-starter Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. See error **Expected behavior** What should have happened when following the steps above? ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- **Describe the feature you'd love to see** A clear and concise description of what you'd love to see added to Preact. **Additional context (optional)** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/workflows/benchmarks.yml ================================================ name: Benchmarks on: workflow_dispatch: workflow_call: jobs: prepare: name: Prepare environment runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Download locally built preact package uses: actions/download-artifact@v4 with: name: npm-package - run: mv preact.tgz preact-local.tgz - name: Download base package uses: andrewiggins/download-base-artifact@v3 with: artifact: npm-package workflow: ci.yml required: true - run: mv preact.tgz preact-main.tgz - name: Upload locally build & base preact package uses: actions/upload-artifact@v4 with: name: bench-environment path: | preact-local.tgz preact-main.tgz bench_todo: name: Bench todo uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: todo/todo timeout: 10 bench_text_update: name: Bench text-update uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: text-update/text-update timeout: 10 bench_many_updates: name: Bench many-updates uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: many-updates/many-updates timeout: 10 bench_replace1k: name: Bench replace1k uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: table-app/replace1k bench_update10th1k: name: Bench 03_update10th1k_x16 uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: table-app/update10th1k bench_create10k: name: Bench create10k uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: table-app/create10k bench_hydrate1k: name: Bench hydrate1k uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: table-app/hydrate1k bench_filter_list: name: Bench filter-list uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: filter-list/filter-list timeout: 10 ================================================ FILE: .github/workflows/build-test.yml ================================================ name: Build & Test on: workflow_dispatch: workflow_call: inputs: ref: description: 'Branch or tag ref to check out' type: string required: false default: '' artifact_name: description: 'Name of the artifact to upload' type: string required: false default: 'npm-package' jobs: build_test: name: Build & Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.ref || '' }} - uses: actions/setup-node@v4 with: node-version-file: 'package.json' cache: 'npm' cache-dependency-path: '**/package-lock.json' - run: npm ci - name: test env: CI: true COVERAGE: true FLAKEY: false # Not using `npm test` since it rebuilds source which npm ci has already done run: npm run lint && npm run test:unit - name: Coveralls GitHub Action uses: coverallsapp/github-action@v2.3.0 timeout-minutes: 2 with: github-token: ${{ secrets.GITHUB_TOKEN }} fail-on-error: false - name: Package # Use --ignore-scripts here to avoid re-building again before pack run: | npm pack --ignore-scripts mv preact-*.tgz preact.tgz - name: Upload npm package uses: actions/upload-artifact@v4 with: name: ${{ inputs.artifact_name || 'npm-package' }} path: preact.tgz ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: workflow_dispatch: pull_request: branches: - '**' push: branches: - main - restructure - v11 jobs: filter_jobs: name: Filter jobs runs-on: ubuntu-latest outputs: jsChanged: ${{ steps.filter.outputs.jsChanged }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: # Should be kept in sync with the filter in the PR Reporter workflow predicate-quantifier: 'every' filters: | jsChanged: - '**/src/**/*.js' - '!devtools/src/devtools.js' compressed_size: name: Compressed Size needs: filter_jobs if: ${{ needs.filter_jobs.outputs.jsChanged == 'true' }} uses: ./.github/workflows/size.yml build_test: name: Build & Test needs: filter_jobs uses: ./.github/workflows/build-test.yml benchmarks: name: Benchmarks needs: build_test if: ${{ needs.filter_jobs.outputs.jsChanged == 'true' }} uses: ./.github/workflows/benchmarks.yml ================================================ FILE: .github/workflows/pr-reporter.yml ================================================ name: Report Results to PR on: # The pull_request event can't write comments for PRs from forks so using this # workflow_run workflow as a workaround workflow_run: workflows: ['CI'] types: - completed - requested jobs: filter_jobs: name: Filter jobs runs-on: ubuntu-latest if: | github.event.workflow_run.event == 'pull_request' outputs: jsChanged: ${{ steps.filter.outputs.jsChanged }} steps: - uses: actions/checkout@v4 # Warning: This is kinda gnarly and weird! GitHub doesn't expose the `pull_request` # data if the PR is from a fork, nor does it let you access that data via their API # (by querying for PRs associated with the commit or querying the commit itself, both # come up empty for forked commits). As such, to get the base SHA of the PR we need to # query for all PRs on the repo and match the owner+branch to find the right one and # then extract the base SHA from that. # # I see no reason why GitHub shouldn't expose this, it's just an SHA, branch name, and # URL, but they don't so we're doing it this way. Hopefully we can remove this one day. - name: Get PR Base SHA id: get_pr_base_sha env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} FORK_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }} FORK_BRANCH: ${{ github.event.workflow_run.head_branch }} run: | set -euo pipefail PR_JSON=$(gh api "repos/preactjs/preact/pulls?state=all&head=$FORK_OWNER:$FORK_BRANCH") BASE_SHA=$(jq -r '.[0].base.sha' <<< "$PR_JSON") echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" - uses: dorny/paths-filter@v3 id: filter with: # As this Workflow is triggered by a `workflow_run` event, the filter action # can't automatically assume we're working with PR data. As such, we need to # wire it up manually with a base (merge target) and ref (source branch). base: ${{ steps.get_pr_base_sha.outputs.base_sha }} ref: ${{ github.event.workflow_run.head_sha }} # Should be kept in sync with the filter in the CI workflow predicate-quantifier: 'every' filters: | jsChanged: - '**/src/**/*.js' - '!devtools/src/devtools.js' report_running: name: Report benchmarks are in-progress needs: filter_jobs runs-on: ubuntu-latest # Only add the "benchmarks are running" text when a workflow_run is # requested (a.k.a starting) if: | needs.filter_jobs.outputs.jsChanged == 'true' && github.event.action == 'requested' steps: - name: Report Tachometer Running uses: andrewiggins/tachometer-reporter-action@v2 with: # Set initialize to true so this action just creates the comment or # adds the "benchmarks are running" text initialize: true report_results: name: Report benchmark results needs: filter_jobs runs-on: ubuntu-latest # Only run this job if the event action was "completed" and the triggering # workflow_run was successful if: | needs.filter_jobs.outputs.jsChanged == 'true' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' steps: # Download the artifact from the triggering workflow that contains the # Tachometer results to report - uses: dawidd6/action-download-artifact@v2 with: workflow: ${{ github.event.workflow.id }} run_id: ${{ github.event.workflow_run.id }} name_is_regexp: true name: results-* path: results # Create/update the comment with the latest results - name: Report Tachometer Results uses: andrewiggins/tachometer-reporter-action@v2 with: path: results/**/*.json base-bench-name: preact-main pr-bench-name: preact-local summarize: 'duration, usedJSHeapSize' ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: create jobs: build: if: github.ref_type == 'tag' uses: preactjs/preact/.github/workflows/build-test.yml@main release: runs-on: ubuntu-latest needs: build steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: npm-package - name: Create draft release id: create-release uses: actions/github-script@v6 with: script: | const script = require('./scripts/release/create-gh-release.js') return script({ github, context }) - name: Upload release artifact uses: actions/github-script@v6 with: script: | const script = require('./scripts/release/upload-gh-asset.js') return script({ require, github, context, glob, release: ${{ steps.create-release.outputs.result }} }) ================================================ FILE: .github/workflows/run-bench.yml ================================================ name: Benchmark Worker # Expectations: # # This workflow expects calling workflows to have uploaded an artifact named # "bench-environment" that contains any built artifacts required to run the # benchmark. This typically is the dist/ folder that running `npm run build` # produces and/or a tarball of a previous build to bench the local build against on: workflow_call: inputs: benchmark: description: 'The name of the benchmark to run. Should be name of an HTML file without the .html extension' type: string required: true trace: description: 'Whether to capture browser traces for this benchmark run' type: boolean required: false default: false timeout: description: 'How many minutes to give the benchmark to run before timing out and failing' type: number required: false default: 20 jobs: run_bench: name: Bench ${{ inputs.benchmark }} runs-on: ubuntu-latest timeout-minutes: ${{ inputs.timeout }} steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - uses: actions/setup-node@v4 with: node-version-file: 'package.json' cache: 'npm' cache-dependency-path: '**/package-lock.json' # Setup pnpm - name: Install pnpm uses: pnpm/action-setup@v3 with: version: 8 run_install: false - name: Get pnpm store directory id: pnpm-cache run: | echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- # Install benchmark dependencies - uses: actions/download-artifact@v4 with: name: bench-environment - name: Move tarballs from env to correct location run: | ls -al mv preact-local.tgz benchmarks/dependencies/preact/local-pinned/preact-local-pinned.tgz ls -al benchmarks/dependencies/preact/local-pinned mv preact-main.tgz benchmarks/dependencies/preact/main/preact-main.tgz ls -al benchmarks/dependencies/preact/main - name: Install deps working-directory: benchmarks # Set the CHROMEDRIVER_FILEPATH so the chromedriver npm package uses the # correct binary when its installed run: | export CHROMEDRIVER_FILEPATH=$(which chromedriver) pnpm install # Install local dependencies with --no-frozen-lockfile to ensure local tarballs # are installed regardless of if they match the integrity hash stored in the lockfile pnpm install --no-frozen-lockfile --filter ./dependencies # Run benchmark - name: Run benchmark working-directory: benchmarks run: | export CHROMEDRIVER_FILEPATH=$(which chromedriver) pnpm run bench apps/${{ inputs.benchmark }}.html -d preact@local-pinned -d preact@main --trace=${{ inputs.trace }} # Prepare output - name: Anaylze logs if present working-directory: benchmarks run: '[ -d out/logs ] && pnpm run analyze ${{ inputs.benchmark }} || echo "No logs to analyze"' - name: Tar logs if present working-directory: benchmarks run: | if [ -d out/logs ]; then LOGS_FILE=out/${{ inputs.benchmark }}_logs.tgz mkdir -p $(dirname $LOGS_FILE) tar -zcvf $LOGS_FILE out/logs else echo "No logs found" fi # Upload results and logs - name: Calculate log artifact name id: log-artifact-name run: | NAME=$(echo "${{ inputs.benchmark }}" | sed -r 's/[\/]+/_/g') echo "clean_name=$NAME" >> $GITHUB_OUTPUT echo "artifact_name=logs_$NAME" >> $GITHUB_OUTPUT - name: Upload results uses: actions/upload-artifact@v4 with: name: results-${{ steps.log-artifact-name.outputs.clean_name }} path: benchmarks/out/results/${{ inputs.benchmark }}.json - name: Upload logs uses: actions/upload-artifact@v4 with: name: ${{ steps.log-artifact-name.outputs.artifact_name }} path: benchmarks/out/${{ inputs.benchmark }}_logs.tgz if-no-files-found: ignore ================================================ FILE: .github/workflows/single-bench.yml ================================================ name: Benchmark Debug on: workflow_dispatch: inputs: benchmark: description: 'Which benchmark to run' type: choice options: - table-app/replace1k - table-app/update10th1k - table-app/create10k - table-app/hydrate1k - filter_list/filter-list - many-updates/many-updates - text-update/text-update - todo/todo required: true base: description: 'The branch name, tag, or commit sha of the version of preact to benchmark against.' type: string default: main required: false trace: description: 'Whether to capture browser traces for this benchmark run' type: boolean default: true required: false # A bug in GitHub actions prevents us from passing numbers (as either # number or string type) to called workflows. So disabling this for now. # See: https://github.com/orgs/community/discussions/67182 # # timeout: # description: 'How many minutes to give the benchmark to run before timing out and failing' # type: number # default: 20 # required: false jobs: build_local: name: Build local package uses: ./.github/workflows/ci.yml build_base: name: Build base package uses: ./.github/workflows/ci.yml with: ref: ${{ inputs.base }} artifact_name: base-npm-package prepare: name: Prepare environment runs-on: ubuntu-latest needs: - build_local - build_base timeout-minutes: 5 steps: - name: Download locally built preact package uses: actions/download-artifact@v4 with: name: npm-package - run: mv preact.tgz preact-local.tgz - name: Clear working directory run: | ls -al rm -rf * echo "====================" ls -al - name: Download base package uses: actions/download-artifact@v4 with: name: base-npm-package - run: mv preact.tgz preact-main.tgz - name: Upload locally built & base preact package uses: actions/upload-artifact@v4 with: name: bench-environment path: | preact-local.tgz preact-main.tgz benchmark: name: Bench ${{ inputs.benchmark }} uses: ./.github/workflows/run-bench.yml needs: prepare with: benchmark: ${{ inputs.benchmark }} trace: ${{ inputs.trace }} # timeout: ${{ inputs.timeout }} ================================================ FILE: .github/workflows/size.yml ================================================ name: Compressed Size on: workflow_call: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: 'package.json' cache: 'npm' cache-dependency-path: '**/package-lock.json' - uses: preactjs/compressed-size-action@v2 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' # Our `prepare` script already builds the app post-install, # building it again would be redundant build-script: 'npm run --if-present noop' ================================================ FILE: .gitignore ================================================ .DS_Store node_modules npm-debug.log dist */package-lock.json yarn.lock .vscode .idea test/ts/**/*.js coverage *.sw[op] *.log package/ preact-*.tgz preact.tgz *.local.* ================================================ FILE: .gitmodules ================================================ [submodule "benchmarks"] path = benchmarks url = https://github.com/preactjs/benchmarks ================================================ FILE: .husky/pre-commit ================================================ npx nano-staged ================================================ FILE: .oxlintrc.json ================================================ { "$schema": "./node_modules/oxlint/configuration_schema.json", "ignorePatterns": [ "**/dist/**", "benchmarks/**" ], "rules": { "camelcase": [ 1, { "allow": ["__test__*", "unstable_*", "UNSAFE_*"] } ], "no-unused-vars": [ 2, { "args": "none", "caughtErrors": "none", "varsIgnorePattern": "^h|React|createElement|Fragment$" } ], "typescript/no-namespace": 0, "no-constant-binary-expression": 0, "no-useless-catch": 0, "no-empty-pattern": 0, "prefer-rest-params": 0, "prefer-spread": 0, "no-cond-assign": 0, "react/jsx-no-bind": 0, "react/no-danger": 0, "react/no-danger-with-children": 0, "react/prefer-stateless-function": 0, "react/sort-comp": 0, "jest/valid-expect": 0, "jest/no-disabled-tests": 0, "jest/no-test-callback": 0, "jest/expect-expect": 0, "jest/no-standalone-expect": 0, "jest/no-export": 0, "react/no-find-dom-node": 0, "react/no-direct-mutation-state": 0, "react/no-children-prop": 0, "react/jsx-key": 0, "react/no-string-refs": 0, "react/require-render-return": 0, "unicorn/no-new-array": 0, "unicorn/prefer-string-starts-ends-with": 0 } } ================================================ FILE: .prettierignore ================================================ .DS_Store node_modules npm-debug.log dist */package-lock.json yarn.lock .vscode .idea test/ts/**/*.js coverage *.sw[op] *.log package/ preact-*.tgz preact.tgz package-lock.json ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing This document is intended for developers interested in making contributions to Preact and documents our internal processes like releasing a new version. ## Getting Started These steps will help you set up your development environment. That includes all dependencies we use to build Preact and developer tooling like git commit hooks. 1. Clone the git repository: `git clone git@github.com:preactjs/preact.git` 2. Go into the cloned folder: `cd preact/` 3. Install all dependencies: `npm install` ## The Repo Structure This repository contains Preact itself, as well as several addons like the debugging package for example. This is reflected in the directory structure of this repository. Each package has a `src/` folder where the source code can be found, a `test` folder for all sorts of tests that check if the code in `src/` is correct, and a `dist/` folder where you can find the bundled artifacts. Note that the `dist/` folder may not be present initially. It will be created as soon as you run any of the build scripts inside `package.json`. More on that later ;) A quick overview of our repository: ```bash # The repo root (folder where you cloned the repo into) / src/ # Source code of our core test/ # Unit tests for core dist/ # Build artifacts for publishing on npm (may not be present) # Sub-package, can be imported via `preact/compat` by users. # Compat stands for react-compatibility layer which tries to mirror the # react API as close as possible (mostly legacy APIs) compat/ src/ # Source code of the compat addon test/ # Tests related to the compat addon dist/ # Build artifacts for publishing on npm (may not be present) # Sub-package, can be imported via `preact/hooks` by users. # The hooks API is an effect based API to deal with component lifecycles. # It's similar to hooks in React hooks/ src/ # Source code of the hooks addon test/ # Tests related to the hooks addon dist/ # Build artifacts for publishing on npm (may not be present) # Sub-package, can be imported via `preact/debug` by users. # Includes debugging warnings and error messages for common mistakes found # in Preact applications. Also hosts the devtools bridge debug/ src/ # Source code of the debug addon test/ # Tests related to the debug addon dist/ # Build artifacts for publishing on npm (may not be present) # Sub-package, can be imported via `preact/test-utils` by users. # Provides helpers to make testing Preact applications easier test-utils/ src/ # Source code of the test-utils addon test/ # Tests related to the test-utils addon dist/ # Build artifacts for publishing on npm (may not be present) # A demo application that we use to debug tricky errors and play with new # features. demo/ # Contains build scripts and dependencies for development package.json ``` _Note: The code for rendering Preact on the server lives in another repo and is a completely separate npm package. It can be found here: [https://github.com/preactjs/preact-render-to-string](https://github.com/preactjs/preact-render-to-string)_ ### What does `mangle.json` do? It's a special file that can be used to specify how `terser` (previously known as `uglify`) will minify variable names. Because each sub-package has its own distribution files we need to ensure that the variable names stay consistent across bundles. ## What does `options.js` do? Unique to Preact we do support several ways to hook into our renderer. All our addons use that to inject code at different stages of a render process. They are documented in our typings in `internal.d.ts`. The core itself doesn't make use of them, which is why the file only contains an empty `object`. ## Important Branches We have a couple of important branches to be aware of: - `main` - This is the main development branch and represents the upcoming v11 release line. - `v10.x` - This branch represents the current stable release line, v10. As we have yet to release v11, contributors are welcome to use either branch to build upon. We will try to port changes between the branches when possible, to keep them in sync, but if you're feeling generous, we'd love if you'd submit PRs to both branches! ## Creating your first Pull-Request We try to make it as easy as possible to contribute to Preact and make heavy use of GitHub's "Draft PR" feature which tags Pull-Requests (short = PR) as work in progress. PRs tend to be published as soon as there is an idea that the developer deems worthwhile to include into Preact and has written some rough code. The PR doesn't have to be perfect or anything really ;) Once a PR or a Draft PR has been created our community typically joins the discussion about the proposed change. Sometimes that includes ideas for test cases or even different ways to go about implementing a feature. Often this also includes ideas on how to make the code smaller. We usually refer to the latter as "code-golfing" or just "golfing". When everything is good to go someone will approve the PR and the changes will be merged into the `main` or `v10.x` branches and we usually cut a release a few days/ a week later. _The big takeaway for you here is, that we will guide you along the way. We're here to help to make a PR ready for approval!_ The short summary is: 1. Make changes and submit a PR 2. Modify change according to feedback (if there is any) 3. PR will be merged into `main` or `v10.x` 4. A new release will be cut (every 2-3 weeks). ## Commonly used scripts for contributions Scripts can be executed via `npm run [script]`. - `build` - compiles all packages ready for publishing to npm - `build:core` - builds just Preact itself - `build:debug` - builds the debug addon only - `build:devtools` - builds the devtools addon only - `build:hooks` - builds the hook addon only - `build:test-utils` - builds the test-utils addon only - `build:compat` - builds the compat addon only - `build:jsx` - builds the JSX runtime addon only - `test` - Run all tests (linting, TypeScript definitions, unit/integration tests) - `test:ts` - Run all tests for TypeScript definitions - `test:vitest` - Run all unit/integration tests. - `test:vitest:watch` - Same as above, but it will automatically re-run the test suite if a code change was detected. But to be fair, the ones we mostly use locally are `build` and `test:vitest:watch`. The other ones are mainly used on our CI pipeline. _Note: Both `test:vitest` and `test:vitest:watch` listen to the environment variable `COVERAGE=true`. Disabling code coverage can significantly speed up the time it takes to complete the test suite._ _Note2: Individual tests can be executed by appending `.only`:_ ```jsx it.only('should test something', () => { expect(1).to.equal(1); }); ``` ## Common terminology and variable names - `vnode` -> shorthand for `virtual-node` which is an object that specifies how a Component or DOM-node looks like - `commit` -> A commit is the moment in time when you flush all changes to the DOM - `c` -> The variable `c` always refers to a `component` instance throughout our code base. - `diff/diffing` -> Diffing describes the process of comparing two "things". In our case we compare the previous `vnode` tree with the new one and apply the delta to the DOM. - `root` -> The topmost node of a `vnode` tree ## Tips for getting to know the code base - Check the JSDoc block right above the function definition to understand what it does. It contains a short description of each function argument and what it does. - Check the callsites of a function to understand how it's used. Modern editors/IDEs allow you to quickly find those, or use the plain old search feature instead. ## Benchmarks We have a benchmark suite that we use to measure the performance of Preact. Our benchmark suite lives in our [preactjs/benchmarks repository](https://github.com/preactjs/benchmarks), but is included here as Git submodule. To run the benchmarks, first ensure [PNPM](https://pnpm.io/installation) is installed on your system and initialize and setup the submodule (it uses `pnpm` as a package manager): ```bash pnpm -v # Make sure pnpm is installed git submodule update --init --recursive cd benchmarks pnpm i ``` Then you can run the benchmarks: ```bash # In the benchmarks folder pnpm run bench ``` Checkout the README in the benchmarks folder for more information on running benchmarks. > **Note:** When switching branches, git submodules are not automatically updated to the commit of the new branch - it stays at the commit of the previous branch. This can be a feature! It allows you to work in different branches with the latest versions of the benchmarks - especially if you have made changes to the benchmarks. > > However if you want to switch branches and also update the benchmarks to the latest commit of the new branch, you can run `git submodule update --recursive` after switching branches, or run `git checkout --recurse-submodules` when checking out a new branch. ## FAQ ### Why does the JSDoc use TypeScript syntax to specify types? Several members of the team are very fond of TypeScript and we wanted to leverage as many of its advantages, like improved autocompletion, for Preact. We even attempted to port Preact to TypeScript a few times, but we ran into many issues with the DOM typings. Those would force us to fill our codebase with many `any` castings, making our code very noisy. Luckily TypeScript has a mode where it can somewhat reliably typecheck JavaScript code by reusing the types defined in JSDoc blocks. It's not perfect and it often has trouble inferring the correct types the further one strays away from the function arguments, but it's good enough that it helps us a lot with autocompletion. Another plus is that we can make sure that our TypeScript definitions are correct at the same time. Check out the [official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) for more information. _Note that we have separate tests for our TypeScript definition files. We only use `ts-check` for local development and don't check it anywhere else like on the CI._ ## How to create a good bug report To be able to fix issues we need to see them on our machine. This is only possible when we can reproduce the error. The easiest way to do that is narrow down the problem to specific components or combination of them. This can be done by removing as much unrelated code as possible. The perfect way to do that is to make a [codesandbox](https://codesandbox.io/). That way you can easily share the problematic code and ensure that others can see the same issue you are seeing. For us a [codesandbox](https://codesandbox.io/) says more than a 1000 words :tada: ## I have more questions on how to contribute to Preact. How can I reach you? We closely watch our issues and have a pretty active [Slack workspace](https://chat.preactjs.com/). Nearly all our communication happens via these two forms of communication. ## Releasing Preact (Maintainers only) This guide is intended for core team members that have the necessary rights to publish new releases on npm. 1. Make a PR where **only** the version number is incremented in `package.json` and everywhere else. A simple search and replace works. (note: We follow `SemVer` conventions) 2. Wait until the PR is approved and merged. 3. Switch back to the `main` branch and pull the merged PR 4. Create and push a tag for the new version you want to publish: 1. `git tag 10.0.0` 2. `git push --tags` 5. Wait for the Release workflow to complete - It'll create a draft release and upload the built npm package as an asset to the release 6. [Fill in the release notes](#writing-release-notes) in GitHub and publish them 7. Run the publish script with the tag you created 1. `node ./scripts/release/publish.mjs 10.0.0` 2. Make sure you have 2FA enabled in npm, otherwise the above command will fail. 3. If you're doing a pre-release add `--npm-tag next` to the `publish.mjs` command to publish it under a different tag (default is `latest`) 8. Tweet it out ## Legacy Releases (8.x) > **ATTENTION:** Make sure that you've cleared the project correctly > when switching from a 10.x branch. 0. Run `rm -rf dist node_modules && npm i` to make sure to have the correct dependencies. 1. [Write the release notes](#writing-release-notes) and keep them as a draft in GitHub 1. I'd recommend writing them in an offline editor because each edit to a draft will change the URL in GitHub. 2. Make a PR where **only** the version number is incremented in `package.json` (note: We follow `SemVer` conventions) 3. Wait until the PR is approved and merged. 4. Switch back to the `main` branch and pull the merged PR 5. Run `npm run build && npm publish` 1. Make sure you have 2FA enabled in npm, otherwise the above command will fail. 2. If you're doing a pre-release add `--tag next` to the `npm publish` command to publish it under a different tag (default is `latest`) 6. Publish the release notes and create the correct git tag. 7. Tweet it out ## Writing release notes The release notes have become a sort of tiny blog post about what's happening in preact-land. The title usually has this format: ```txt Version Name ``` Example: ```txt 10.0.0-beta.1 Los Compresseros ``` The name is optional, we just have fun finding creative names :wink: To keep them interesting we try to be as concise as possible and to just reflect where we are. There are some rules we follow while writing them: - Be nice, use a positive tone. Avoid negative words - Show, don't just tell. - Be honest. - Don't write too much, keep it simple and short. - Avoid making promises and don't overpromise. That leads to unhappy users - Avoid framework comparisons if possible - Highlight awesome community contributions (or great issue reports) - If in doubt, praise the users. After this section we typically follow with a changelog part that's divided into 4 groups in order of importance for the user: - Features - Bug Fixes - Typings - Maintenance We generate it via this handy cli program: [changelogged](https://github.com/marvinhagemeister/changelogged). It will collect and format the descriptions of all PRs that have been merged between two tags. The usual command is `changelogged 10.0.0-rc.2..HEAD` similar to how you'd diff two points in time with git. This will get you 90% there, but you still need to divide it into groups. It's also a good idea to unify the formatting of the descriptions, so that they're easier to read and don't look like a mess. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-present Jason Miller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ > [!NOTE] > This is the branch for the upcoming release, for patches to v10 you need the [v10.x branch](https://github.com/preactjs/preact/tree/v10.x)
Fast 4kB alternative to React with the same modern API.
**All the power of Virtual DOM components, without the overhead:** - Familiar React API & patterns: ES6 Class, hooks, and Functional Components - Extensive React compatibility via a simple [preact/compat] alias - Everything you need: JSX, VDOM, [DevTools], HMR, SSR. - Highly optimized diff algorithm and seamless hydration from Server Side Rendering - Supports all modern browsers - Transparent asynchronous rendering with a pluggable scheduler ### 💁 More information at the [Preact Website ➞](https://preactjs.com)| [](https://www.npmjs.com/package/preact) [](https://chat.preactjs.com) [](#backers) [](#sponsors) [](https://coveralls.io/github/preactjs/preact) [](https://unpkg.com/preact/dist/preact.min.js) [](https://unpkg.com/preact/dist/preact.min.js) |
Do you agree to the statement: "Preact is awesome"?
setInput(e.target.value)} />
{ ( props: _preact.RenderableProps
, context?: any ): _preact.ComponentChildren; displayName?: string; defaultProps?: Partial
| undefined; } export interface ComponentClass
{ new (props: P, context?: any): _preact.Component
; displayName?: string; defaultProps?: Partial
;
contextType?: _preact.Context ,
state: Readonly {
componentWillMount?(): void;
componentDidMount?(): void;
componentWillUnmount?(): void;
getChildContext?(): object;
componentWillReceiveProps?(nextProps: Readonly , nextContext: any): void;
shouldComponentUpdate?(
nextProps: Readonly ,
nextState: Readonly ,
nextState: Readonly , oldState: Readonly ,
previousState: Readonly {
constructor(props?: P, context?: any);
static displayName?: string;
static defaultProps?: any;
static contextType?: _preact.Context
): Partial | null;
getDerivedStateFromError?(error: any): Partial | null;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export interface Component,
nextContext: any
): boolean;
componentWillUpdate?(
nextProps: Readonly,
nextContext: any
): void;
getSnapshotBeforeUpdate?(oldProps: Readonly): any;
componentDidUpdate?(
previousProps: Readonly,
snapshot: any
): void;
componentDidCatch?(error: any, errorInfo: _preact.ErrorInfo): void;
}
export abstract class Component
Timeline details
); }) .then(() => { rerender(); expect(scratch.innerHTML).to.equal( 'Timeline details
' ); }); }); it('should correctly render Suspense components inside Fragments', () => { // Issue #2106. const [Lazy1, resolve1] = createLazy(); const [Lazy2, resolve2] = createLazy(); const [Lazy3, resolve3] = createLazy(); const Loading = () =>{state}
; }); const suspense = (hello world
'); const [resolve] = suspend(); rerender(); expect(scratch.innerHTML).to.equal(`hello new world
).then(() => { rerender(); expect(scratch.innerHTML).to.eql(`hello new world
`); }); }); it('should not crash when suspended child updates after unmount', () => { let childInstance = null; const neverResolvingPromise = new Promise(() => {}); class ThrowingChild extends Component { constructor(props) { super(props); this.state = { suspend: false, value: 0 }; childInstance = this; } render(props, state) { if (state.suspend) { throw neverResolvingPromise; } returnHello world
; let set; const App = () => { const [show, setShow] = useState(true); set = setShow; return show ? (Hello world
'); childInstance.setState({ value: 1 }); rerender(); expect(scratch.innerHTML).to.equal('Hello world
'); }); it('should not crash when suspended child resolves after unmount', async () => { let childInstance = null, res; const neverResolvingPromise = new Promise(r => { res = r; }); class ThrowingChild extends Component { constructor(props) { super(props); this.state = { suspend: false, value: 0 }; childInstance = this; } render(props, state) { if (state.suspend) { throw neverResolvingPromise; } returnHello world
; let set; const App = () => { const [show, setShow] = useState(true); set = setShow; return show ? (Hello world
'); res(); return neverResolvingPromise.then(() => { rerender(); expect(scratch.innerHTML).to.equal('Hello world
'); }); }); it('should not crash when suspense promise resolves after unmount', () => { let resolve; const promise = new Promise(r => { resolve = r; }); class ThrowingChild extends Component { render() { throw promise; } } render(hello world
).then(() => { rerender(); expect(scratch.innerHTML).to.equal('hello world
{value}
; }; act(() => { render(hello world
'); expect(subscribe).toHaveBeenCalledOnce(); expect(getSnapshot).toHaveBeenCalledTimes(3); }); it('subscribes and rerenders when called', () => { /** @type {() => void} */ let flush; const subscribe = vi.fn(cb => { flush = cb; return () => {}; }); let called = false; const getSnapshot = vi.fn(() => { if (called) { return 'hello new world'; } return 'hello world'; }); const App = () => { const value = useSyncExternalStore(subscribe, getSnapshot); return{value}
; }; act(() => { render(hello world
'); expect(subscribe).toHaveBeenCalledOnce(); expect(getSnapshot).toHaveBeenCalledTimes(3); called = true; flush(); rerender(); expect(scratch.innerHTML).to.equal('hello new world
'); }); it('getSnapshot can return NaN without causing infinite loop', () => { /** @type {() => void} */ let flush; const subscribe = vi.fn(cb => { flush = cb; return () => {}; }); let called = false; const getSnapshot = vi.fn(() => { if (called) { return NaN; } return 1; }); const App = () => { const value = useSyncExternalStore(subscribe, getSnapshot); return{value}
; }; act(() => { render(1
'); expect(subscribe).toHaveBeenCalledOnce(); expect(getSnapshot).toHaveBeenCalledTimes(3); called = true; flush(); rerender(); expect(scratch.innerHTML).to.equal('NaN
'); }); it('should not call function values on subscription', () => { /** @type {() => void} */ let flush; const subscribe = vi.fn(cb => { flush = cb; return () => {}; }); const func = () => 'value: ' + i++; let i = 0; const getSnapshot = vi.fn(() => { return func; }); const App = () => { const value = useSyncExternalStore(subscribe, getSnapshot); return{value()}
; }; act(() => { render(value: 0
'); expect(subscribe).toHaveBeenCalledOnce(); expect(getSnapshot).toHaveBeenCalledTimes(3); flush(); rerender(); expect(scratch.innerHTML).to.equal('value: 0
'); }); it('should work with changing getSnapshot', () => { /** @type {() => void} */ let flush; const subscribe = vi.fn(cb => { flush = cb; return () => {}; }); let i = 0; const App = () => { const value = useSyncExternalStore(subscribe, () => { return i; }); returnvalue: {value}
; }; act(() => { render(value: 0
'); expect(subscribe).toHaveBeenCalledOnce(); i++; flush(); rerender(); expect(scratch.innerHTML).to.equal('value: 1
'); }); it('works with useCallback', () => { /** @type {() => void} */ let toggle; const App = () => { const [state, setState] = useState(true); toggle = setState.bind(this, () => false); const value = useSyncExternalStore( useCallback(() => { return () => {}; }, [state]), () => (state ? 'yep' : 'nope') ); return{value}
; }; act(() => { render(yep
'); toggle(); rerender(); expect(scratch.innerHTML).to.equal('nope
'); }); it('handles store updates before subscribing', async () => { // This test is testing scheduling mechanics, so teardown the manual // rerender test setup to rely on Preact's built-in scheduling and verify // this behavior works. We still need a DOM container to render into so set // that back up. teardown(scratch); scratch = setupScratch(); const store = createExternalStore(0); function App() { const value = useSyncExternalStore(store.subscribe, store.getState); useEffect(() => { Scheduler.log('Passive effect: ' + value); }, [value]); returnHello world
; const importComponent = async () => { return { MyComponent: Comp }; }; const Lazy = React.lazy(() => importComponent().then(mod => ({ default: mod.MyComponent })) ); // eslint-disable-next-line function App() { return