├── .changeset ├── README.md └── config.json ├── .github ├── CODE_OF_CONDUCT.md ├── assets │ ├── airbnb-logo.png │ ├── demo.gif │ ├── faire-logo.svg │ ├── github.mp4 │ ├── instagram.mp4 │ ├── logo.svg │ ├── perplexity-logo.png │ ├── shopify-logo.png │ └── twitter.mp4 └── workflows │ ├── build-extension.yml │ └── pkg-pr-new.yaml ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── BROWSER_EXTENSION_GUIDE.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin ├── generate-certs.sh └── serve-scan.sh ├── biome.json ├── docs └── installation │ ├── astro.md │ ├── cdn.md │ ├── create-react-app.md │ ├── next-js-app-router.md │ ├── next-js-page-router.md │ ├── parcel.md │ ├── react-router.md │ ├── remix.md │ ├── rsbuild.md │ ├── tanstack-start.md │ └── vite.md ├── kitchen-sink ├── index.html ├── package.json ├── postcss.config.mjs ├── src │ ├── examples │ │ ├── sierpinski │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── todo-list │ │ │ ├── index.tsx │ │ │ └── styles.css │ ├── index.css │ ├── index.tsx │ ├── main.css │ └── main.tsx ├── tailwind.config.mjs ├── tsconfig.json └── vite.config.ts ├── package.json ├── packages ├── extension │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── build │ │ ├── brave-extension-v1.0.9.zip │ │ ├── chrome-extension-v1.0.9.zip │ │ └── firefox-extension-v1.0.9.zip │ ├── package.json │ ├── public │ │ └── icons │ │ │ ├── disabled │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ └── 48.png │ │ │ └── enabled │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ └── 48.png │ ├── src │ │ ├── assets │ │ │ └── css │ │ │ │ └── no-react.css │ │ ├── background │ │ │ ├── icon.ts │ │ │ └── index.ts │ │ ├── content │ │ │ └── index.ts │ │ ├── inject │ │ │ ├── index.ts │ │ │ ├── notification.ts │ │ │ └── react-scan.ts │ │ ├── manifest.chrome.json │ │ ├── manifest.firefox.json │ │ ├── types │ │ │ ├── global.d.ts │ │ │ └── messages.ts │ │ ├── utils │ │ │ ├── constants.ts │ │ │ └── helpers.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── scan │ ├── .gitignore │ ├── README.md │ ├── auto.d.ts │ ├── bin │ │ └── cli.js │ ├── global.d.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── postcss.rem2px.mjs │ ├── scripts │ │ └── bump-version.js │ ├── src │ │ ├── auto.ts │ │ ├── cli.mts │ │ ├── core │ │ │ ├── fast-serialize.test.ts │ │ │ ├── index.ts │ │ │ ├── instrumentation.ts │ │ │ ├── monitor │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── network.ts │ │ │ │ ├── params │ │ │ │ │ ├── astro │ │ │ │ │ │ ├── Monitoring.astro │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── next.ts │ │ │ │ │ ├── react-router-v5.ts │ │ │ │ │ ├── react-router-v6.ts │ │ │ │ │ ├── remix.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── performance.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── notifications │ │ │ │ ├── event-tracking.ts │ │ │ │ ├── interaction-store.ts │ │ │ │ ├── outline-overlay.ts │ │ │ │ ├── performance-store.ts │ │ │ │ ├── performance-utils.ts │ │ │ │ ├── performance.ts │ │ │ │ └── types.ts │ │ │ └── utils.ts │ │ ├── index.ts │ │ ├── install-hook.ts │ │ ├── monitoring │ │ │ └── next.ts │ │ ├── new-outlines │ │ │ ├── canvas.ts │ │ │ ├── index.ts │ │ │ ├── offscreen-canvas.worker.ts │ │ │ └── types.ts │ │ ├── polyfills.ts │ │ ├── react-component-name │ │ │ ├── __tests__ │ │ │ │ ├── arrow-function.test.ts │ │ │ │ ├── complex-patterns.test.ts │ │ │ │ ├── function-declarations.test.ts │ │ │ │ ├── general-cases.test.ts │ │ │ │ ├── react-patterns.test.ts │ │ │ │ ├── ts-patterns.test.ts │ │ │ │ └── utils.ts │ │ │ ├── astro.ts │ │ │ ├── babel │ │ │ │ ├── get-descriptive-name.ts │ │ │ │ ├── get-root-statement-path.ts │ │ │ │ ├── index.ts │ │ │ │ ├── is-componentish-name.ts │ │ │ │ ├── is-nested-expression.ts │ │ │ │ ├── is-path-valid.ts │ │ │ │ ├── is-statement-top-level.ts │ │ │ │ ├── path-references-import.ts │ │ │ │ └── unwrap.ts │ │ │ ├── core │ │ │ │ └── options.ts │ │ │ ├── esbuild.ts │ │ │ ├── index.ts │ │ │ ├── loader.ts │ │ │ ├── rolldown.ts │ │ │ ├── rollup.ts │ │ │ ├── rspack.ts │ │ │ ├── tsconfig.json │ │ │ ├── vite.ts │ │ │ └── webpack.ts │ │ ├── types.d.ts │ │ ├── types.ts │ │ ├── web │ │ │ ├── assets │ │ │ │ └── css │ │ │ │ │ └── styles.tailwind.css │ │ │ ├── components │ │ │ │ ├── copy-to-clipboard │ │ │ │ │ └── index.tsx │ │ │ │ ├── icon │ │ │ │ │ └── index.tsx │ │ │ │ ├── slider │ │ │ │ │ └── index.tsx │ │ │ │ ├── sticky-section │ │ │ │ │ └── index.tsx │ │ │ │ ├── svg-sprite │ │ │ │ │ └── index.tsx │ │ │ │ └── toggle │ │ │ │ │ └── index.tsx │ │ │ ├── constants.ts │ │ │ ├── hooks │ │ │ │ ├── use-delayed-value.ts │ │ │ │ ├── use-merged-refs.ts │ │ │ │ └── use-virtual-list.ts │ │ │ ├── state.ts │ │ │ ├── toolbar.tsx │ │ │ ├── utils │ │ │ │ ├── constants.ts │ │ │ │ ├── create-store.ts │ │ │ │ ├── geiger.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── lerp.ts │ │ │ │ ├── log.ts │ │ │ │ ├── lru.ts │ │ │ │ ├── outline.ts │ │ │ │ ├── pin.ts │ │ │ │ └── preact │ │ │ │ │ ├── constant.ts │ │ │ │ │ ├── use-constant.ts │ │ │ │ │ └── use-lazy-ref.ts │ │ │ ├── views │ │ │ │ ├── index.tsx │ │ │ │ ├── inspector │ │ │ │ │ ├── components-tree │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── state.ts │ │ │ │ │ ├── diff-value.tsx │ │ │ │ │ ├── flash-overlay.ts │ │ │ │ │ ├── header.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── logging.ts │ │ │ │ │ ├── overlay │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── properties.tsx │ │ │ │ │ ├── states.ts │ │ │ │ │ ├── timeline │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ ├── what-changed.tsx │ │ │ │ │ └── whats-changed │ │ │ │ │ │ └── use-change-store.ts │ │ │ │ ├── notifications │ │ │ │ │ ├── collapsed-event.tsx │ │ │ │ │ ├── data.ts │ │ │ │ │ ├── details-routes.tsx │ │ │ │ │ ├── icons.tsx │ │ │ │ │ ├── notification-header.tsx │ │ │ │ │ ├── notification-tabs.tsx │ │ │ │ │ ├── notifications.tsx │ │ │ │ │ ├── optimize.tsx │ │ │ │ │ ├── other-visualization.tsx │ │ │ │ │ ├── popover.tsx │ │ │ │ │ ├── render-bar-chart.tsx │ │ │ │ │ ├── render-explanation.tsx │ │ │ │ │ └── slowdown-history.tsx │ │ │ │ ├── settings │ │ │ │ │ └── header.tsx │ │ │ │ └── toolbar │ │ │ │ │ └── index.tsx │ │ │ └── widget │ │ │ │ ├── fps-meter.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── resize-handle.tsx │ │ │ │ └── types.ts │ │ └── worker-shim.ts │ ├── tailwind.config.mjs │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── vite.config.mts │ └── worker-plugin.ts ├── vite-plugin-react-scan │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── global.d.ts │ │ └── index.ts │ └── tsconfig.json └── website │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ ├── api │ │ └── waitlist │ │ │ └── route.ts │ ├── favicon.ico │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ ├── globals.css │ ├── layout.tsx │ ├── monitoring │ │ ├── (components) │ │ │ └── waitlist.tsx │ │ └── page.tsx │ ├── page.tsx │ ├── react-scan.ts │ └── replay │ │ └── page.tsx │ ├── components │ ├── cli.tsx │ ├── code.tsx │ ├── companies.tsx │ ├── counter.tsx │ ├── footer.tsx │ ├── header.tsx │ ├── install-guide.tsx │ ├── test-data-types.tsx │ └── todo-demo.tsx │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ ├── after.png │ ├── airbnb-logo.png │ ├── banner.png │ ├── discord.svg │ ├── faire-logo.svg │ ├── logo.svg │ ├── monitoring.png │ ├── perplexity-logo.png │ ├── player-video.mp4 │ ├── profiler.svg │ ├── shopify-logo.png │ └── thumbnail.png │ ├── tailwind.config.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── build-worker.ts ├── bump-version.js ├── version-warning.mjs └── workspace.mjs └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [["react-scan", "@react-scan/extension"]], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at aiden.bai05@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | For answers to common questions about this code of conduct, see 74 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /.github/assets/airbnb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/airbnb-logo.png -------------------------------------------------------------------------------- /.github/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/demo.gif -------------------------------------------------------------------------------- /.github/assets/github.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/github.mp4 -------------------------------------------------------------------------------- /.github/assets/instagram.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/instagram.mp4 -------------------------------------------------------------------------------- /.github/assets/perplexity-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/perplexity-logo.png -------------------------------------------------------------------------------- /.github/assets/shopify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/shopify-logo.png -------------------------------------------------------------------------------- /.github/assets/twitter.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidenybai/react-scan/6df072adeec70117b62bddf02981113596e0b99a/.github/assets/twitter.mp4 -------------------------------------------------------------------------------- /.github/workflows/build-extension.yml: -------------------------------------------------------------------------------- 1 | name: Build Extension 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'packages/extension/**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | ref: main 21 | 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: '20' 26 | 27 | - name: Setup PNPM 28 | uses: pnpm/action-setup@v2 29 | with: 30 | version: 9.x 31 | 32 | - name: Install dependencies 33 | run: pnpm install 34 | 35 | - name: Get package info 36 | id: package 37 | run: | 38 | echo "name=$(node -p "require('./packages/extension/package.json').name")" >> $GITHUB_OUTPUT 39 | echo "version=$(node -p "require('./packages/extension/package.json').version")" >> $GITHUB_OUTPUT 40 | 41 | - name: Build extensions 42 | run: | 43 | pnpm build 44 | cd packages/extension 45 | rm -rf build 46 | pnpm pack:all 47 | 48 | - name: Commit changes 49 | if: github.ref == 'refs/heads/main' 50 | run: | 51 | git checkout main 52 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 53 | git config --local user.name "github-actions[bot]" 54 | git add -f packages/extension/build/*.zip 55 | git diff --staged --quiet || (git commit -m "chore: update extension builds [skip ci]" && git push) 56 | -------------------------------------------------------------------------------- /.github/workflows/pkg-pr-new.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Any Commit 2 | on: 3 | push: 4 | branches: 5 | - "**" 6 | pull_request: 7 | branches: 8 | - "**" 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [18] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v4 22 | with: 23 | version: 9.1.0 24 | 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: "pnpm" 30 | 31 | - name: Install dependencies 32 | run: pnpm install --frozen-lockfile --strict-peer-dependencies=false 33 | 34 | - name: Build 35 | run: | 36 | cd packages/scan 37 | NODE_ENV=production pnpm build 38 | env: 39 | NODE_ENV: production 40 | 41 | - name: Publish NPM Package to pkg-pr-new 42 | run: pnpx pkg-pr-new publish ./packages/scan 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .env 4 | dist 5 | **/*.tgz 6 | *.log 7 | build 8 | !packages/extension/build/ 9 | playgrounds 10 | # SSL Certificates 11 | bin/certs/*.pem 12 | bin/certs/*.key 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | prefer-workspace-packages=true 2 | link-workspace-packages=true -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports.biome": "always", 6 | "quickfix.biome": "always" 7 | }, 8 | "css.lint.unknownAtRules": "ignore", 9 | "[typescript]": { 10 | "editor.defaultFormatter": "biomejs.biome" 11 | }, 12 | "[javascript]": { 13 | "editor.defaultFormatter": "biomejs.biome" 14 | }, 15 | "[json]": { 16 | "editor.defaultFormatter": "biomejs.biome" 17 | }, 18 | "[markdown]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | }, 21 | "[html]": { 22 | "editor.defaultFormatter": "esbenp.prettier-vscode" 23 | }, 24 | "typescript.tsdk": "node_modules/typescript/lib", 25 | "[css]": { 26 | "editor.defaultFormatter": "biomejs.biome" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BROWSER_EXTENSION_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Browser Extension Installation Guide 2 | 3 | > [!NOTE] 4 | > The React Scan browser extension currently uses `react-scan@0.2.9` 5 | 6 | 7 | ## Chrome 8 | > You can download the Chrome browser extension from the [Chrome Web Store](https://chromewebstore.google.com/detail/react-scan/anmmhkomejbdklkhoiloeaehppaffmdf). Below is a guide to installing the extension manually. 9 | 10 | 1. Download the [`chrome-extension-v1.0.8.zip`](https://github.com/aidenybai/react-scan/tree/main/packages/extension/build) file. 11 | 2. Unzip the file. 12 | 3. Open Chrome and navigate to `chrome://extensions/`. 13 | 4. Enable "Developer mode" if it is not already enabled. 14 | 5. Click "Load unpacked" and select the unzipped folder (or drag the folder into the page). 15 | 16 | ## Firefox 17 | > React Scan's Browser extension is still pending approvals from Firefox Add-ons. Below is a guide to installing the extension manually. 18 | 1. Download the [`firefox-extension-v1.0.8.zip`](https://github.com/aidenybai/react-scan/tree/main/packages/extension/build) file. 19 | 2. Unzip the file. 20 | 3. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`. 21 | 4. Click "Load Temporary Add-on..." 22 | 5. Select `manifest.json` from the unzipped folder 23 | 24 | ## Brave 25 | > React Scan's Browser extension is still pending approvals from Brave Browser. Below is a guide to installing the extension manually. 26 | 27 | 1. Download the [`brave-extension-v1.0.8.zip`](https://github.com/aidenybai/react-scan/tree/main/packages/extension/build) file. 28 | 2. Unzip the file. 29 | 3. Open Brave and navigate to `brave://extensions`. 30 | 4. Click "Load unpacked" and select the unzipped folder (or drag the folder into the page). 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to React Scan 2 | 3 | First off, thanks for taking the time to contribute! ❤️ 4 | 5 | ## Table of Contents 6 | 7 | - [Contributing to React Scan](#contributing-to-react-scan) 8 | - [Table of Contents](#table-of-contents) 9 | - [Project Structure](#project-structure) 10 | - [Development Setup](#development-setup) 11 | - [Contributing Guidelines](#contributing-guidelines) 12 | - [Commits](#commits) 13 | - [Pull Request Process](#pull-request-process) 14 | - [Development Workflow](#development-workflow) 15 | - [Getting Help](#getting-help) 16 | 17 | ## Project Structure 18 | 19 | This is a monorepo containing several packages: 20 | 21 | - `packages/scan` - Core React Scan package 22 | - `packages/vite-plugin-react-scan` - Vite plugin for React Scan 23 | - `packages/extension` - VS Code extension 24 | 25 | ## Development Setup 26 | 27 | 1. **Clone and Install** 28 | ```bash 29 | git clone https://github.com/aidenybai/react-scan.git 30 | cd react-scan 31 | pnpm install 32 | ``` 33 | 34 | 2. **Build all packages** 35 | ```bash 36 | pnpm build 37 | ``` 38 | 39 | 3. **Testing React Scan** 40 | ```bash 41 | cd packages/scan 42 | pnpm build:copy 43 | ``` 44 | - This will build the package and then copy it to your clipboard as an IIFE (immedietely invoked function expression). This will allow you to paste it into the browser console to test it on any website 45 | 46 | https://github.com/user-attachments/assets/f279e664-479f-4e39-bff4-1bbfee30af22 47 | 48 | ## Contributing Guidelines 49 | 50 | ### Commits 51 | 52 | We use conventional commits to ensure consistent commit messages: 53 | 54 | - `feat:` New features 55 | - `fix:` Bug fixes 56 | - `docs:` Documentation changes 57 | - `chore:` Maintenance tasks 58 | - `test:` Adding or updating tests 59 | - `refactor:` Code changes that neither fix bugs nor add features 60 | 61 | Example: `fix(scan): fix a typo` 62 | 63 | ### Pull Request Process 64 | 65 | 1. Fork the repository 66 | 2. Create your feature branch (`git checkout -b feat/amazing-feature`) 67 | 3. Commit your changes using conventional commits 68 | 4. Push to your branch 69 | 5. Open a Pull Request 70 | 6. Ask for reviews (@pivanov, @RobPruzan are your friends in this journey) 71 | 72 | ### Development Workflow 73 | 74 | 1. **TypeScript** 75 | - All code must be written in TypeScript 76 | - Ensure strict type checking passes 77 | - No `any` types unless absolutely necessary 78 | 79 | 2. **Code Style** 80 | - We use Biome for formatting and linting 81 | - Run `pnpm format` to format code 82 | - Run `pnpm lint` to check for issues 83 | 84 | 3. **Documentation** 85 | - Update relevant documentation 86 | - Add JSDoc comments for public APIs 87 | - Update README if needed 88 | 89 | ## Getting Help 90 | - Check existing issues 91 | - Create a new issue 92 | 93 |
94 | 95 | ⚛️ Happy coding! 🚀 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Aiden Bai, Million Software, Inc. 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /bin/generate-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p bin/certs 4 | openssl req -x509 -newkey rsa:2048 -keyout bin/certs/key.pem -out bin/certs/cert.pem -days 365 -nodes -subj "/CN=127.0.0.1" 5 | -------------------------------------------------------------------------------- /bin/serve-scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Determine the directory of the script 4 | SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) 5 | 6 | # Default values 7 | DEFAULT_PATH="./packages/scan/dist" 8 | DEFAULT_PORT="4000" 9 | DEFAULT_CERT="$SCRIPT_DIR/certs/cert.pem" 10 | DEFAULT_KEY="$SCRIPT_DIR/certs/key.pem" 11 | 12 | # Positional arguments 13 | SERVE_PATH="$1" # First argument is the path 14 | 15 | # Get optional flags 16 | shift # Remove the first argument from the list 17 | while [[ "$#" -gt 0 ]]; do 18 | case $1 in 19 | --port) PORT_ARG="$2"; shift ;; 20 | --cert) CERT_ARG="$2"; shift ;; 21 | --key) KEY_ARG="$2"; shift ;; 22 | *) echo "Unknown parameter: $1" >&2; exit 1 ;; 23 | esac 24 | shift 25 | done 26 | 27 | # Use provided arguments or defaults 28 | SERVE_PATH="${SERVE_PATH:-$DEFAULT_PATH}" 29 | SERVE_PORT="${PORT_ARG:-$DEFAULT_PORT}" 30 | SERVE_CERT="${CERT_ARG:-$DEFAULT_CERT}" 31 | SERVE_KEY="${KEY_ARG:-$DEFAULT_KEY}" 32 | 33 | # Run the server with CORS enabled 34 | http-server "$SERVE_PATH" -p "$SERVE_PORT" --ssl --cert "$SERVE_CERT" --key "$SERVE_KEY" --cors 35 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [ 11 | "**/dist/**", 12 | "**/build/**", 13 | "node_modules", 14 | "**/node_modules/**", 15 | "**/*.css", 16 | "**/*.astro", 17 | "packages/website" 18 | ] 19 | }, 20 | "organizeImports": { 21 | "enabled": false 22 | }, 23 | "linter": { 24 | "enabled": true, 25 | "rules": { 26 | "recommended": false, 27 | "correctness": { 28 | "noUnusedFunctionParameters": { 29 | "level": "warn", 30 | "fix": "unsafe" 31 | }, 32 | "noUnusedImports": { 33 | "level": "warn", 34 | "fix": "unsafe" 35 | }, 36 | "noUnusedLabels": { 37 | "level": "warn", 38 | "fix": "unsafe" 39 | }, 40 | "noUnusedPrivateClassMembers": { 41 | "level": "warn", 42 | "fix": "unsafe" 43 | }, 44 | "noUnusedVariables": { 45 | "level": "warn", 46 | "fix": "unsafe" 47 | }, 48 | "useExhaustiveDependencies": "warn" 49 | }, 50 | "suspicious": { 51 | "noExplicitAny": "warn", 52 | "noConsole": "warn" 53 | }, 54 | "security": { 55 | "noDangerouslySetInnerHtml": "error" 56 | }, 57 | "style": { 58 | "noNonNullAssertion": "warn" 59 | } 60 | } 61 | }, 62 | "formatter": { 63 | "enabled": true, 64 | "indentStyle": "space", 65 | "indentWidth": 2, 66 | "lineWidth": 80, 67 | "lineEnding": "lf" 68 | }, 69 | "javascript": { 70 | "formatter": { 71 | "quoteStyle": "single", 72 | "trailingCommas": "all" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /docs/installation/astro.md: -------------------------------------------------------------------------------- 1 | # Astro Guide 2 | 3 | ## As a script tag 4 | 5 | Add the script tag to your root layout. 6 | 7 | Refer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs. 8 | 9 | ```astro 10 | 11 | 12 | 13 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/installation/cdn.md: -------------------------------------------------------------------------------- 1 | # CDN 2 | 3 | You can choose one of the following URLs to initialize React Scan via CDN. 4 | 5 | ## Usage 6 | 7 | ```html 8 | 9 | ``` 10 | 11 | ## Available URLs 12 | 13 | ### JSDelivr 14 | 15 | ```txt 16 | https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js 17 | ``` 18 | 19 | ### UNPKG 20 | 21 | ```txt 22 | https://unpkg.com/react-scan/dist/auto.global.js 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/installation/create-react-app.md: -------------------------------------------------------------------------------- 1 | # Create React App (CRA) Guide 2 | 3 | ## As a script tag 4 | 5 | Add the script tag to your `index.html`. 6 | 7 | Refer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs. 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | ## As a module import 24 | 25 | In your project entrypoint (e.g. `src/index`, `src/main`): 26 | 27 | ```jsx 28 | // src/index.jsx 29 | 30 | // must be imported before React and React DOM 31 | import { scan } from "react-scan"; 32 | import React from "react"; 33 | 34 | scan({ 35 | enabled: true, 36 | }); 37 | ``` 38 | 39 | > [!CAUTION] 40 | > React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it. 41 | -------------------------------------------------------------------------------- /docs/installation/next-js-app-router.md: -------------------------------------------------------------------------------- 1 | # NextJS App Router Guide 2 | 3 | ## As a script tag 4 | 5 | Add the script tag to your `app/layout`. 6 | 7 | Refer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs. 8 | 9 | ```jsx 10 | // app/layout 11 | export default function RootLayout({ children }) { 12 | return ( 13 | 14 | 15 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/installation/react-router.md: -------------------------------------------------------------------------------- 1 | # React Router v7 Guide 2 | 3 | ## As a script tag 4 | 5 | Add the script tag to your `Layout` component in the `app/root`. 6 | 7 | Refer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs. 8 | 9 | ```jsx 10 | // app/root 11 | // ... 12 | export function Layout({ children }: { children: React.ReactNode }) { 13 | return ( 14 | 15 | 16 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | ## As a module import 43 | 44 | In your project entrypoint (e.g. `src/index`, `src/main`): 45 | 46 | ```jsx 47 | // src/index.jsx 48 | 49 | // must be imported before React and React DOM 50 | import { scan } from "react-scan"; 51 | import React from "react"; 52 | 53 | scan({ 54 | enabled: true, 55 | }); 56 | ``` 57 | 58 | > [!CAUTION] 59 | > React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it. 60 | -------------------------------------------------------------------------------- /docs/installation/tanstack-start.md: -------------------------------------------------------------------------------- 1 | # TanStack Router Guide 2 | 3 | ## As a script tag 4 | 5 | Add the script tag to your `` component at `app/routes/__root`. 6 | 7 | Refer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs. 8 | 9 | ```jsx 10 | // app/routes/__root 11 | import { Meta, Scripts } from "@tanstack/start"; 12 | // ... 13 | 14 | function RootDocument({ children }) { 15 | return ( 16 | 17 | 18 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | ## As a module import 24 | 25 | In your project entrypoint (e.g. `src/index`, `src/main`): 26 | 27 | ```jsx 28 | // src/index 29 | import { scan } from "react-scan"; // must be imported before React and React DOM 30 | import React from "react"; 31 | 32 | scan({ 33 | enabled: true, 34 | }); 35 | ``` 36 | 37 | > [!CAUTION] 38 | > React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it. 39 | 40 | ## Vite plugin 41 | 42 | TODO 43 | 44 | ## Preserving component names 45 | 46 | TODO 47 | -------------------------------------------------------------------------------- /kitchen-sink/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Scan 8 | 9 | 13 | 14 | 15 | 16 | 17 | 21 | 25 | 26 | 27 | 28 | 29 | 33 | 37 | 38 | 39 | 40 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /kitchen-sink/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-scan/kitchen-sink", 3 | "type": "module", 4 | "private": true, 5 | "publishConfig": { 6 | "access": "restricted" 7 | }, 8 | "scripts": { 9 | "dev": "vite", 10 | "build": "vite build", 11 | "serve": "vite preview" 12 | }, 13 | "dependencies": { 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-scan": "workspace:*", 17 | "tailwindcss": "^3.4.17" 18 | }, 19 | "devDependencies": { 20 | "@tailwindcss/postcss": "^4.0.0", 21 | "@types/react": "^19.0.8", 22 | "@types/react-dom": "^19.0.3", 23 | "@vitejs/plugin-react": "^4.3.4", 24 | "postcss": "^8.4.49", 25 | "typescript": "^5.7.3", 26 | "vite": "^6.0.11" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kitchen-sink/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /kitchen-sink/src/examples/sierpinski/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | font-size: 15px; 5 | line-height: 1.7; 6 | margin: 0; 7 | padding: 30px; 8 | } 9 | 10 | a { 11 | color: #4183c4; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | 19 | code { 20 | background-color: #f8f8f8; 21 | border: 1px solid #ddd; 22 | border-radius: 3px; 23 | font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; 24 | font-size: 12px; 25 | margin: 0 2px; 26 | padding: 0px 5px; 27 | } 28 | 29 | h1, h2, h3, h4 { 30 | font-weight: bold; 31 | margin: 0 0 15px; 32 | padding: 0; 33 | } 34 | 35 | h1 { 36 | border-bottom: 1px solid #ddd; 37 | font-size: 2.5em; 38 | font-weight: bold; 39 | margin: 0 0 15px; 40 | padding: 0; 41 | } 42 | 43 | h2 { 44 | border-bottom: 1px solid #eee; 45 | font-size: 2em; 46 | } 47 | 48 | h3 { 49 | font-size: 1.5em; 50 | } 51 | 52 | h4 { 53 | font-size: 1.2em; 54 | } 55 | 56 | p, ul { 57 | margin: 15px 0; 58 | } 59 | 60 | ul { 61 | padding-left: 30px; 62 | } 63 | 64 | .container { 65 | position: absolute; 66 | transform-origin: 0 0; 67 | left: 50%; 68 | top: 50%; 69 | width: 10px; 70 | height: 10px; 71 | background: #eee; 72 | } 73 | 74 | .dot { 75 | position: absolute; 76 | font: normal 15px sans-serif; 77 | text-align: center; 78 | cursor: pointer; 79 | } -------------------------------------------------------------------------------- /kitchen-sink/src/examples/todo-list/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { scan, Store } from 'react-scan'; 3 | import './styles.css'; 4 | 5 | 6 | Store.isInIframe.value = false; 7 | scan({ 8 | enabled: true, 9 | dangerouslyForceRunInProduction: true, 10 | }); 11 | 12 | interface TodoItem { 13 | id: number; 14 | message: string; 15 | done: boolean; 16 | } 17 | 18 | interface TodoListItemProps { 19 | setList: (action: (list: TodoItem[]) => TodoItem[]) => void; 20 | item: TodoItem; 21 | } 22 | 23 | function TodoListItem({ item, setList }: TodoListItemProps): JSX.Element { 24 | return ( 25 |
26 |
{item.message}
27 |
28 | 47 | 56 |
57 |
58 | ); 59 | } 60 | 61 | interface TodoListFormProps { 62 | index: number; 63 | setIndex: (update: number) => void; 64 | setList: (action: (list: TodoItem[]) => TodoItem[]) => void; 65 | } 66 | 67 | function TodoListForm({ 68 | setList, 69 | index, 70 | setIndex, 71 | }: TodoListFormProps): JSX.Element { 72 | const [message, setMessage] = useState(''); 73 | 74 | return ( 75 |
{ 78 | e.preventDefault(); 79 | 80 | setList(list => [ 81 | ...list, 82 | { 83 | done: false, 84 | message, 85 | id: index, 86 | }, 87 | ]); 88 | setIndex(index + 1); 89 | setMessage(''); 90 | }} 91 | > 92 | { 96 | setMessage((e.target as HTMLInputElement).value); 97 | }} 98 | /> 99 | 102 |
103 | ); 104 | } 105 | 106 | function TodoList(): JSX.Element { 107 | const [list, setList] = useState([]); 108 | const [index, setIndex] = useState(0); 109 | return ( 110 | <> 111 | 112 |
113 | {list.map(item => ( 114 | 115 | ))} 116 |
117 | 118 | ); 119 | } 120 | 121 | export default function App(): JSX.Element { 122 | return ( 123 |
124 |

Todo List

125 | 126 |
127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /kitchen-sink/src/examples/todo-list/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 3 | } 4 | 5 | button { 6 | margin: 0.5rem; 7 | padding: 0.5rem 1rem; 8 | border-radius: 0.5rem; 9 | outline: none; 10 | color: white; 11 | border-style: none; 12 | cursor: pointer; 13 | transition: background-color 200ms; 14 | } 15 | 16 | button:disabled { 17 | background-color: rgba(156, 163, 175, 255); 18 | color: rgba(229, 231, 235, 255); 19 | } 20 | 21 | .app { 22 | margin: 5%; 23 | } 24 | 25 | .todo-list-form { 26 | width: 100%; 27 | 28 | display: flex; 29 | align-items: center; 30 | justify-content: space-between; 31 | } 32 | 33 | .todo-list-form input { 34 | width: 100%; 35 | margin: 0.5rem; 36 | padding: 0.5rem; 37 | border-radius: 0.5rem; 38 | outline: none; 39 | border-width: 1px; 40 | border-color: black; 41 | border-style: solid; 42 | } 43 | 44 | .todo-list-form button:not(:disabled) { 45 | background-color: rgba(99, 102, 241, 255); 46 | } 47 | 48 | .todo-list-form button:not(:disabled):hover { 49 | background-color: rgba(67, 56, 202, 255); 50 | } 51 | 52 | .todo-list-form button:not(:disabled):active { 53 | background-color: rgba(79, 70, 229, 255); 54 | } 55 | 56 | .todo-item-toggle.pending { 57 | background-color: rgba(245, 158, 11, 255); 58 | } 59 | 60 | .todo-item-toggle.pending:hover { 61 | background-color: rgba(180, 83, 9, 255); 62 | } 63 | 64 | .todo-item-toggle.pending:active { 65 | background-color: rgba(217, 119, 6, 255); 66 | } 67 | 68 | .todo-item-toggle.complete { 69 | background-color: rgba(16, 185, 129, 255); 70 | } 71 | 72 | .todo-item-toggle.complete:hover { 73 | background-color: rgba(4, 120, 87, 255); 74 | } 75 | 76 | .todo-item-toggle.complete:active { 77 | background-color: rgba(5, 150, 105, 255); 78 | } 79 | 80 | .todo-item-delete:not(:disabled) { 81 | background-color: rgba(239, 68, 68, 255); 82 | } 83 | 84 | .todo-item-delete:not(:disabled):hover { 85 | background-color: rgba(185, 28, 28, 255); 86 | } 87 | 88 | .todo-item-delete:not(:disabled):active { 89 | background-color: rgba(220, 38, 38, 255); 90 | } 91 | 92 | .todo-item { 93 | margin: 0.5rem; 94 | padding: 0.5rem 1rem; 95 | border-radius: 0.5rem; 96 | 97 | display: flex; 98 | align-items: center; 99 | justify-content: space-between; 100 | transition: background-color 200ms; 101 | } 102 | 103 | .todo-item.loading { 104 | background-color: rgba(17, 24, 39, 255); 105 | color: white; 106 | } 107 | 108 | 109 | .todo-item.complete { 110 | background-color: rgba(209, 250, 229, 255); 111 | } 112 | 113 | .todo-item.pending { 114 | background-color: rgba(254, 243, 199, 255); 115 | } 116 | -------------------------------------------------------------------------------- /kitchen-sink/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | -------------------------------------------------------------------------------- /kitchen-sink/src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'react-scan'; 2 | 3 | import { FC, lazy } from 'react'; 4 | import { createRoot } from 'react-dom/client'; 5 | import Home from './main'; 6 | 7 | import './index.css'; 8 | 9 | const examples = import.meta.glob }>( 10 | './examples/**/index.tsx', 11 | { 12 | eager: false, 13 | }, 14 | ); 15 | 16 | const root = document.getElementById('root'); 17 | 18 | if (root) { 19 | const embedded = new URLSearchParams(window.location.search); 20 | const page = embedded.get('example'); 21 | const target = `./examples/${page}/index.tsx`; 22 | if (page && target in examples) { 23 | const App = lazy(examples[target]); 24 | createRoot(root).render(); 25 | } else { 26 | createRoot(root).render(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kitchen-sink/src/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | overflow: hidden; 7 | } 8 | -------------------------------------------------------------------------------- /kitchen-sink/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { type JSX, useState } from 'react'; 2 | import './main.css'; 3 | 4 | interface Example { 5 | title: string; 6 | url: string; 7 | } 8 | 9 | const examples: Example[] = [ 10 | { title: 'Sierpinski Triangle', url: '/?example=sierpinski' }, 11 | { title: 'Todo List', url: '/?example=todo-list' }, 12 | ]; 13 | 14 | export default function Home(): JSX.Element { 15 | const [example, setExample] = useState(0); 16 | 17 | return ( 18 |
19 |
20 |

react-scan

21 |
22 |
23 | {/* content */} 24 |
25 | {/* sidebar */} 26 | {examples.map((item, index) => ( 27 | 35 | ))} 36 |
37 |
38 | {/* iframe */} 39 |