Showing preview only (2,890K chars total). Download the full file or copy to clipboard to get everything.
Repository: aidenybai/million
Branch: main
Commit: 134062655660
Files: 399
Total size: 2.7 MB
Directory structure:
gitextract_pl2ny0b4/
├── .eslintignore
├── .eslintrc.cjs
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── COMMIT_CONVENTION.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── documentation.yml
│ │ ├── feature_request.yml
│ │ └── tooling.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── labeler.yml
│ └── workflows/
│ ├── auto-response.yml
│ ├── ci.yml
│ ├── issue-needs-repro.yml
│ └── publish.yml
├── .gitignore
├── .prettierignore
├── LICENSE
├── README.md
├── build.config.ts
├── cli.js
├── compiler.d.ts
├── jsx-runtime.d.ts
├── package.json
├── packages/
│ ├── cli/
│ │ ├── build.mjs
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── compiler/
│ │ ├── README.md
│ │ ├── auto.ts
│ │ ├── babel.ts
│ │ ├── block.ts
│ │ ├── constants.ts
│ │ ├── experimental/
│ │ │ ├── optimize.ts
│ │ │ ├── render.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── for.old.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── plugin.ts
│ │ ├── types.ts
│ │ ├── utils/
│ │ │ ├── ast.ts
│ │ │ ├── checks.ts
│ │ │ ├── generate-unique-name.ts
│ │ │ ├── get-descriptive-name.ts
│ │ │ ├── get-import-specifier.ts
│ │ │ ├── get-root-statement-path.ts
│ │ │ ├── get-valid-import-definition.ts
│ │ │ ├── is-guaranteed-literal.ts
│ │ │ ├── is-jsx-component-element.ts
│ │ │ ├── is-use-client.ts
│ │ │ ├── jsx.ts
│ │ │ ├── log.ts
│ │ │ ├── object.ts
│ │ │ ├── register-import-definition.ts
│ │ │ ├── unwrap-node.ts
│ │ │ └── unwrap-path.ts
│ │ └── vdom/
│ │ ├── index.ts
│ │ └── visitor.ts
│ ├── experimental/
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── jsx-runtime/
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── kitchen-sink/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── css/
│ │ │ │ ├── examples/
│ │ │ │ │ ├── age-calculator.css
│ │ │ │ │ ├── bmi-calculator.css
│ │ │ │ │ ├── carousel.css
│ │ │ │ │ ├── create-event-calender.css
│ │ │ │ │ ├── dice-roller.css
│ │ │ │ │ └── style.css
│ │ │ │ └── style.css
│ │ │ ├── examples/
│ │ │ │ ├── age-calculator.tsx
│ │ │ │ ├── bmi-calculator.jsx
│ │ │ │ ├── book-recommendation.jsx
│ │ │ │ ├── calculator.tsx
│ │ │ │ ├── carousel.tsx
│ │ │ │ ├── context.tsx
│ │ │ │ ├── countdown-timer.jsx
│ │ │ │ ├── counter.tsx
│ │ │ │ ├── create-event-calender.jsx
│ │ │ │ ├── crypto-tracker.jsx
│ │ │ │ ├── currency-convertor.tsx
│ │ │ │ ├── data-converter.tsx
│ │ │ │ ├── dice-roller.tsx
│ │ │ │ ├── digital-personal-journal.jsx
│ │ │ │ ├── discount-calculator.tsx
│ │ │ │ ├── emoji-picker.tsx
│ │ │ │ ├── etch-a-sketch.tsx
│ │ │ │ ├── expense-tracker.tsx
│ │ │ │ ├── file-upload-preview.jsx
│ │ │ │ ├── form.tsx
│ │ │ │ ├── github-user-search.jsx
│ │ │ │ ├── guestbook.tsx
│ │ │ │ ├── hangman-game.tsx
│ │ │ │ ├── interactive-card-game.tsx
│ │ │ │ ├── investment-calculator.tsx
│ │ │ │ ├── jotai-counter.tsx
│ │ │ │ ├── list.tsx
│ │ │ │ ├── location-app.tsx
│ │ │ │ ├── markdown-editor.tsx
│ │ │ │ ├── million-quiz.jsx
│ │ │ │ ├── morse-code-translator.jsx
│ │ │ │ ├── mortgage-calculator.tsx
│ │ │ │ ├── movie-finder.tsx
│ │ │ │ ├── multi-children.jsx
│ │ │ │ ├── music-player.jsx
│ │ │ │ ├── news-aggregator.tsx
│ │ │ │ ├── number-guessing.tsx
│ │ │ │ ├── password-generator.tsx
│ │ │ │ ├── pomodoro-timer.jsx
│ │ │ │ ├── qr-code-generator.tsx
│ │ │ │ ├── quote-generator.tsx
│ │ │ │ ├── react-router-dom.tsx
│ │ │ │ ├── recipe-finder.jsx
│ │ │ │ ├── redux-todo.tsx
│ │ │ │ ├── repro.tsx
│ │ │ │ ├── rock-paper-scissors.tsx
│ │ │ │ ├── snake-game.jsx
│ │ │ │ ├── style.tsx
│ │ │ │ ├── styled-counter.tsx
│ │ │ │ ├── svg.tsx
│ │ │ │ ├── swr.tsx
│ │ │ │ ├── tanstack-query.tsx
│ │ │ │ ├── tanstack-virtual.tsx
│ │ │ │ ├── task-tracker.tsx
│ │ │ │ ├── tic-tac-toe.jsx
│ │ │ │ ├── todolist.tsx
│ │ │ │ ├── type-race.jsx
│ │ │ │ ├── valtio-counter.tsx
│ │ │ │ ├── virtual-whiteboard.tsx
│ │ │ │ ├── weather-app.tsx
│ │ │ │ ├── wouter.tsx
│ │ │ │ └── zustand-counter.tsx
│ │ │ ├── main.tsx
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── million/
│ │ ├── README.md
│ │ ├── alias.ts
│ │ ├── array.ts
│ │ ├── block.ts
│ │ ├── constants.ts
│ │ ├── dom.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── template.ts
│ │ └── types.ts
│ ├── react/
│ │ ├── README.md
│ │ ├── block.ts
│ │ ├── compiled-block.ts
│ │ ├── constants.ts
│ │ ├── for.ts
│ │ ├── index.ts
│ │ ├── its-fine.tsx
│ │ ├── package.json
│ │ └── utils.ts
│ ├── react-server/
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── utils.ts
│ ├── telemetry/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── config.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── post.ts
│ │ ├── project-info.ts
│ │ ├── system-info.ts
│ │ └── utils/
│ │ ├── detect-package-manager.ts
│ │ ├── is-ci.ts
│ │ ├── is-docker.ts
│ │ ├── is-inside-container.ts
│ │ └── is-wsl.ts
│ └── types/
│ ├── README.md
│ ├── index.ts
│ └── package.json
├── pnpm-workspace.yaml
├── react-server.d.ts
├── react.d.ts
├── test/
│ ├── alias.test.ts
│ ├── block.test.ts
│ ├── map-array.test.ts
│ └── prettier.js
├── tsconfig.json
├── types.d.ts
├── vitest.config.ts
└── website/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── components/
│ ├── ad.tsx
│ ├── automatic-mode-warning.tsx
│ ├── back-in-block/
│ │ ├── block-vdom.tsx
│ │ ├── combined-block.tsx
│ │ ├── count.tsx
│ │ ├── slideshow.tsx
│ │ ├── static-analysis.tsx
│ │ └── vdom.tsx
│ ├── bounties.tsx
│ ├── box.tsx
│ ├── chart.tsx
│ ├── cursor.tsx
│ ├── demo.tsx
│ ├── extra-content.tsx
│ ├── home/
│ │ ├── about.tsx
│ │ ├── container.tsx
│ │ ├── cta.tsx
│ │ ├── faq.tsx
│ │ ├── hero.tsx
│ │ ├── index.tsx
│ │ ├── shimmer-button.tsx
│ │ └── showcase.tsx
│ ├── icons/
│ │ ├── discord-icon.tsx
│ │ ├── github-icon.tsx
│ │ └── twitter-x-icon.tsx
│ ├── lint/
│ │ ├── article-wrapper.tsx
│ │ ├── border-beam.tsx
│ │ ├── cases.tsx
│ │ ├── index.tsx
│ │ ├── intro.tsx
│ │ ├── join.tsx
│ │ ├── left.tsx
│ │ └── right.tsx
│ ├── million-library/
│ │ ├── block-878fb9ae.d.ts
│ │ ├── chunks/
│ │ │ ├── block.cjs
│ │ │ ├── block.mjs
│ │ │ ├── constants.cjs
│ │ │ └── constants.mjs
│ │ ├── compiler.cjs
│ │ ├── compiler.d.ts
│ │ ├── compiler.mjs
│ │ ├── jsx-runtime.cjs
│ │ ├── jsx-runtime.d.ts
│ │ ├── jsx-runtime.mjs
│ │ ├── million.cjs
│ │ ├── million.d.ts
│ │ ├── million.mjs
│ │ ├── react-server.cjs
│ │ ├── react-server.d.ts
│ │ ├── react-server.mjs
│ │ ├── react.cjs
│ │ ├── react.d.ts
│ │ ├── react.mjs
│ │ └── types-35702ad2.d.ts
│ ├── retro-grid.tsx
│ ├── spotlight.tsx
│ ├── use-dark-mode.tsx
│ └── wrapped/
│ ├── index.tsx
│ └── useMockProgress.ts
├── hooks/
│ └── use-translations.ts
├── middleware.js
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pages/
│ ├── _app.mdx
│ ├── _meta.en-US.json
│ ├── _meta.es-ES.json
│ ├── _meta.fr-FR.json
│ ├── _meta.zh-CN.json
│ ├── api/
│ │ └── og.tsx
│ ├── blog/
│ │ ├── .prettierignore
│ │ ├── _meta.en-US.json
│ │ ├── _meta.es-ES.json
│ │ ├── _meta.fr-FR.json
│ │ ├── _meta.zh-CN.json
│ │ ├── behind-the-block.en-US.mdx
│ │ ├── behind-the-block.es-ES.mdx
│ │ ├── behind-the-block.fr-FR.mdx
│ │ ├── behind-the-block.zh-CN.mdx
│ │ ├── lint.en-US.mdx
│ │ ├── million-3.en-US.mdx
│ │ ├── million-3.es-ES.mdx
│ │ ├── million-3.fr-FR.mdx
│ │ ├── million-3.zh-CN.mdx
│ │ ├── million-beyond-speed.en-US.mdx
│ │ ├── million-beyond-speed.es-ES.mdx
│ │ ├── million-beyond-speed.fr-FR.mdx
│ │ ├── million-beyond-speed.zh-CN.mdx
│ │ ├── million-v2.5.1.en-US.mdx
│ │ ├── million-v2.5.1.es-ES.mdx
│ │ ├── million-v2.5.1.fr-FR.mdx
│ │ ├── million-v2.5.1.zh-CN.mdx
│ │ ├── million-v2.5.3.en-US.mdx
│ │ ├── million-v2.5.3.es-ES.mdx
│ │ ├── million-v2.5.3.fr-FR.mdx
│ │ ├── million-v2.5.3.zh-CN.mdx
│ │ ├── virtual-dom.en-US.mdx
│ │ ├── virtual-dom.es-ES.mdx
│ │ ├── virtual-dom.fr-FR.mdx
│ │ └── virtual-dom.zh-CN.mdx
│ ├── blog.en-US.mdx
│ ├── blog.es-ES.mdx
│ ├── blog.fr-FR.mdx
│ ├── blog.zh-CN.mdx
│ ├── code-policy.en-US.mdx
│ ├── docs/
│ │ ├── _meta.en-US.json
│ │ ├── _meta.es-ES.json
│ │ ├── _meta.fr-FR.json
│ │ ├── _meta.zh-CN.json
│ │ ├── automatic.en-US.mdx
│ │ ├── automatic.es-ES.mdx
│ │ ├── automatic.fr-FR.mdx
│ │ ├── automatic.zh-CN.mdx
│ │ ├── experimental.en-US.mdx
│ │ ├── index.zh-CN.mdx
│ │ ├── install.en-US.mdx
│ │ ├── install.es-ES.mdx
│ │ ├── install.fr-FR.mdx
│ │ ├── install.zh-CN.mdx
│ │ ├── internals/
│ │ │ ├── _meta.en-US.json
│ │ │ ├── _meta.es-ES.json
│ │ │ ├── _meta.fr-FR.json
│ │ │ ├── _meta.zh-CN.json
│ │ │ ├── block.en-US.mdx
│ │ │ ├── block.es-ES.mdx
│ │ │ ├── block.fr-FR.mdx
│ │ │ ├── block.zh-CN.mdx
│ │ │ ├── map-array.en-US.mdx
│ │ │ ├── map-array.es-ES.mdx
│ │ │ ├── map-array.fr-FR.mdx
│ │ │ ├── map-array.zh-CN.mdx
│ │ │ ├── mount.en-US.mdx
│ │ │ ├── mount.es-ES.mdx
│ │ │ ├── mount.fr-FR.mdx
│ │ │ ├── mount.zh-CN.mdx
│ │ │ ├── patch.en-US.mdx
│ │ │ ├── patch.es-ES.mdx
│ │ │ ├── patch.fr-FR.mdx
│ │ │ ├── patch.zh-CN.mdx
│ │ │ ├── render-to-template.en-US.mdx
│ │ │ ├── render-to-template.es-ES.mdx
│ │ │ ├── render-to-template.fr-FR.mdx
│ │ │ ├── render-to-template.zh-CN.mdx
│ │ │ ├── string-to-dom.en-US.mdx
│ │ │ ├── string-to-dom.es-ES.mdx
│ │ │ ├── string-to-dom.fr-FR.mdx
│ │ │ └── string-to-dom.zh-CN.mdx
│ │ ├── introduction.en-US.mdx
│ │ ├── introduction.es-ES.mdx
│ │ ├── introduction.fr-FR.mdx
│ │ ├── introduction.zh-CN.mdx
│ │ └── manual-mode/
│ │ ├── _meta.en-US.json
│ │ ├── _meta.es-ES.json
│ │ ├── _meta.fr-FR.json
│ │ ├── _meta.zh-CN.json
│ │ ├── block.en-US.mdx
│ │ ├── block.es-ES.mdx
│ │ ├── block.fr-FR.mdx
│ │ ├── block.zh-CN.mdx
│ │ ├── for.en-US.mdx
│ │ ├── for.es-ES.mdx
│ │ ├── for.fr-FR.mdx
│ │ ├── for.zh-CN.mdx
│ │ ├── manual-mode.en-US.mdx
│ │ ├── manual-mode.es-ES.mdx
│ │ ├── manual-mode.fr-FR.mdx
│ │ ├── manual-mode.zh-CN.mdx
│ │ ├── virtualization.en-US.mdx
│ │ ├── virtualization.es-ES.mdx
│ │ ├── virtualization.fr-FR.mdx
│ │ └── virtualization.zh-CN.mdx
│ ├── faq.en-US.mdx
│ ├── faq.es-ES.mdx
│ ├── faq.fr-FR.mdx
│ ├── faq.zh-CN.mdx
│ ├── foundation.en-US.mdx
│ ├── foundation.es-ES.mdx
│ ├── foundation.fr-FR.mdx
│ ├── foundation.zh-CN.mdx
│ ├── index.en-US.mdx
│ ├── index.es-ES.mdx
│ ├── index.fr-FR.mdx
│ ├── index.zh-CN.mdx
│ ├── lint.en-US.mdx
│ ├── privacy.en-US.mdx
│ ├── showcase.en-US.mdx
│ ├── showcase.es-ES.mdx
│ ├── showcase.fr-FR.mdx
│ ├── showcase.zh-CN.mdx
│ ├── telemetry.en-US.mdx
│ ├── terms.en-US.mdx
│ └── wrapped/
│ ├── [id].mdx
│ └── _meta.json
├── postcss.config.js
├── public/
│ └── lint/
│ └── demo.mp4.json
├── styles/
│ ├── global.css
│ └── wrapped.css
├── tailwind.config.js
├── theme.config.tsx
├── translations.ts
├── tsconfig.json
├── vercel.json
└── video.d.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
**/*.js
/*.ts
dist/
node_modules/
packages/kitchen-sink
*.old.ts
================================================
FILE: .eslintrc.cjs
================================================
const { resolve } = require('path');
const project = resolve(__dirname, 'tsconfig.json');
module.exports = {
root: true,
extends: [
require.resolve('@vercel/style-guide/eslint/node'),
require.resolve('@vercel/style-guide/eslint/typescript'),
],
parserOptions: {
project,
},
settings: {
'import/resolver': {
typescript: {
project,
},
},
},
overrides: [
{
files: ['*.ts'],
rules: {
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-loop-func': 'off',
'eslint-comments/disable-enable-pair': 'off',
'import/no-cycle': 'off',
'import/no-default-export': 'off',
'no-nested-ternary': 'off',
'no-param-reassign': 'off',
'tsdoc/syntax': 'off',
'import/no-extraneous-dependencies': 'off',
'eslint-comments/require-description': 'off',
'import/no-relative-packages': 'off',
},
},
],
};
================================================
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, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, 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 aiden.bai05@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and 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](https://www.contributor-covenant.org), version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: .github/COMMIT_CONVENTION.md
================================================
## Git Commit Message Convention
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
#### TL;DR:
Messages must be matched by the following regex:
```js
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,72}/;
```
### Full Message Format
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
### Revert
If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
### Scope
The scope could be anything specifying the place of the commit change.
### Subject
The subject contains a succinct description of the change:
- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize the first letter
- no dot (.) at the end
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
================================================
FILE: .github/CONTRIBUTING.md
================================================
# ✨ Contributing Guide
First of all, thank you for taking the time to contribute! 🎉
The following is a set of guidelines for contributing to Million.js. These are just guidelines, not rules, so use your best judgment and feel free to propose changes to this document in a pull request.
Before contributing, we encourage you to read our [Code of Conduct](https://github.com/aidenybai/million/blob/main/.github/CODE_OF_CONDUCT.md).
## Table of Contents
- [Reporting Bugs](#reporting-bugs)
- [How Do I Submit A Good Bug Report?](#good-bug-report)
- [Suggesting Enhancements/Features](#suggesting-enhancements-and-features)
- [How Do I Submit A Good Feature Request?](#good-feature-request)
- [Understanding the Project](#understanding-the-project)
- [Prerequisite](#prerequisite)
- [Project Structure](#project-structure)
- [Contributing to the Project](#contributing-to-the-project)
- [Cloning the Repository (repo)](#cloning-the-repo)
- [Making your Changes](#making-your-changes)
- [Opening a Pull Request(PR)](#opening-a-pull-request)
- [Creating A Development Playground](#creating-a-dev-playground)
## <span id="reporting-bugs">💣 Reporting Bugs</span>
This section guides you through submitting a bug report. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating a new issue, **[Perform a cursory search](https://github.com/aidenybai/million/issues)** to see if the report exists. If it does, go through the discussion thread and leave a comment instead of opening a new one.
If you find a **Closed** issue that is the same as what you are experiencing, open a new issue and include a link to the original case in the body of your new one.
If you cannot find an open or closed issue addressing the problem, [open a new issue](https://github.com/aidenybai/million/issues).
Be sure to include a **clear title and description**, as much **relevant information** as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
### <span id="good-bug-report"> How Do I Submit A Good Bug Report?</span>
A good Bug Report should include the following:
- A clear and descriptive title for the bug report
- The exact steps to reproduce the bug
- The behavior you observed after following the steps
- The behavior you expected to see instead
- What might be causing the issue (if you have any idea)
- Screenshots or animated GIFs of the issue (if applicable)
- If you use the keyboard while following the steps, use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://gitlab.gnome.org/Archive/byzanz) on Linux.
[🔝 Back to top](#table-of-content)
## <span id="suggesting-enhancements-and-features">🛠 Suggesting Enhancements/Features</span>
We track Enhancements and Feature Requests as GitHub issues.
Before submitting an enhancement/feature request, **[perform a cursory search](https://github.com/aidenybai/million/issues)** to see if the request exists. If it does, go through the conversation thread to see if there is additional information and contribute to the conversation instead of opening a new one.
If you find a **Closed** issue, go through the conversation thread to see if the feature has already been implemented or rejected. If not, you can open a new issue.
### <span id="good-feature-request">How Do I Submit A Good Feature Request?</span>
A good Feature Request should include the following:
- A clear and descriptive title of the request
- A detailed description of the request, including:
- The problem you are trying to solve
- How you are currently working around the lack of this feature
- Any possible drawbacks of the feature
- How the feature can be implemented
- An explanation of why this enhancement would be helpful to the project and the community
- Any links to resources or other projects that might help in implementing the feature
[🔝 Back to top](#table-of-content)
## <span id="understanding-the-project">✨ Understanding the Project</span>
Before contributing to the project, you need to understand the project structure and how it works. This section guides you through the project structure and how to run the project locally.
### <span id="prerequisite">Prerequisite</span>
This project uses [pnpm](https://pnpm.io/) as its package manager. If you already have [Node.js](https://nodejs.org/en) installed, this is how you can install pnpm globally:
```bash
npm i -g pnpm
```
If you do not have Node.js installed, run the command:
```bash
npm install -g @pnpm/exe
```
> Note: For node 20.x upwards, some users have reported issues with pnpm. Check the [pnpm documentation](https://pnpm.io/installation#compatibility) for more information, including other ways to install pnpm.
### <span id="project-structure">Project Structure</span>
The project uses a [monorepo](https://monorepo.tools/) structure and is divided into three main folders:
1. `packages` - This folder contains all the packages used within the Million.js library
2. `test` - This folder contains all the tests written for the Million.js library
3. `website` - This folder contains the code for the website [Million.js](https://million.dev/)
#### 1. `packages` folder
The `packages` folder contains all the code related to the Million.js package, built using Typescript. The source code is divided into six main folders:
- `compiler` - This folder contains all the code related to the Million.js React compilers
- `jsx-runtime` - This folder contains the code related to the runtime code for jsx.
- `million` - This folder contains the code for the core Million.js package. The implementation of the optimized array rendering `<For/>`, `block()`, and virtual DOM for React are in here.
- `react` and `react-server` - This folder contains the Million.js support package for React
- `types` - This folder contains all the shared types between packages
To run the project locally, run the following commands:
```bash
pnpm install
pnpm run dev
```
#### 2. `test` folder
This folder contains the code for the tests for the implementation of core Million.js features like the "map-array" with `<For/>`, `block()`, and so on. The test uses the [Vitest](https://vitest.dev/).
To run the test locally:
```bash
pnpm install
pnpm run test
```
#### 3. `website` folder
This folder contains the code for the Million.js website built with [Nextra](https://nextra.site/), a framework that allows you to create static websites with [Next.js](https://nextjs.org/) and [MDX](https://mdxjs.com/).
The source code is divided into four main folders:
- `pages` - This folder contains all the website pages.
- `components` - This folder contains all the website components.
- `styles` - This folder contains all the styles of the website
- `public` - This folder contains all the website's static files.
> Note: To contribute to the website, you need to have a basic understanding of [Next.js](https://nextjs.org/docs) and [MDX.](https://mdxjs.com/)
Then, you'll need to install the dependencies and run the development server following the commands below:
```bash
pnpm install
cd website # Takes you to the website mono-repo
pnpm run dev
```
[🔝 Back to top](#table-of-content)
## <span id="contributing-to-the-project">📝 Contributing to the Project</span>
If you want to do more than report an issue or suggest an enhancement, you can contribute to the project by:
- cloning the repository (repo)
- making your changes
- opening a pull request
### <span id="cloning-the-repo">Cloning the Repository (repo)</span>
#### 1. Fork the repo
Click the fork button at the top right of the page to create a copy of this repo in your account, or go to the [Million.js fork page](https://github.com/aidenybai/million/fork).
After successfully forking the repo, you will be directed to your repo copy.
#### 2. Clone the forked repo
On your forked repo, click the green button that says `Code`. It will open a dropdown menu. Copy the link in the input with the label `HTTPS` or `GitHub CLI` depending on your preferred cloning mode.
For HTTPS, open up your terminal and run the following command:
```bash
git clone <your-clone-link>
# or
git clone https://github.com/<your-username>/million.git
```
Replace `<your-username>` with your GitHub username.
You can also clone the repo using the GitHub CLI. To do this, run the following command:
```bash
gh repo clone <your-username>/million
```
#### 3. Set up the project
To set up the project, navigate into the project directory and open up the project in your preferred code editor.
```bash
cd million
code . # Opens up the project in VSCode
```
Install the dependencies using pnpm. You need to have [pnpm](https://pnpm.io/) installed; see the [prerequisite](#prerequisite) section.
```bash
pnpm install
```
### <span id="making-your-changes">Making your Changes</span>
#### 1. Create a new branch
Create a new branch from the `main` branch. Your branch name should be descriptive of the changes you are making. E.g., `docs-updating-the-readme-file`. Some ideas to get you started:
- For Feature Updates: `feat-<brief 2-4 words-Description>-<ISSUE_NO>`
- For Bug Fixes: `fix-<brief 2-4 words-Description>-<ISSUE_NO>`
- For Documentation: `docs-<brief 2-4 words-Description>-<ISSUE_NO>`
To create a new branch, use the following command:
```bash
git checkout -b <your-branch-name>
```
#### 2. Run the project
The project is a mono repo containing the codes for the **website**, the **core Million.js package**, the **playground**, and the **tests**.
If you want to run the website locally, you need to `cd` into the website folder before running the development server:
```bash
cd website # Takes you to the website mono-repo
pnpm run dev
```
#### 3. Make your changes
You are to make only one contribution per pull request. It makes it easier to review and merge. If you have multiple bug fixes or features, create separate pull requests for each.
#### 4. Commit your changes
Your commit message should give a concise idea of the issue you are solving. It should follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specification, as this helps us generate the project changelog automatically. A traditional structure of commit looks like so:
```bash
<type>(optional scope): <description>
```
To commit your changes, run the following command:
```bash
git add .
git commit -m "<your_commit_message>"
```
Eg:
```bash
git commit -m "feat: add support for Next.js"
```
#### 5. Clean up your code and push changes
After committing your changes, run the following command before pushing your local commits to the remote repository and ensure that all tests pass and there are no linting errors.
```bash
pnpm cleanup
pnpm lint
pnpm test
```
Once all tests and linting pass, push your local commits to your remote repository.
```bash
git push origin your-branch-name
```
### <span id="opening-a-pull-request">Opening a Pull Request(PR)</span>
#### 1. Create a new [Pull Request (PR)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)
Go to the [Million.js repository](https://github.com/aidenybai/million/tree/main) and click the `compare & pull request` button or go to the [Pull Request page](https://github.com/aidenybai/million/pulls) and click on the `New pull request` button. It will take you to the `Open a pull request` page.
> Note: Make sure your PR points to the `main` branch, not any other one.
#### 2. Wait for review
🎉 Congratulations! You've made your pull request! A maintainer will review and merge your code or request changes. If changes are requested, make them and push them to your branch. Your pull request will automatically track the changes on your branch and update.
[🔝 Back to top](#table-of-content)
## <span id="creating-a-dev-playground">🎮 Creating A Development Playground</span>
If you are contributing to the Million.js package, you might want to test your changes in a controlled environment before applying them to the official Million.js package. This section guides you through creating a development playground for testing your changes.
### <span id="setting-up-a-development-environment">Setting Up A Development Environment</span>
To test your changes to the Million.js project on your computer, you must set up a development environment within an existing React project that uses Million.js as a dependency.
> Note: If you don't have an existing React project, you can create a new one using [Create React App](https://create-react-app.dev/) or [Next.js](https://nextjs.org/). Then install Million.js as a dependency using `npm install million`, `pnpm install million`, or `yarn add million`. If you already have the Million.js project cloned on your computer, you could also import it from your local file system as will be shown below.
Here's how to do it:
**1. Prepare Your Project**: Open the React or Next.js project you want to use as a playground for testing Million.js changes.
**2. Update Import Paths**: In your project's code, find the places where you import or want to Million.js components or functions. The imports might look something like this:
```jsx
import { block } from 'million/react';
```
**3. Switch to Absolute Paths**: Instead of using relative import paths (like 'million/react'), you'll change them to or use absolute paths that directly point to your local Million.js codebase on your computer. For example:
```jsx
import { block } from '/home/your-username/path-to-your-local-million/react';
```
Replace `/home/your-username/path-to-your-local-million/react` with the actual path to your Million.js codebase.
By doing this, you're effectively telling your project to use the local version of Million.js on your computer rather than the published version from npm. This way, you can test your changes in a controlled environment before applying them to the official Million.js package.
**4. Run Your Project**: Run your project and test your changes. If you make any changes to the Million.js codebase, you'll need to restart your project to see the changes.
[🔝 Back to top](#table-of-content)
## <span id="license">📄 License</span>
Million.js is [MIT licensed](https://github.com/aidenybai/million/blob/main/LICENSE).
================================================
FILE: .github/FUNDING.yml
================================================
github: [aidenybai]
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "\U0001F41B Bug Report"
description: Report an issue or possible bug
labels: []
assignees: []
body:
- type: markdown
attributes:
value: |
## Quick Checklist
Thank you for taking the time to file a bug report! Please fill out this form as completely as possible.
✅ I am using the **latest version of Million.js** and all plugins.
✅ I am using a version of Node that supports ESM (`v14.18.0+`, or `v16.12.0+`)
- type: input
id: million-version
attributes:
label: What version of `million` are you using?
placeholder: 0.0.0
validations:
required: true
- type: input
id: ssr-adapter
attributes:
label: Are you using an SSR adapter? If so, which one?
placeholder: None, or Netlify, Vercel, Cloudflare, etc.
validations:
required: true
- type: input
id: package-manager
attributes:
label: What package manager are you using?
placeholder: npm, yarn, pnpm
validations:
required: true
- type: input
id: os
attributes:
label: What operating system are you using?
placeholder: Mac, Windows, Linux
validations:
required: true
- type: input
id: browser
attributes:
label: What browser are you using?
placeholder: Chrome, Firefox, Safari
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Describe the Bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: bug-expectation
attributes:
label: What's the expected result?
description: Describe what you expect to happen.
validations:
required: true
- type: input
id: bug-reproduction
attributes:
label: Link to Minimal Reproducible Example
description: '**A minimal reproduction is required** so that others can help debug your issue. If a report is vague (e.g. just a generic error message) and has no reproduction, it may be auto-closed.'
placeholder: 'https://stackblitz.com/abcd1234'
validations:
required: true
- type: checkboxes
id: will-pr
attributes:
label: Participation
options:
- label: I am willing to submit a pull request for this issue.
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/documentation.yml
================================================
description: Report a typo or missing area of documentation
labels:
- "area: documentation"
name: 📝 Documentation
title: "📝 Documentation: <short description of the request>"
body:
- attributes:
description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you!
label: Bug Report Checklist
options:
- label: I have pulled the latest `main` branch of the repository.
required: true
- label: "I have searched for [related issues](https://github.com/aidenybai/million/issues) and found none that matched my issue."
required: true
type: checkboxes
- attributes:
description: What would you like to report?
label: Overview
type: textarea
validations:
required: true
- attributes:
description: Any additional info you'd like to provide.
label: Additional Info
type: textarea
description: Report a typo or missing area of documentation
labels:
- "area: documentation"
name: 📝 Documentation
title: "📝 Documentation: <short description of the request>"
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: "🚀 Feature Request"
description: Suggest a Feature or an Improvement
labels: ["pending triage"]
body:
- type: markdown
attributes:
value: Thank you for taking the time to fill out this feature request!
- type: textarea
id: feature-description
attributes:
label: Describe the feature
description: A clear and concise description of what you think would be a helpful addition, including the possible use cases and alternatives you have considered. If you have a working prototype or module that implements it, please include a link.
placeholder: Feature description
validations:
required: true
- type: checkboxes
id: additional-info
attributes:
label: Additional information
description: Additional information that helps us decide how to proceed.
options:
- label: Would you be willing to help implement this feature?
================================================
FILE: .github/ISSUE_TEMPLATE/tooling.yml
================================================
description: Report a bug or request an enhancement in repository tooling
labels:
- 'area: tooling'
name: 🛠 Tooling
title: '🛠 Tooling: <short description of the change>'
body:
- attributes:
description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you!
label: Bug Report Checklist
options:
- label: I have tried restarting my IDE and the issue persists.
required: true
- label: I have pulled the latest `main` branch of the repository.
required: true
- label: 'I have searched for [related issues](https://github.com/aidenybai/million/issues) and found none that matched my issue.'
required: true
type: checkboxes
- attributes:
description: What did you expect to be able to do?
label: Overview
type: textarea
validations:
required: true
- attributes:
description: Any additional info you'd like to provide.
label: Additional Info
type: textarea
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
**Please describe the changes this PR makes and why it should be merged:**
**Status**
- [ ] Code changes have been tested against prettier, or there are no code changes
- [ ] I know how to update typings and have done so, or typings don't need updating
**Semantic versioning classification:**
- [ ] This PR changes the codebase
- [ ] This PR includes breaking changes (methods removed or renamed, parameters moved or removed)
- [ ] This PR changes the internal workings with no modifications to the external API (bug fixes, performance improvements)
- [ ] This PR **only** includes non-code changes, like changes to documentation, README, etc.
================================================
FILE: .github/labeler.yml
================================================
name: Auto Labeling
on:
issues:
types: [opened, labeled, unlabeled]
pull_request:
types: [opened, labeled, unlabeled]
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Label issues and pull requests
uses: actions/labeler@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/auto-response.yml
================================================
name: Automated Responses
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
jobs:
respond:
runs-on: ubuntu-latest
steps:
- name: Comment on new issue
if: github.event_name == 'issues'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Thanks for opening this issue! A maintainer will review it soon.',
})
- name: Comment on new pull request
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.pulls.createReview({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Thanks for opening this pull request! A maintainer will review it soon.',
event: 'COMMENT',
})
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [19.x]
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2.0.1
with:
version: 8.1.1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: pnpm i
- run: pnpm lint
- run: pnpm test
- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
flag-name: run-${{ matrix.test_number }}
parallel: true
finish:
needs: build
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
================================================
FILE: .github/workflows/issue-needs-repro.yml
================================================
name: Close Issues (needs repro)
on:
schedule:
- cron: '0 0 * * *'
jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- name: needs repro
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
token: ${{ secrets.GITHUB_TOKEN }}
labels: 'needs repro'
inactive-day: 3
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish Package to npmjs
on:
release:
types: [published]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.0.1
with:
version: 8.1.1
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v3
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- run: pnpm i
- run: pnpm test
- run: pnpm lint
- run: pnpm build
- run: npm i -g npm
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
dist
node_modules
package-lock.json
yarn-error.log
yarn.lock
dev
coverage
.DS_Store
.pnpm-debug.log
*.tgz
.next
.turbo
.eslintcache
.vscode
================================================
FILE: .prettierignore
================================================
node_modules
dist
website/pages/docs/compiler.mdx
website/pages/docs/install.mdx
website/pages/docs/quickstart.mdx
website/pages/docs/rules.mdx
website/pages/docs/automatic.mdx
website/pages/blog/*.mdx
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021-present Aiden Bai
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
================================================
> I'm working on something new (still free + open source!)
>
> React Grab allows you to select an element and copy its context (like HTML, React component, and file source)
>
> Check it out: [**react-grab.com**](https://react-grab.com)
<a href="https://million.dev">
<img src="https://raw.githubusercontent.com/aidenybai/million/main/.github/assets/banner.png" alt="Million.js Banner" />
</a>
<div align="center">
<a href="https://img.shields.io/github/actions/workflow/status/aidenybai/million/ci.yml?branch=main" target="_blank"><img src="https://img.shields.io/github/actions/workflow/status/aidenybai/million/ci.yml?branch=main&style=flat&colorA=000000&colorB=000000" alt="CI" /></a>
<a href="https://www.npmjs.com/package/million" target="_blank"><img src="https://img.shields.io/npm/v/million?style=flat&colorA=000000&colorB=000000" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/package/million" target="_blank"><img src="https://img.shields.io/npm/dt/million.svg?style=flat&colorA=000000&colorB=000000" alt="NPM Downloads" /></a>
<a href="https://discord.gg/X9yFbcV2rF" target="_blank"><img src="https://img.shields.io/discord/938129049539186758?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff" /></a>
<table>
<tbody>
<tr>
<td>
<a href="https://million.dev/docs/introduction">📚 Read the docs</a>
</td>
<td>
<a href="https://www.youtube.com/watch?v=VkezQMb1DHw">🎦 Watch video</a>
</td>
<td>
<a href="https://million.dev/chat">💬 Join our Discord</a>
</td>
<td>
<a href="https://twitter.com/milliondotjs">🌐 Follow on Twitter</a>
</td>
</tr>
</tbody>
</table>
</div>
## What is Million.js?
Million.js is an extremely fast and lightweight optimizing compiler that make [components](https://react.dev) up to [_**70% faster**_](https://krausest.github.io/js-framework-benchmark/current.html).
> Oh man... Another [`/virtual dom|javascript/gi`](https://regexr.com/6mr5f) framework? I'm fine with [React](https://reactjs.org) already, why do I need this?
Million.js works with React and makes reconciliation faster. By using a fine-tuned, optimized virtual DOM, Million.js reduces the overhead of diffing ([_try it out here_](https://demo.million.dev))
**TL;DR:** Imagine [React](https://react.dev) components running at the speed of raw JavaScript.
### [**👉 Setup Million.js in seconds! →**](https://million.dev/)
## Installation
The Million.js CLI will automatically install the package and configure your project for you.
```bash
npx million@latest
```
Once your down, just run your project and information should show up in your command line!
> Having issues installing? [**→ View the installation guide**](https://million.dev/docs/install)
## Why Million.js?
To understand why to use Million.js, we need to understand how React updates interfaces. When an application's state or props change, React undergoes an update in two parts: rendering and reconciliation.
To show this, let's say this is our `App`:
```jsx
function App() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
```
In this `App`, when I click on the button, the `count` state will update and the `<p>` tag will update to reflect the new value. Let's break this down.
### Rendering
The first step is rendering. Rendering is the process of generating a snapshot of the current component. You can imagine it as simply "calling" the `App` function and storing the output in a variable. This is what the `App` snapshot would look like:
```jsx
const snapshot = App();
// snapshot =
<div>
<p>Count: 1</p>
<button onClick={increment}>Increment</button>
</div>;
```
### Reconciliation
In order to update the interface to reflect the new state, React needs to compare the previous snapshot to the new snapshot (_called "diffing"_). React's reconciler will go to each element in the previous snapshot and compare it to the new snapshot. If the element is the same, it will skip it. If the element is different, it will update it.
- The `<div>` tag is the same, so it doesn't need to be updated. ✅
- The `<p>` tag is the same, so it doesn't needs to be updated. ✅
- The text inside the `<p>` tag is different, so it needs to be updated. ⚠ ️
- The `<button>` tag is the same, so it doesn't need to be updated. ✅
- The `onClick` prop is the same, so it doesn't need to be updated. ✅
- The text inside the `<button>` tag is the same, so it doesn't need to be updated. ✅
_(total: 6 diff checks)_
```diff
<div>
- <p>Count: 0</p>
+ <p>Count: 1</p>
<button onClick={increment}>Increment</button>
</div>
```
From here, we can see that the `<p>` tag needs to be updated. React will then update the `<p>` DOM node to reflect the new value.
```jsx
<p>.innerHTML = `Count: ${count}`;
```
### How Million.js makes this faster
React is slow.
The issue with React's reconciliation it becomes **exponentially slower** the more JSX elements you have. With this simple `App`, it only needs to diff a few elements. In a real world React app, you can easily have hundreds of elements, slowing down interface updates.
Million.js solves this by **skipping the diffing step entirely** and directly updating the DOM node.
Here is a conceptual example of how Million.js reconciler works:
```jsx
function App() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
// generated by compiler
if (count !== prevCount) {
<p>.innerHTML = `Count: ${count}`;
}
<button>.onclick = increment;
// ...
}
```
Notice how when the `count` is updated, Million.js will directly update the DOM node. Million.js turns React reconciliation from `O(n)` (linear time) to `O(1)` (constant time).
> How fast is it? [**→ View the benchmarks**](https://krausest.github.io/js-framework-benchmark/current.html)
## Resources & Contributing Back
Looking for the docs? Check the [documentation](https://million.dev) or the [Contributing Guide](https://github.com/aidenybai/million/blob/main/.github/CONTRIBUTING.md) out. We also recommend reading [_Virtual DOM: Back in Block_](https://million.dev/blog/virtual-dom) to learn more about Million.js's internals.
Want to talk to the community? Hop in our [Discord](https://discord.gg/X9yFbcV2rF) and share your ideas and what you've build with Million.js.
Find a bug? Head over to our [issue tracker](https://github.com/aidenybai/million/issues) and we'll do our best to help. We love pull requests, too!
We expect all Million.js contributors to abide by the terms of our [Code of Conduct](https://github.com/aidenybai/million/blob/main/.github/CODE_OF_CONDUCT.md).
[**→ Start contributing on GitHub**](https://github.com/aidenybai/million/blob/main/.github/CONTRIBUTING.md)

## Codebase
This repo is a "mono-repo" with modules. Million.js ships as one NPM package, but has first class modules for more complex, but important extensions. Each module has its own folder in the `/packages` directory.
You can also track our progress through our [Roadmap](https://github.com/users/aidenybai/projects/5/views/1?layout=roadmap).
| Module | Description |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| [`million`](https://github.com/aidenybai/million/tree/main/packages/million) | The main Virtual DOM with all of Million.js's core. |
| [`react`](https://github.com/aidenybai/million/tree/main/packages/react) / [`react-server`](https://github.com/aidenybai/million/tree/main/packages/react-server) | React compatibility for Million.js. |
| [`compiler`](https://github.com/aidenybai/million/tree/main/packages/compiler) | The compiler for Million.js in React. |
| [`jsx-runtime`](https://github.com/aidenybai/million/tree/main/packages/jsx-runtime) | A simple JSX runtime for Million.js core. |
| [`types`](https://github.com/aidenybai/million/tree/main/packages/types) | Shared types between packages |
## Sponsors
<p align="center">
<a href="https://github.com/sponsors/aidenybai">
<img src="https://raw.githubusercontent.com/aidenybai/aidenybai/master/sponsors.svg" />
</a>
<a href="https://vercel.com?utm_source=millionjs&utm_campaign=oss"><img height="30" src="https://raw.githubusercontent.com/abumalick/powered-by-vercel/master/powered-by-vercel.svg" /></a>
</p>
## Acknowledgments
Million.js takes heavy inspiration from the following projects:
- [`blockdom`](https://github.com/ged-odoo/blockdom) ([Géry Debongnie](https://github.com/ged-odoo))
Thank you to Géry pioneering the concept of "blocks" in the virtual DOM. Many parts of the Million.js codebase either directly or indirectly derive from his work.
- [`voby`](https://github.com/vobyjs/voby) ([Fabio Spampinato](https://github.com/fabiospampinato))
The Million.js "template" concept is derived from Voby's `template()` API.
- [Hack the Wave](https://hackthewave.com) ([Melinda Chang](https://github.com/melindachang)) for their homepage.
- [`react`](https://react.dev) and [`turbo`](https://turbo.build) for their documentation. Many parts of the current Million.js documentation are grokked and modified from theirs.
- [`ivi`](https://github.com/localvoid/ivi), [Preact](https://github.com/preactjs/preact), [and more](https://krausest.github.io/js-framework-benchmark/2021/table_chrome_96.0.4664.45.html)
## License
Million.js is [MIT-licensed](LICENSE) open-source software by [Aiden Bai](https://aiden.mov) and [contributors](https://github.com/aidenybai/million/graphs/contributors):
<a href="https://github.com/aidenybai/million/graphs/contributors">
<img src="https://contrib.rocks/image?repo=aidenybai/million" />
</a>
================================================
FILE: build.config.ts
================================================
import { defineBuildConfig } from 'unbuild';
import banner from 'rollup-plugin-banner2';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import replace from '@rollup/plugin-replace';
const version = JSON.parse(
readFileSync(join(__dirname, 'package.json'), 'utf-8'),
).version;
export default defineBuildConfig({
entries: [
'./packages/million',
'./packages/experimental',
'./packages/jsx-runtime',
'./packages/compiler',
'./packages/react',
'./packages/react-server',
'./packages/types',
'./packages/cli',
],
declaration: true,
clean: true,
failOnWarn: false,
rollup: {
emitCJS: true,
inlineDependencies: true,
},
hooks: {
'rollup:options'(_ctx, options) {
if (Array.isArray(options?.plugins)) {
options.plugins.push(banner(() => `'use client';\n`) as any);
options.plugins.push(
replace({
preventAssignment: true,
'process.env.VERSION': JSON.stringify(version),
}),
);
}
},
},
externals: ['react', 'react-dom', 'million', 'vite', 'esbuild', 'rollup'],
});
================================================
FILE: cli.js
================================================
#!/usr/bin/env node
const { spawn } = require('child_process');
try {
spawn('npx', ['@million/install@latest'], {
stdio: ['inherit', 'inherit', 'ignore'],
shell: true,
});
} catch (_) {}
================================================
FILE: compiler.d.ts
================================================
import unplugin from './dist/packages/compiler';
export * from './dist/packages/compiler';
export default unplugin;
================================================
FILE: jsx-runtime.d.ts
================================================
export * from './dist/packages/jsx-runtime';
================================================
FILE: package.json
================================================
{
"name": "million",
"version": "3.1.10",
"description": "Make React Faster. Automatically.",
"keywords": [
"million",
"virtualdom",
"vdom",
"library",
"web",
"ui",
"browser"
],
"homepage": "https://million.dev",
"bugs": {
"url": "https://github.com/aidenybai/million/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/aidenybai/million.git"
},
"funding": "https://github.com/sponsors/aidenybai",
"license": "MIT",
"author": {
"name": "Aiden Bai",
"email": "hello@aidenybai.com",
"url": "https://aidenybai.com"
},
"sideEffects": false,
"exports": {
".": {
"import": {
"types": "./dist/packages/million.d.mts",
"default": "./dist/packages/million.mjs"
},
"require": {
"types": "./dist/packages/million.d.ts",
"default": "./dist/packages/million.cjs"
}
},
"./jsx-runtime": {
"import": {
"types": "./dist/packages/jsx-runtime.d.mts",
"default": "./dist/packages/jsx-runtime.mjs"
},
"require": {
"types": "./dist/packages/jsx-runtime.d.ts",
"default": "./dist/packages/jsx-runtime.cjs"
}
},
"./compiler": {
"import": {
"types": "./dist/packages/compiler.d.mts",
"default": "./dist/packages/compiler.mjs"
},
"require": {
"types": "./dist/packages/compiler.d.ts",
"default": "./dist/packages/compiler.cjs"
}
},
"./experimental": {
"import": {
"types": "./dist/packages/experimental.d.mts",
"default": "./dist/packages/experimental.mjs"
},
"require": {
"types": "./dist/packages/experimental.d.ts",
"default": "./dist/packages/experimental.cjs"
}
},
"./react": {
"import": {
"types": "./dist/packages/react.d.mts",
"default": "./dist/packages/react.mjs"
},
"require": {
"types": "./dist/packages/react.d.ts",
"default": "./dist/packages/react.cjs"
}
},
"./react-server": {
"import": {
"types": "./dist/packages/react-server.d.mts",
"default": "./dist/packages/react-server.mjs"
},
"require": {
"types": "./dist/packages/react-server.d.ts",
"default": "./dist/packages/react-server.cjs"
}
},
"./types": {
"import": {
"types": "./dist/packages/types.d.mts",
"default": "./dist/packages/types.mjs"
},
"require": {
"types": "./dist/packages/types.d.ts",
"default": "./dist/packages/types.cjs"
}
},
"./*": "./*"
},
"main": "dist/packages/million.mjs",
"jsdelivr": "dist/packages/million.mjs",
"unpkg": "dist/packages/million.mjs",
"module": "dist/packages/million.mjs",
"types": "dist/packages/million.d.ts",
"bin": "./cli.js",
"files": [
"dist/**/*",
"packages/cli/dist/index.js",
"react.d.ts",
"jsx-runtime.d.ts",
"compiler.d.ts",
"react-server.d.ts",
"types.d.ts",
"cli.js"
],
"scripts": {
"build": "unbuild",
"bump": "pnpm build && pnpm test && pnpm lint && bumpp",
"cleanup": "prettier --write ./**/*.{ts,tsx}",
"lint": "eslint ./packages --ext .ts --cache",
"lint:fix": "pnpm lint --fix",
"test": "vitest run --coverage",
"test:dev": "vitest watch"
},
"prettier": "@vercel/style-guide/prettier",
"dependencies": {
"@babel/core": "^7.23.7",
"@babel/types": "^7.23.6",
"@rollup/pluginutils": "^5.1.0",
"kleur": "^4.1.5",
"undici": "^6.3.0",
"unplugin": "^1.6.0"
},
"devDependencies": {
"@rollup/plugin-replace": "^5.0.5",
"@types/babel__core": "^7.20.5",
"@types/node": "^18.19.4",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vercel/style-guide": "^5.1.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^1.1.1",
"babel-plugin-tester": "^11.0.4",
"bumpp": "^9.2.1",
"c8": "^7.14.0",
"esbuild": "^0.14.54",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
"jsdom": "^21.1.2",
"prettier": "^3.1.1",
"rollup": "^4.9.2",
"rollup-plugin-banner2": "^1.2.2",
"source-map": "^0.7.4",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"unbuild": "2.0.0",
"vite": "^5.0.12",
"vite-tsconfig-paths": "^4.2.3",
"vitest": "1.0.1"
},
"packageManager": "pnpm@9.1.4"
}
================================================
FILE: packages/cli/build.mjs
================================================
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { build } from 'esbuild';
import { replace } from 'esbuild-plugin-replace';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const version = JSON.parse(
readFileSync(join(__dirname, '../../package.json'), 'utf-8')
).version;
build({
entryPoints: ['src/index.ts'],
// bundle: true,
platform: 'node',
outfile: 'dist/index.js',
format: 'cjs',
plugins: [
replace({
'process.env.VERSION': JSON.stringify(version),
}),
],
});
================================================
FILE: packages/cli/package.json
================================================
{
"main": "dist/index.js",
"bin": {
"million": "./dist/index.js"
},
"scripts": {
"prepare": "node build.mjs",
"link": "yarn unlink --global && chmod +x dist/index.js && yarn link",
"dev": "node ./dist/index.js",
"prepublishOnly": "pnpm run build",
"clean": "rm -r dist node_modules"
},
"devDependencies": {
"@types/node": "^18.14.1",
"esbuild": "^0.14.54",
"esbuild-plugin-replace": "^1.4.0",
"prettier": "^2.8.4",
"typescript": "^5.1.6"
}
}
================================================
FILE: packages/cli/src/index.ts
================================================
#! /usr/bin/env node
import { install } from '@million/install';
void install('Million', process.env.VERSION);
================================================
FILE: packages/cli/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es2016",
"lib": [],
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
================================================
FILE: packages/compiler/README.md
================================================
# `million/compiler`
> Note: this page is a stub
Welcome to the Million.js compiler! Here is a [great primer](https://million.dev/blog/behind-the-block) on how the compiler works.
================================================
FILE: packages/compiler/auto.ts
================================================
import type * as babel from '@babel/core';
import * as t from '@babel/types';
import { REACT_IMPORTS, TRACKED_IMPORTS } from './constants';
import type { StateContext } from './types';
import {
isComponentishName,
isPathValid,
isStatementTopLevel,
} from './utils/checks';
import { generateUniqueName } from './utils/generate-unique-name';
import { getDescriptiveName } from './utils/get-descriptive-name';
import { getImportIdentifier } from './utils/get-import-specifier';
import { getRootStatementPath } from './utils/get-root-statement-path';
import { getValidImportDefinition } from './utils/get-valid-import-definition';
import { isGuaranteedLiteral } from './utils/is-guaranteed-literal';
import { isJSXComponentElement } from './utils/is-jsx-component-element';
import { logImprovement } from './utils/log';
import { isUseClient } from './utils/is-use-client';
import { registerImportDefinition } from './utils/register-import-definition';
import { unwrapNode } from './utils/unwrap-node';
import { unwrapPath } from './utils/unwrap-path';
import { shouldBeIgnored } from './utils/ast';
interface JSXStateContext {
bailout: boolean;
elements: number;
attributes: number;
components: number;
text: number;
returns: number;
}
function measureExpression(
state: JSXStateContext,
expr: babel.NodePath<t.Expression>,
portal: boolean,
): void {
const unwrappedJSX = unwrapPath(expr, t.isJSXElement);
if (unwrappedJSX) {
measureJSXExpressions(state, unwrappedJSX);
return;
}
const unwrappedFragment = unwrapPath(expr, t.isJSXFragment);
if (unwrappedFragment) {
measureJSXExpressions(state, unwrappedFragment);
return;
}
if (isGuaranteedLiteral(expr.node)) {
return;
}
if (portal) {
state.components++;
} else {
state.attributes++;
}
}
function measureJSXSpreadChild(
state: JSXStateContext,
path: babel.NodePath<t.JSXSpreadChild>,
): void {
measureExpression(state, path.get('expression'), false);
}
function measureJSXExpressionContainer(
state: JSXStateContext,
path: babel.NodePath<t.JSXExpressionContainer>,
portal: boolean,
): void {
const expr = path.get('expression');
if (isPathValid(expr, t.isExpression)) {
measureExpression(state, expr, portal);
}
}
function measureJSXAttribute(
state: JSXStateContext,
attr: babel.NodePath<t.JSXAttribute>,
): void {
const value = attr.get('value');
if (isPathValid(value, t.isJSXElement)) {
measureJSXExpressions(state, value);
} else if (isPathValid(value, t.isJSXFragment)) {
measureJSXExpressions(state, value);
} else if (isPathValid(value, t.isJSXExpressionContainer)) {
measureJSXExpressionContainer(state, value, false);
}
}
function measureJSXSpreadAttribute(
state: JSXStateContext,
attr: babel.NodePath<t.JSXSpreadAttribute>,
): void {
measureExpression(state, attr.get('argument'), false);
}
function measureJSXAttributes(
state: JSXStateContext,
attrs: babel.NodePath<t.JSXAttribute | t.JSXSpreadAttribute>[],
): void {
// TODO handle specific attributes
for (let i = 0, len = attrs.length; i < len; i++) {
const attr = attrs[i];
if (isPathValid(attr, t.isJSXAttribute)) {
measureJSXAttribute(state, attr);
} else if (isPathValid(attr, t.isJSXSpreadAttribute)) {
measureJSXSpreadAttribute(state, attr);
}
}
}
function measureJSXElement(
state: JSXStateContext,
path: babel.NodePath<t.JSXElement>,
): boolean {
const openingElement = path.get('openingElement');
/**
* If this is a component element, move the JSX expression
* to the expression array.
*/
if (isJSXComponentElement(path)) {
state.components++;
return true;
}
/**
* Otherwise, continue extracting in attributes
*/
state.elements++;
measureJSXAttributes(state, openingElement.get('attributes'));
return false;
}
function measureJSXExpressions(
state: JSXStateContext,
path: babel.NodePath<t.JSXElement | t.JSXFragment>,
): void {
if (isPathValid(path, t.isJSXElement) && measureJSXElement(state, path)) {
return;
}
const children = path.get('children');
for (let i = 0, len = children.length; i < len; i++) {
const child = children[i];
if (isPathValid(child, t.isJSXElement)) {
measureJSXExpressions(state, child);
} else if (isPathValid(child, t.isJSXFragment)) {
measureJSXExpressions(state, child);
} else if (isPathValid(child, t.isJSXSpreadChild)) {
measureJSXSpreadChild(state, child);
} else if (isPathValid(child, t.isJSXExpressionContainer)) {
measureJSXExpressionContainer(state, child, true);
} else if (isPathValid(child, t.isJSXText)) {
if (child.node.value.trim() !== '') state.text++;
}
}
}
function shouldTransform(
ctx: StateContext,
name: string,
path: babel.NodePath,
): boolean {
const state: JSXStateContext = {
bailout: false,
elements: 0,
components: 0,
attributes: 0,
text: 0,
returns: 0,
};
path.traverse({
JSXElement(innerPath) {
measureJSXExpressions(state, innerPath);
innerPath.skip();
},
JSXFragment(innerPath) {
measureJSXExpressions(state, innerPath);
innerPath.skip();
},
ReturnStatement(innerPath) {
if (innerPath.scope.uid !== path.scope.uid) return;
state.returns++;
if (state.returns > 1) {
state.bailout = true;
innerPath.stop();
}
},
});
if (state.bailout) return false;
const good = state.elements + state.attributes + state.text;
if (good < 5) return false;
const bad = state.components;
const improvement = (good - bad) / (good + bad);
if (isNaN(improvement) || !isFinite(improvement)) return false;
const threshold =
typeof ctx.options.auto === 'object' && ctx.options.auto.threshold
? ctx.options.auto.threshold
: 0.1;
if (improvement <= threshold) return false;
if (ctx.options.log === true || ctx.options.log === 'info') {
logImprovement(
name,
improvement,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
ctx.options.telemetry,
);
}
return true;
}
function transformFunctionDeclaration(
ctx: StateContext,
program: babel.NodePath<t.Program>,
path: babel.NodePath<t.FunctionDeclaration>,
): void {
if (isStatementTopLevel(path)) {
if (shouldBeIgnored(path)) {
return;
}
const decl = path.node;
// Check if declaration is FunctionDeclaration
if (
// Check if the declaration has an identifier, and then check
decl.id &&
// if the name is component-ish
isComponentishName(decl.id.name) &&
// Might be component-like, but the only valid components
// have zero or one parameter
decl.params.length < 2 &&
// For RSC, only transform if it's a client component
(ctx.options.rsc
? ctx.topLevelRSC || isUseClient(decl.body.directives)
: true) &&
// Check if the function should be transformed
shouldTransform(ctx, decl.id.name, path)
) {
const newPath = program.unshiftContainer(
'body',
t.variableDeclaration('const', [
t.variableDeclarator(
decl.id,
t.callExpression(
getImportIdentifier(
ctx,
path,
TRACKED_IMPORTS.block[ctx.serverMode],
),
[
t.functionExpression(
decl.id,
decl.params,
decl.body,
decl.generator,
decl.async,
),
],
),
),
]),
)[0];
program.scope.registerDeclaration(newPath);
newPath.skip();
if (path.parentPath.isExportNamedDeclaration()) {
path.parentPath.replaceWith(
t.exportNamedDeclaration(undefined, [
t.exportSpecifier(decl.id, decl.id),
]),
);
} else if (path.parentPath.isExportDefaultDeclaration()) {
path.replaceWith(decl.id);
} else {
path.remove();
}
}
}
}
function isValidFunction(
node: t.Node,
): node is t.ArrowFunctionExpression | t.FunctionExpression {
return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node);
}
function transformVariableDeclarator(
ctx: StateContext,
program: babel.NodePath<t.Program>,
path: babel.NodePath<t.VariableDeclarator>,
): void {
if (
path.parentPath.isVariableDeclaration() &&
!isStatementTopLevel(path.parentPath)
) {
return;
}
if (shouldBeIgnored(path)) {
return;
}
const identifier = path.node.id;
if (!t.isIdentifier(identifier)) {
return;
}
const init = path.get('init');
if (isComponentishName(identifier.name) && init.node) {
const trueFuncExpr = unwrapNode(init.node, isValidFunction);
// Check for valid FunctionExpression or ArrowFunctionExpression
if (
trueFuncExpr &&
// Must not be async or generator
!(trueFuncExpr.async || trueFuncExpr.generator) &&
// Might be component-like, but the only valid components
t.isIdentifier(identifier) &&
isComponentishName(identifier.name) &&
// have zero or one parameter
trueFuncExpr.params.length < 2 &&
// For RSC, only transform if it's a client component
(ctx.options.rsc
? ctx.topLevelRSC ||
(t.isBlockStatement(trueFuncExpr.body) &&
isUseClient(trueFuncExpr.body.directives))
: true) &&
// Check if the function should be transformed
shouldTransform(ctx, identifier.name, path)
) {
path.node.init = t.callExpression(
getImportIdentifier(ctx, path, TRACKED_IMPORTS.block[ctx.serverMode]),
[trueFuncExpr],
);
}
}
}
function transformCallExpression(
ctx: StateContext,
path: babel.NodePath<t.CallExpression>,
): void {
if (shouldBeIgnored(path)) {
return;
}
const definition = getValidImportDefinition(ctx, path.get('callee'));
if (definition === REACT_IMPORTS.memo[ctx.serverMode]) {
const args = path.get('arguments');
const arg = args[0];
const trueFuncExpr = unwrapPath(arg, isValidFunction);
if (trueFuncExpr) {
const descriptiveName = getDescriptiveName(trueFuncExpr, 'Anonymous');
if (
// For RSC, only transform if it's a client component
(ctx.options.rsc
? ctx.topLevelRSC ||
(t.isBlockStatement(trueFuncExpr.node.body) &&
isUseClient(trueFuncExpr.node.body.directives))
: true) &&
shouldTransform(ctx, descriptiveName, path)
) {
const root = getRootStatementPath(trueFuncExpr);
const uid = generateUniqueName(trueFuncExpr, descriptiveName);
root.scope.registerDeclaration(
root.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
uid,
t.callExpression(
getImportIdentifier(
ctx,
path,
TRACKED_IMPORTS.block[ctx.serverMode],
),
[trueFuncExpr.node],
),
),
]),
)[0],
);
trueFuncExpr.replaceWith(uid);
}
}
}
}
export function transformAuto(
ctx: StateContext,
program: babel.NodePath<t.Program>,
): void {
// First, identify the HOCs
program.traverse({
ImportDeclaration(path) {
const mod = path.node.source.value;
for (const importName in REACT_IMPORTS) {
const definition = REACT_IMPORTS[importName][ctx.serverMode];
if (definition.source === mod) {
registerImportDefinition(ctx, path, definition);
}
}
},
});
program.traverse({
FunctionDeclaration(path) {
transformFunctionDeclaration(ctx, program, path);
},
VariableDeclarator(path) {
transformVariableDeclarator(ctx, program, path);
},
CallExpression(path) {
transformCallExpression(ctx, path);
},
});
}
================================================
FILE: packages/compiler/babel.ts
================================================
import type { PluginObj, PluginPass } from '@babel/core';
import { transformAuto } from './auto';
import { transformBlock } from './block';
import type { TrackedImports } from './constants';
import { INVERSE_IMPORTS, TRACKED_IMPORTS } from './constants';
import type { CompilerOptions, StateContext } from './types';
import { isUseClient } from './utils/is-use-client';
import { registerImportDefinition } from './utils/register-import-definition';
interface PluginState extends PluginPass {
state: StateContext;
opts: CompilerOptions;
}
export function babel(): PluginObj<PluginState> {
return {
name: 'million',
pre(): void {
this.state = {
options: this.opts,
definitions: {
identifiers: new Map(),
namespaces: new Map(),
},
imports: new Map(),
topLevelRSC: false,
serverMode: this.opts.server ? 'server' : 'client',
};
},
visitor: {
Program(programPath, ctx) {
if (ctx.state.options.rsc) {
ctx.state.topLevelRSC = isUseClient(programPath.node.directives);
}
if (ctx.state.options.auto) {
transformAuto(ctx.state, programPath);
}
programPath.traverse({
ImportDeclaration(path) {
if (
INVERSE_IMPORTS[ctx.state.serverMode].source ===
path.node.source.value
) {
path.node.source.value =
INVERSE_IMPORTS[ctx.state.serverMode].target;
}
const mod = path.node.source.value;
for (const importName in TRACKED_IMPORTS) {
const definition =
TRACKED_IMPORTS[importName as keyof TrackedImports][
ctx.state.serverMode
];
if (definition.source === mod) {
registerImportDefinition(ctx.state, path, definition);
}
}
},
});
},
CallExpression(path, ctx) {
transformBlock(ctx.state, path);
},
},
};
}
================================================
FILE: packages/compiler/block.ts
================================================
import * as t from '@babel/types';
import {
HIDDEN_IMPORTS,
JSX_SKIP_ANNOTATION,
SKIP_ANNOTATION,
SVG_ELEMENTS,
TRACKED_IMPORTS,
} from './constants';
import type { StateContext } from './types';
import { findComment, shouldBeIgnored } from './utils/ast';
import { isComponent, isComponentishName, isPathValid } from './utils/checks';
import { generateUniqueName } from './utils/generate-unique-name';
import { getDescriptiveName } from './utils/get-descriptive-name';
import { getImportIdentifier } from './utils/get-import-specifier';
import { getRootStatementPath } from './utils/get-root-statement-path';
import { getValidImportDefinition } from './utils/get-valid-import-definition';
import { isGuaranteedLiteral } from './utils/is-guaranteed-literal';
import { isJSXComponentElement } from './utils/is-jsx-component-element';
import { unwrapPath } from './utils/unwrap-path';
interface JSXStateContext {
ctx: StateContext;
// The source of array values
source: t.Identifier;
// The expressions from the JSX moved into an array
exprs: t.JSXAttribute[];
keys: t.Expression[];
}
function pushExpression(state: JSXStateContext, expr: t.Expression): string {
const key = `v${state.exprs.length}`;
state.exprs.push(
t.jsxAttribute(
t.jsxIdentifier(key),
t.jsxExpressionContainer(t.cloneNode(expr)),
),
);
return key;
}
function pushExpressionAndReplace(
state: JSXStateContext,
target: babel.NodePath<t.Expression>,
top: boolean,
portal: boolean,
): void {
if (isGuaranteedLiteral(target.node)) {
return;
}
const key = pushExpression(state, target.node);
const expr = t.memberExpression(state.source, t.identifier(key));
target.replaceWith(top ? expr : t.jsxExpressionContainer(expr));
if (portal) {
state.keys.push(t.stringLiteral(key));
}
}
function extractJSXExpressionsFromExpression(
state: JSXStateContext,
expr: babel.NodePath<t.Expression>,
portal: boolean,
): void {
const unwrappedJSX = unwrapPath(expr, t.isJSXElement);
if (unwrappedJSX) {
extractJSXExpressions(state, unwrappedJSX, true);
return;
}
const unwrappedFragment = unwrapPath(expr, t.isJSXFragment);
if (unwrappedFragment) {
extractJSXExpressions(state, unwrappedFragment, true);
return;
}
// TODO Skip if the value is static
pushExpressionAndReplace(state, expr, true, portal);
}
function extractJSXExpressionsFromJSXSpreadChild(
state: JSXStateContext,
path: babel.NodePath<t.JSXSpreadChild>,
): void {
extractJSXExpressionsFromExpression(state, path.get('expression'), false);
}
function extractJSXExpressionsFromJSXExpressionContainer(
state: JSXStateContext,
path: babel.NodePath<t.JSXExpressionContainer>,
portal: boolean,
): void {
const expr = path.get('expression');
if (isPathValid(expr, t.isExpression)) {
extractJSXExpressionsFromExpression(state, expr, portal);
}
}
function extractJSXExpressionsFromJSXAttribute(
state: JSXStateContext,
attr: babel.NodePath<t.JSXAttribute>,
): void {
const value = attr.get('value');
if (isPathValid(value, t.isJSXElement)) {
extractJSXExpressions(state, value, false);
} else if (isPathValid(value, t.isJSXFragment)) {
extractJSXExpressions(state, value, false);
} else if (isPathValid(value, t.isJSXExpressionContainer)) {
extractJSXExpressionsFromJSXExpressionContainer(state, value, false);
}
}
function extractJSXExpressionsFromJSXSpreadAttribute(
state: JSXStateContext,
attr: babel.NodePath<t.JSXSpreadAttribute>,
): void {
extractJSXExpressionsFromExpression(state, attr.get('argument'), false);
}
function extractJSXExpressionsFromJSXAttributes(
state: JSXStateContext,
attrs: babel.NodePath<t.JSXAttribute | t.JSXSpreadAttribute>[],
): void {
// TODO handle specific attributes
for (let i = 0, len = attrs.length; i < len; i++) {
const attr = attrs[i];
if (isPathValid(attr, t.isJSXAttribute)) {
extractJSXExpressionsFromJSXAttribute(state, attr);
} else if (isPathValid(attr, t.isJSXSpreadAttribute)) {
extractJSXExpressionsFromJSXSpreadAttribute(state, attr);
}
}
}
function isJSXSVGElement(path: babel.NodePath<t.JSXElement>): boolean {
const openingElement = path.get('openingElement');
const name = openingElement.get('name');
/**
* Only valid component elements are member expressions and identifiers
* starting with component-ish name
*/
if (isPathValid(name, t.isJSXIdentifier)) {
if (SVG_ELEMENTS.includes(name.node.name)) {
return true;
}
}
return false;
}
function isJSXForElement(
ctx: StateContext,
path: babel.NodePath<t.JSXElement>,
): boolean {
const openingElement = path.get('openingElement');
const name = openingElement.get('name');
/**
* Only valid component elements are member expressions and identifiers
* starting with component-ish name
*/
if (
isPathValid(name, t.isJSXIdentifier) ||
isPathValid(name, t.isJSXMemberExpression)
) {
return (
getValidImportDefinition(ctx, name) ===
TRACKED_IMPORTS.For[ctx.serverMode]
);
}
return false;
}
function transformJSXForElement(
ctx: StateContext,
path: babel.NodePath<t.JSXElement>,
): void {
if (isJSXForElement(ctx, path)) {
path.node.openingElement.attributes.push(
t.jsxAttribute(t.jsxIdentifier('scoped')),
);
}
}
function extractJSXExpressionsFromJSXElement(
state: JSXStateContext,
path: babel.NodePath<t.JSXElement>,
top: boolean,
): boolean {
const openingElement = path.get('openingElement');
/**
* If this is a component element, move the JSX expression
* to the expression array.
*/
if (isJSXComponentElement(path)) {
transformJSXForElement(state.ctx, path);
pushExpressionAndReplace(state, path, top, true);
return true;
}
/**
* Otherwise, continue extracting in attributes
*/
extractJSXExpressionsFromJSXAttributes(
state,
openingElement.get('attributes'),
);
return false;
}
function extractJSXChildren(
state: JSXStateContext,
children: babel.NodePath<t.JSXElement['children'][0]>[],
): t.JSXElement['children'] {
const newChildren: t.JSXElement['children'] = [];
for (let i = 0, len = children.length; i < len; i++) {
const child = children[i];
if (isPathValid(child, t.isJSXElement)) {
extractJSXExpressions(state, child, false);
} else if (isPathValid(child, t.isJSXFragment)) {
Array.prototype.push.apply(
newChildren,
extractJSXChildren(state, child.get('children')),
);
} else if (isPathValid(child, t.isJSXSpreadChild)) {
extractJSXExpressionsFromJSXSpreadChild(state, child);
} else if (isPathValid(child, t.isJSXExpressionContainer)) {
extractJSXExpressionsFromJSXExpressionContainer(state, child, true);
}
if (child && !isPathValid(child, t.isJSXFragment)) {
newChildren.push(child.node);
}
}
return newChildren;
}
function extractJSXExpressions(
state: JSXStateContext,
path: babel.NodePath<t.JSXElement | t.JSXFragment>,
top: boolean,
): void {
/**
* Check if JSX should be skipped for children extraction
* We only do this since Component elements
* cannot be in the compiledBlock JSX
*/
if (
isPathValid(path, t.isJSXElement) &&
extractJSXExpressionsFromJSXElement(state, path, top)
) {
return;
}
if (isPathValid(path, t.isJSXFragment)) {
path.replaceWith(
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier('slot'), []),
t.jsxClosingElement(t.jsxIdentifier('slot')),
path.node.children,
),
);
}
path.node.children = extractJSXChildren(state, path.get('children'));
}
function transformJSX(
ctx: StateContext,
path: babel.NodePath<t.JSXElement | t.JSXFragment>,
): void {
/**
* For top-level JSX, we skip at elements that are constructed from components
*/
// if (isPathValid(path, t.isJSXElement) && isJSXComponentElement(path)) {
if (findComment(path.node, JSX_SKIP_ANNOTATION)) {
path.skip();
return;
}
const state: JSXStateContext = {
ctx,
source: path.scope.generateUidIdentifier('props'),
exprs: [],
keys: [],
};
/**
* Begin extracting expressions/portals
*/
extractJSXExpressions(
state,
path,
!(
isPathValid(path.parentPath, t.isJSXElement) ||
isPathValid(path.parentPath, t.isJSXFragment) ||
isPathValid(path.parentPath, t.isJSXAttribute)
),
);
/**
* Get the nearest descriptive name
*/
const descriptiveName = getDescriptiveName(path, 'Anonymous');
/**
* Generate a new name based on the descriptive name
*/
const id = generateUniqueName(
path,
isComponentishName(descriptiveName)
? descriptiveName
: `JSX_${descriptiveName}`,
);
if (ctx.options.hmr) {
state.exprs.push(
t.jsxAttribute(
t.jsxIdentifier('_hmr'),
t.stringLiteral(String(Date.now())),
),
);
}
/**
* The following are arguments for the new render function
*/
const args: t.Identifier[] = [];
/**
* If there are any extracted expressions, declare the argument
*/
if (state.exprs.length) {
args.push(state.source);
} else {
path.scope.removeBinding(state.source.name);
}
/**
* The new "render" function
*/
const newComponent = t.arrowFunctionExpression(args, path.node);
/**
* Following are the `compiledBlock` options
*/
const options = [
// TODO add dev mode
t.objectProperty(t.identifier('name'), t.stringLiteral(id.name)),
];
/**
* If there are any portals, declare them in the options
*/
if (state.keys.length) {
options.push(
t.objectProperty(t.identifier('portals'), t.arrayExpression(state.keys)),
);
}
if (isPathValid(path, t.isJSXElement) && isJSXSVGElement(path)) {
options.push(t.objectProperty(t.identifier('svg'), t.booleanLiteral(true)));
}
/**
* Generate the new compiledBlock
*/
const generatedBlock = t.variableDeclaration('const', [
t.variableDeclarator(
id,
t.callExpression(
getImportIdentifier(
ctx,
path,
HIDDEN_IMPORTS.compiledBlock[ctx.serverMode],
),
[newComponent, t.objectExpression(options)],
),
),
]);
const rootPath = getRootStatementPath(path);
rootPath.scope.registerDeclaration(rootPath.insertBefore(generatedBlock)[0]);
path.replaceWith(
t.addComment(
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier(id.name), state.exprs, true),
t.jsxClosingElement(t.jsxIdentifier(id.name)),
[],
true,
),
'leading',
JSX_SKIP_ANNOTATION,
),
);
}
export function transformBlock(
ctx: StateContext,
path: babel.NodePath<t.CallExpression>,
): void {
if (shouldBeIgnored(path)) {
return;
}
const definition = getValidImportDefinition(ctx, path.get('callee'));
// Check first if the call is a valid `block` call
if (TRACKED_IMPORTS.block[ctx.serverMode] !== definition) {
return;
}
// Check if we should skip because the compiler
// can also output a `block` call. Without this,
// compiler will suffer a recursion.
if (findComment(path.node, SKIP_ANNOTATION)) {
return;
}
const args = path.get('arguments');
// Make sure that we have at least one argument,
// and that argument is a component.
if (args.length <= 0) {
return;
}
const identifier = unwrapPath(args[0]!, t.isIdentifier);
// Handle identifiers differently
if (identifier) {
return;
}
const component = unwrapPath(args[0]!, isComponent);
if (!component) {
return;
}
// Transform all top-level JSX (aka the JSX owned by the component)
component.traverse({
JSXElement(childPath) {
const functionParent = childPath.getFunctionParent();
if (functionParent === component) {
transformJSX(ctx, childPath);
}
},
JSXFragment(childPath) {
const functionParent = childPath.getFunctionParent();
if (functionParent === component) {
transformJSX(ctx, childPath);
}
},
});
// TODO this isn't a good check
// if (isPathValid(component, t.isArrowFunctionExpression) && t.isExpression(component.node.body)) {
// const root = getRootStatementPath(component);
// const descriptiveName = getDescriptiveName(component, 'Anonymous');
// const uniqueName = generateUniqueName(
// component,
// isComponentishName(descriptiveName)
// ? descriptiveName
// : `JSX_${descriptiveName}`,
// );
// root.insertBefore([
// t.variableDeclaration('const', [
// t.variableDeclarator(uniqueName, component.node),
// ]),
// t.expressionStatement(
// t.assignmentExpression(
// '=',
// t.memberExpression(
// uniqueName,
// t.identifier('_c'),
// ),
// t.booleanLiteral(true),
// ),
// ),
// ]);
// path.replaceWith(uniqueName);
// } else {
path.replaceWith(component);
// }
}
================================================
FILE: packages/compiler/constants.ts
================================================
import type { ImportDefinition } from './types';
export const RENDER_SCOPE = 'slot';
export const SKIP_ANNOTATION = '@million skip';
export const IGNORE_ANNOTATION = 'million-ignore';
export const JSX_SKIP_ANNOTATION = '@million jsx-skip';
export const SVG_ELEMENTS = [
'circle',
'ellipse',
'foreignObject',
'image',
'line',
'path',
'polygon',
'polyline',
'rect',
'text',
'textPath',
'tspan',
'svg',
'g',
];
export const NO_PX_PROPERTIES = [
'animationIterationCount',
'boxFlex',
'boxFlexGroup',
'boxOrdinalGroup',
'columnCount',
'fillOpacity',
'flex',
'flexGrow',
'flexPositive',
'flexShrink',
'flexNegative',
'flexOrder',
'fontWeight',
'lineClamp',
'lineHeight',
'opacity',
'order',
'orphans',
'stopOpacity',
'strokeDashoffset',
'strokeOpacity',
'strokeWidth',
'tabSize',
'widows',
'zIndex',
'zoom',
// SVG-related properties
'fillOpacity',
'floodOpacity',
'stopOpacity',
'strokeDasharray',
'strokeDashoffset',
'strokeMiterlimit',
'strokeOpacity',
'strokeWidth',
];
export interface TrackedImports {
block: {
client: ImportDefinition;
server: ImportDefinition;
};
For: {
client: ImportDefinition;
server: ImportDefinition;
};
}
export const INVERSE_IMPORTS = {
client: {
source: 'million/react-server',
target: 'million/react',
},
server: {
source: 'million/react',
target: 'million/react-server',
},
};
export const TRACKED_IMPORTS: TrackedImports = {
block: {
client: {
kind: 'named',
name: 'block',
source: 'million/react',
},
server: {
kind: 'named',
name: 'block',
source: 'million/react-server',
},
},
For: {
client: {
kind: 'named',
name: 'For',
source: 'million/react',
},
server: {
kind: 'named',
name: 'For',
source: 'million/react-server',
},
},
};
export interface HiddenImports {
compiledBlock: {
client: ImportDefinition;
server: ImportDefinition;
};
}
export const HIDDEN_IMPORTS: HiddenImports = {
compiledBlock: {
client: {
kind: 'named',
name: 'compiledBlock',
source: 'million/react',
},
server: {
kind: 'named',
name: 'compiledBlock',
source: 'million/react-server',
},
},
};
interface ReactImports {
memo: {
client: ImportDefinition;
server: ImportDefinition;
};
}
export const REACT_IMPORTS: ReactImports = {
memo: {
client: {
kind: 'named',
name: 'memo',
source: 'react',
},
server: {
kind: 'named',
name: 'memo',
source: 'react',
},
},
};
================================================
FILE: packages/compiler/experimental/optimize.ts
================================================
import * as t from '@babel/types';
// import { addNamed } from '@babel/helper-module-imports';
import type { NodePath } from '@babel/core';
import { EventFlag } from '../../million/constants';
import type { Info } from '../babel';
import { addImport } from '../utils/is-use-client';
import { getUniqueId } from '../utils/id';
import { hoistElements, renderToString, renderToTemplate } from './render';
import { createDirtyChecker, createEdit } from './utils';
import type { IrEdit, IrEditBase } from './types';
export const optimize = (
callExpressionPath: NodePath<t.CallExpression>,
info: Info,
holes: string[],
jsx: t.JSXElement,
): {
blockFactory: t.ArrowFunctionExpression;
variables: t.VariableDeclaration;
} => {
const edits: IrEdit[] = [];
const template = renderToTemplate(jsx, edits, [], holes);
const paths: number[][] = [];
let maxPathLength = 0;
for (let i = 0, j = edits.length; i < j; ++i) {
const path = edits[i]?.path || [];
if (path.length > maxPathLength) maxPathLength = path.length;
paths.push(path);
}
const { declarators, accessedIds } = hoistElements(
paths,
callExpressionPath,
'million',
);
const editsArray = t.arrayExpression(
edits.map((edit) => {
const editsProperties: t.ObjectExpression[] = [];
const initsProperties: t.ObjectExpression[] = [];
for (let i = 0, j = edit.edits.length; i < j; ++i) {
const { type, name, hole, listener, value, index } = edit.edits[
i
] as IrEditBase;
editsProperties.push(
createEdit({
type,
name,
hole,
index,
listener,
value,
patch: value,
block: value,
}),
);
}
for (let i = 0, j = edit.inits.length; i < j; ++i) {
const { type, name, hole, listener, value, index } = edit.inits[i]!;
if (type.value === EventFlag) {
initsProperties.push(
createEdit({
type,
name,
hole,
index,
listener,
value,
patch: value,
block: value,
}),
);
} else {
initsProperties.push(
createEdit({
type,
hole: t.nullLiteral(),
index,
listener: t.nullLiteral(),
value,
patch: t.nullLiteral(),
block: t.nullLiteral(),
}),
);
}
}
return t.objectExpression([
t.objectProperty(t.identifier('p'), t.arrayExpression()),
t.objectProperty(t.identifier('e'), t.arrayExpression(editsProperties)),
t.objectProperty(
t.identifier('i'),
initsProperties.length
? t.arrayExpression(initsProperties)
: t.arrayExpression(),
),
]);
}),
);
const stringToDOM = addImport(
callExpressionPath,
'stringToDOM',
'million',
info,
);
const domVariable = getUniqueId(callExpressionPath, 'd');
const editsVariable = getUniqueId(callExpressionPath, 'e');
const shouldUpdateVariable = getUniqueId(callExpressionPath, 'su');
const getElementsVariable = getUniqueId(callExpressionPath, 'ge');
const variables = t.variableDeclaration('const', [
t.variableDeclarator(
domVariable,
t.callExpression(stringToDOM, [
t.templateLiteral(
[
t.templateElement({
raw: renderToString(template),
}),
],
[],
),
]),
),
t.variableDeclarator(editsVariable, editsArray),
t.variableDeclarator(shouldUpdateVariable, createDirtyChecker(holes)),
t.variableDeclarator(
getElementsVariable,
declarators.length
? t.arrowFunctionExpression(
[t.identifier('root')],
t.blockStatement([
t.variableDeclaration('const', declarators),
t.returnStatement(t.arrayExpression(accessedIds)),
]),
)
: t.nullLiteral(),
),
]);
const BlockClass = addNamed(callExpressionPath, 'Block', 'million', {
nameHint: 'Block$',
});
const blockFactory = t.arrowFunctionExpression(
[t.identifier('props'), t.identifier('key'), t.identifier('shouldUpdate')],
t.blockStatement([
t.returnStatement(
t.newExpression(BlockClass, [
domVariable,
editsVariable,
t.identifier('props'),
t.logicalExpression(
'??',
t.identifier('key'),
t.memberExpression(t.identifier('props'), t.identifier('key')),
),
t.logicalExpression(
'??',
t.identifier('shouldUpdate'),
shouldUpdateVariable,
),
getElementsVariable,
]),
),
]),
);
return {
blockFactory,
variables,
};
};
================================================
FILE: packages/compiler/experimental/render.ts
================================================
import * as t from '@babel/types';
// import { addNamed } from '@babel/helper-module-imports';
import type { NodePath } from '@babel/core';
import {
X_CHAR,
EventFlag,
StyleAttributeFlag,
SvgAttributeFlag,
AttributeFlag,
ChildFlag,
} from '../../million/constants';
import type { IrEdit, IrPrunedNode, IrTreeNode } from './types';
export const renderToString = (node: t.JSXElement): string => {
const type = node.openingElement.name as t.JSXIdentifier;
const attributes = node.openingElement.attributes;
let html = `<${type.name}`;
for (let i = 0, j = attributes.length; i < j; i++) {
const attribute = attributes[i];
if (
!t.isJSXSpreadAttribute(attribute) &&
attribute?.value &&
t.isJSXIdentifier(attribute.name) &&
'value' in attribute.value
) {
const { name } = attribute.name;
const { value } = attribute.value;
html += ` ${name}${value ? `="${value}"` : ''}`;
}
}
if (node.selfClosing) {
html += ` />`;
return html;
}
html += '>';
if (node.children.length) {
for (let i = 0, j = node.children.length; i < j; i++) {
const child = node.children[i];
if (t.isJSXText(child)) {
html += child.value.trim();
} else if (t.isJSXElement(child)) {
html += renderToString(child);
} else if (t.isJSXExpressionContainer(child)) {
if (t.isStringLiteral(child.expression)) {
html += child.expression.value;
} else if (t.isNumericLiteral(child.expression)) {
html += child.expression.value;
} else if (t.isJSXElement(child.expression)) {
html += renderToString(child.expression);
}
}
}
}
html += `</${type.name}>`;
return html;
};
export const renderToTemplate = (
node: t.JSXElement,
edits: IrEdit[],
path: number[] = [],
holes: string[] = [],
): t.JSXElement => {
const attributesLength = node.openingElement.attributes.length;
const current: IrEdit = {
path, // The location of the edit in in the virtual node tree
edits: [], // Occur on mount + patch
inits: [], // Occur before mount
};
if (attributesLength) {
const newAttributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [];
for (let i = 0; i < attributesLength; ++i) {
const attribute = node.openingElement.attributes[i];
if (
t.isJSXAttribute(attribute) &&
t.isJSXIdentifier(attribute.name) &&
attribute.value
) {
const name = attribute.name.name;
if (name === 'key' || name === 'ref' || name === 'children') {
continue;
}
if (name === 'className') attribute.name.name = 'class';
if (name === 'htmlFor') attribute.name.name = 'for';
if (name.startsWith('on')) {
// We assume that the value is a function, so it must always be an expression;
if (!t.isJSXExpressionContainer(attribute.value)) continue;
const { expression } = attribute.value;
if (
!t.isIdentifier(expression) &&
!t.isArrowFunctionExpression(expression)
) {
continue;
}
const isDynamicListener =
t.isIdentifier(expression) && holes.includes(expression.name);
const event = name.toLowerCase().slice(2);
if (isDynamicListener) {
current.edits.push({
type: t.numericLiteral(EventFlag),
name: t.stringLiteral(event),
hole: t.stringLiteral(expression.name),
});
} else {
current.inits.push({
type: t.numericLiteral(EventFlag),
listener: expression,
name: t.stringLiteral(event),
});
}
continue;
}
if (name === 'style') {
if (!t.isJSXExpressionContainer(attribute.value)) continue;
const { expression } = attribute.value;
if (!t.isObjectExpression(expression)) continue;
let style = '';
for (let i = 0, j = expression.properties.length; i < j; ++i) {
const property = expression.properties[i];
if (
!t.isObjectProperty(property) ||
!t.isIdentifier(property.key) ||
(!t.isStringLiteral(property.value) &&
!t.isNumericLiteral(property.value))
)
continue;
if (!t.isIdentifier(property.key)) continue;
const value = property.value.extra?.raw || '';
style += `${property.key.name}:${String(value)};`;
}
attribute.value = t.stringLiteral(style);
continue;
}
if (t.isJSXExpressionContainer(attribute.value)) {
if (
t.isStringLiteral(attribute.value.expression) ||
t.isNumericLiteral(attribute.value.expression)
) {
newAttributes.push(
t.jsxAttribute(
attribute.name,
t.stringLiteral(String(attribute.value.expression.value)),
),
);
continue;
}
const { expression } = attribute.value as { expression: any };
current.edits.push({
type: t.numericLiteral(
name === 'style'
? StyleAttributeFlag
: name.charCodeAt(0) === X_CHAR
? SvgAttributeFlag
: AttributeFlag,
),
hole: t.stringLiteral(expression.name),
name: t.stringLiteral(name),
});
continue;
}
}
if (
attribute &&
'value' in attribute &&
attribute.value &&
t.isJSXAttribute(attribute)
) {
newAttributes.push(attribute);
}
}
node.openingElement.attributes = newAttributes;
}
const newChildren: (
| t.JSXElement
| t.JSXText
| t.JSXExpressionContainer
| t.JSXSpreadChild
| t.JSXFragment
)[] = [];
let canMergeString = false;
for (let i = 0, j = node.children.length || 0, k = 0; i < j; ++i) {
const child = node.children[i];
if (
t.isJSXExpressionContainer(child) &&
t.isIdentifier(child.expression) &&
holes.includes(child.expression.name)
) {
current.edits.push({
type: t.numericLiteral(ChildFlag),
hole: t.stringLiteral(child.expression.name),
index: t.numericLiteral(i),
name: undefined,
listener: undefined,
value: undefined,
});
continue;
}
if (
t.isJSXText(child) &&
(typeof child.value === 'string' ||
typeof child.value === 'number' ||
typeof child.value === 'bigint')
) {
const value = String(child.value);
if (value.trim() === '') continue;
if (canMergeString) {
current.inits.push({
type: t.numericLiteral(ChildFlag),
value: t.stringLiteral(value),
index: t.numericLiteral(i),
});
continue;
}
canMergeString = true;
newChildren.push(t.jsxText(value));
k++;
continue;
}
canMergeString = false;
if (t.isJSXElement(child)) {
newChildren.push(renderToTemplate(child, edits, path.concat(k++), holes));
}
}
node.children = newChildren;
if (current.inits.length || current.edits.length) {
edits.push(current);
}
return node;
};
/**
* Direct credit to Alexis H. Munsayac (https://github.com/LXSMNSYC)
* for the original implementation of this algorithm.
*/
export const hoistElements = (
paths: number[][],
path: NodePath<t.CallExpression>,
sourceName: string,
): {
declarators: t.VariableDeclarator[];
accessedIds: t.Identifier[];
} => {
const createTreeNode = (): IrTreeNode => ({
children: [],
path: undefined,
});
const createPrunedNode = (
index: number,
parent?: IrPrunedNode,
): IrPrunedNode => ({
index,
parent,
path: undefined,
child: undefined,
next: undefined,
prev: undefined,
});
const tree = createTreeNode();
for (let i = 0, j = paths.length; i < j; i++) {
const path = paths[i]!;
let prev = tree;
for (let k = 0, l = path.length; k < l; k++) {
const index = path[k]!;
prev.children[index] ||= createTreeNode();
prev = prev.children[index]!;
if (k === l - 1) {
prev.path ||= [];
prev.path.push(i);
}
}
}
const prune = (node: IrTreeNode, parent: IrPrunedNode): void => {
let prev = parent;
for (let i = 0, j = node.children.length; i < j; i++) {
const treeNode = node.children[i];
const current = createPrunedNode(i, parent);
if (prev === parent) {
prev.child = current;
} else {
current.prev = prev;
prev.next = current;
}
prev = current;
if (treeNode) {
prev.path = treeNode.path;
prune(treeNode, current);
}
}
};
const root = createPrunedNode(0);
prune(tree, root);
const getId = (): t.Identifier => path.scope.generateUidIdentifier('el$');
const firstChild = addNamed(path, 'firstChild$', sourceName);
const nextSibling = addNamed(path, 'nextSibling$', sourceName);
const declarators: t.VariableDeclarator[] = [];
const accessedIds: t.Identifier[] = Array(paths.length).fill(
t.identifier('root'),
);
const traverse = (
node: IrPrunedNode,
prev: t.Identifier | t.CallExpression,
isParent?: boolean,
): void => {
if (isParent) {
prev = t.callExpression(
t.memberExpression(firstChild, t.identifier('call')),
[prev],
);
for (let i = 0, j = node.index; i < j; i++) {
prev = t.callExpression(
t.memberExpression(nextSibling, t.identifier('call')),
[prev],
);
}
} else {
for (let i = 0, j = node.index - (node.prev?.index ?? 0); i < j; i++) {
prev = t.callExpression(
t.memberExpression(nextSibling, t.identifier('call')),
[prev],
);
}
}
if (node.child && node.next) {
const id = getId();
declarators.push(t.variableDeclarator(id, prev));
prev = id;
if (node.path !== undefined) {
for (let i = 0, j = node.path.length; i < j; ++i) {
accessedIds[node.path[i]!] = id;
}
}
} else if (node.path !== undefined) {
const id = getId();
declarators.push(t.variableDeclarator(id, prev));
for (let i = 0, j = node.path.length; i < j; ++i) {
accessedIds[node.path[i]!] = id;
}
prev = id;
}
if (node.next) {
traverse(node.next, prev, false);
}
if (node.child) {
traverse(node.child, prev, true);
}
};
if (root.child) {
traverse(root.child, t.identifier('root'), true);
}
return {
declarators,
accessedIds,
};
};
================================================
FILE: packages/compiler/experimental/types.ts
================================================
import type {
ArrowFunctionExpression,
StringLiteral,
NumericLiteral,
Identifier,
} from '@babel/types';
export interface IrEditBase {
type: NumericLiteral;
name?: StringLiteral;
value?: StringLiteral;
hole?: StringLiteral;
index?: NumericLiteral;
listener?: ArrowFunctionExpression | Identifier;
}
export interface IrEditAttribute extends IrEditBase {
type: NumericLiteral;
hole: StringLiteral;
name: StringLiteral;
}
export interface IrEditStyleAttribute extends IrEditBase {
type: NumericLiteral;
hole: StringLiteral;
name: StringLiteral;
}
export interface IrEditSvgAttribute extends IrEditBase {
type: NumericLiteral;
hole: StringLiteral;
name: StringLiteral;
}
export interface IrEditChild extends IrEditBase {
type: NumericLiteral;
hole: StringLiteral;
index: NumericLiteral;
}
export interface IrEditBlock extends IrEditBase {
type: NumericLiteral;
index: NumericLiteral;
}
export interface IrEditEvent extends IrEditBase {
type: NumericLiteral;
hole: StringLiteral;
name: StringLiteral;
}
export interface IrInitEvent extends IrEditBase {
type: NumericLiteral;
name: StringLiteral;
listener: ArrowFunctionExpression | Identifier;
}
export interface IrInitChild extends IrEditBase {
type: NumericLiteral;
value: StringLiteral;
index: NumericLiteral;
}
export interface IrEdit {
path: number[];
edits: (
| IrEditAttribute
| IrEditStyleAttribute
| IrEditSvgAttribute
| IrEditChild
| IrEditBlock
| IrEditEvent
)[];
inits: (IrInitEvent | IrInitChild)[];
}
export interface IrTreeNode {
children: IrTreeNode[];
path?: number[];
}
export interface IrPrunedNode {
index: number;
parent?: IrPrunedNode;
path?: number[];
child?: IrPrunedNode;
next?: IrPrunedNode;
prev?: IrPrunedNode;
}
================================================
FILE: packages/compiler/experimental/utils.ts
================================================
import * as t from '@babel/types';
export const createEdit = ({
type,
name,
value,
hole,
index,
listener,
patch,
block,
}: {
type: t.NumericLiteral;
name?: t.StringLiteral;
value?: t.Expression;
hole?: t.Expression;
index?: t.NumericLiteral;
listener?: t.Expression;
patch?: t.Expression;
block?: t.Expression;
}) => {
return t.objectExpression([
t.objectProperty(t.identifier('t'), type),
t.objectProperty(t.identifier('n'), name ?? t.nullLiteral()),
t.objectProperty(t.identifier('v'), value ?? t.nullLiteral()),
t.objectProperty(t.identifier('h'), hole ?? t.nullLiteral()),
t.objectProperty(t.identifier('i'), index ?? t.nullLiteral()),
t.objectProperty(t.identifier('l'), listener ?? t.nullLiteral()),
t.objectProperty(t.identifier('p'), patch ?? t.nullLiteral()),
t.objectProperty(t.identifier('b'), block ?? t.nullLiteral()),
]);
};
export const chainOrLogic = (
...binaryExpressions: t.BinaryExpression[]
): t.LogicalExpression | t.BinaryExpression | t.BooleanLiteral => {
if (binaryExpressions.length === 0) return t.booleanLiteral(true);
if (binaryExpressions.length === 1) return binaryExpressions[0]!;
const [first, ...rest] = binaryExpressions;
return t.logicalExpression('||', first!, chainOrLogic(...rest));
};
export const createDirtyChecker = (holes: string[]) => {
const oldProps = t.identifier('a');
const newProps = t.identifier('b');
if (holes.length === 0) return null;
return t.arrowFunctionExpression(
[oldProps, newProps],
chainOrLogic(
...holes.map((hole) => {
const id = t.identifier(hole);
return t.binaryExpression(
'!==',
t.optionalMemberExpression(oldProps, id, false, true),
t.optionalMemberExpression(newProps, id, false, true),
);
}),
),
);
};
================================================
FILE: packages/compiler/for.old.ts
================================================
/**
* THIS FILE IS DEPRECATED
*
* The file is no longer being used due to a broader
* compilation process that covers this. This is only
* preserved for future references.
*/
import * as t from '@babel/types';
import type { NodePath } from '@babel/core';
import {
dedupeJSXAttributes,
trimJSXChildren,
isAttributeUnsupported,
isComponent,
} from './utils/jsx';
import { deopt, catchError } from './utils/log';
import { findChild } from './utils/ast';
import type { Info } from './babel';
import { transformBlock } from './block';
import { getUniqueId } from './utils/id';
export const transformFor = (
jsxElementPath: NodePath<t.JSXElement>,
info: Info,
) => {
if (!info.imports.source) return;
const jsxElement = jsxElementPath.node;
const jsxId = jsxElement.openingElement.name;
if (
!t.isJSXIdentifier(jsxId) ||
!info.imports.For ||
!info.imports.aliases.For?.has(jsxId.name)
) {
return;
}
const expression = validateForExpression(jsxElementPath, info);
if (hoistFor(jsxElementPath)) return;
const callbackComponentId = getUniqueId(jsxElementPath, 'ForCallback');
const blockComponentId = getUniqueId(jsxElementPath, 'ForBody');
const idNames = new Set<string>();
for (let i = 0, j = expression.params.length; i < j; ++i) {
const id = expression.params[i]!;
if (t.isIdentifier(id)) {
idNames.add(id.name);
continue;
}
if (!t.isObjectPattern(id)) continue;
for (let k = 0, l = id.properties.length; k < l; ++k) {
const prop = id.properties[k]!;
if (!t.isObjectProperty(prop)) continue;
if (t.isIdentifier(prop.key)) {
idNames.add(prop.key.name);
} else if (t.isStringLiteral(prop.key)) {
idNames.add(prop.key.value);
}
}
}
let bailout = false;
jsxElementPath.traverse({
JSXElement(path) {
const jsxId = path.node.openingElement.name;
if (t.isJSXIdentifier(jsxId) && isComponent(jsxId.name)) {
path.stop();
bailout = true;
}
},
JSXAttribute(path) {
const jsxId = path.node.name;
if (!t.isJSXIdentifier(jsxId)) return;
if (jsxId.name === 'ref' || isAttributeUnsupported(path.node)) {
path.stop();
bailout = true;
}
},
JSXSpreadAttribute(path) {
path.stop();
bailout = true;
},
JSXSpreadChild(path) {
path.stop();
bailout = true;
},
Identifier(path: NodePath<t.Identifier>) {
if (info.programPath.scope.hasBinding(path.node.name)) return;
const targetPath = path.parentPath;
if (targetPath.isMemberExpression()) {
if (!targetPath.node.computed && targetPath.node.object !== path.node)
return;
if (targetPath.parentPath.isCallExpression()) {
if (targetPath.parentPath.node.callee !== targetPath.node) return;
}
}
if (targetPath.isObjectProperty() && !targetPath.node.computed) {
if (targetPath.node.key !== path.node) return;
}
if (
targetPath.isObjectMethod() ||
targetPath.isJSXAttribute() ||
targetPath.isJSXOpeningElement() ||
targetPath.isJSXClosingElement()
) {
return;
}
if (
targetPath.isFunctionExpression() ||
targetPath.isArrowFunctionExpression()
) {
if (targetPath.node.params.some((param) => param === path.node)) {
return;
}
}
if (!path.scope.hasBinding(path.node.name)) return;
idNames.add(path.node.name);
},
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (bailout) {
jsxElementPath.stop();
return;
}
const ids = [...idNames].map((id) => t.identifier(id));
const body = t.cloneNode(expression.body);
// We do a similar extraction process as in the call expression visitor
const originalComponent = t.variableDeclaration('const', [
t.variableDeclarator(
callbackComponentId,
t.arrowFunctionExpression(
[
t.objectPattern(
ids.map((id) => t.objectProperty(id, id, false, true)),
),
],
t.isBlockStatement(body)
? body
: t.blockStatement([t.returnStatement(body)]),
),
),
]);
const blockComponent = t.variableDeclaration('const', [
t.variableDeclarator(
blockComponentId,
t.callExpression(t.identifier(info.imports.block!), [
callbackComponentId,
]),
),
]);
const [originalComponentPath] = info.programPath.pushContainer(
'body',
originalComponent,
);
info.programPath.scope.registerDeclaration(originalComponentPath);
const [blockComponentPath] = info.programPath.pushContainer(
'body',
blockComponent,
);
info.programPath.scope.registerDeclaration(blockComponentPath);
expression.body = t.callExpression(blockComponentId, [
t.objectExpression(ids.map((id) => t.objectProperty(id, id))),
]);
const callExpressionPath = findChild<t.CallExpression>(
jsxElementPath,
'CallExpression',
);
if (!callExpressionPath || !callExpressionPath.isCallExpression()) return;
catchError(() => {
transformBlock(callExpressionPath, info);
}, info.options.log);
};
export const validateForExpression = (
jsxElementPath: NodePath<t.JSXElement>,
info: Info,
): t.ArrowFunctionExpression => {
const jsxElement = jsxElementPath.node;
const VALIDATION_MESSAGE = 'Invalid For usage: https://million.dev/docs/for';
trimJSXChildren(jsxElement);
if (jsxElement.children.length !== 1) {
throw deopt(VALIDATION_MESSAGE, info.filename, jsxElementPath);
}
const child = jsxElement.children[0];
if (!t.isJSXExpressionContainer(child)) {
throw deopt(VALIDATION_MESSAGE, info.filename, jsxElementPath);
}
const expression = child.expression;
if (!t.isArrowFunctionExpression(expression)) {
throw deopt(VALIDATION_MESSAGE, info.filename, jsxElementPath);
}
if (t.isBlockStatement(expression.body)) {
const blockStatementPath = jsxElementPath.get(
'children.0.expression.body',
)[0]!;
const returnStatementPath = findChild<t.ReturnStatement>(
blockStatementPath,
'ReturnStatement',
);
if (returnStatementPath === null) {
throw deopt(VALIDATION_MESSAGE, info.filename, jsxElementPath);
}
const argument = returnStatementPath.node.argument;
if (!t.isJSXElement(argument) && !t.isJSXFragment(argument)) {
throw deopt(VALIDATION_MESSAGE, info.filename, jsxElementPath);
}
} else if (
!t.isJSXElement(expression.body) &&
!t.isJSXFragment(expression.body)
) {
throw deopt(VALIDATION_MESSAGE, info.filename, jsxElementPath);
}
return expression;
};
export const hoistFor = (jsxElementPath: NodePath<t.JSXElement>) => {
const jsxElement = jsxElementPath.node;
const jsxElementParent = jsxElementPath.parent;
if (t.isJSXElement(jsxElementParent)) {
const type = jsxElementParent.openingElement.name;
trimJSXChildren(jsxElementParent);
if (
t.isJSXIdentifier(type) &&
type.name.toLowerCase() === type.name &&
jsxElementParent.children.length === 1
) {
if (
!jsxElement.openingElement.attributes.some(
(attr) => t.isJSXAttribute(attr) && attr.name.name === 'as',
)
) {
const jsxElementClone = t.cloneNode(jsxElement);
jsxElementClone.openingElement.attributes = dedupeJSXAttributes([
...jsxElementClone.openingElement.attributes,
...jsxElementParent.openingElement.attributes,
t.jsxAttribute(t.jsxIdentifier('as'), t.stringLiteral(type.name)),
]);
jsxElementPath.parent = jsxElementClone;
return true;
}
}
}
};
================================================
FILE: packages/compiler/index.ts
================================================
import { existsSync } from 'node:fs';
import { babel } from './babel';
import type { Options } from './plugin';
import { unplugin } from './plugin';
export const vite = unplugin.vite;
export const webpack = unplugin.webpack;
export const rollup = unplugin.rollup;
export const rspack = unplugin.rspack;
export const esbuild = unplugin.esbuild;
export const next = (
nextConfig: {
appDir?: boolean;
basePath?: string;
webpack?: (config: Record<string, any>, options: any) => any;
} = {},
overrideOptions: Options = {},
): any => {
const millionConfig: Options = {
mode: 'react',
...overrideOptions,
};
return {
...nextConfig,
webpack(
config: Record<string, any>,
webpackOptions: {
dir: string;
isServer: boolean;
},
) {
if (millionConfig.rsc === undefined) {
millionConfig.rsc =
nextConfig.appDir ??
existsSync(`${webpackOptions.dir}${nextConfig.basePath || ''}/app`);
}
config.plugins.unshift(
webpack({
server: webpackOptions.isServer,
log: webpackOptions.isServer,
...millionConfig,
}),
);
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, webpackOptions);
}
return config;
},
};
};
export { babel };
export default {
vite,
webpack,
rollup,
rspack,
esbuild,
next,
unplugin,
babel,
};
================================================
FILE: packages/compiler/package.json
================================================
{
"private": true,
"main": "./index.ts",
"module": "./index.ts",
"devDependencies": {
"@rollup/pluginutils": "^5.0.5",
"@types/babel-types": "^7.0.15",
"@types/babel__core": "^7.20.5",
"@types/babel__generator": "^7.6.8",
"@types/babel__helper-module-imports": "^7.18.3",
"@types/which-pm-runs": "^1.0.2"
},
"dependencies": {
"ci-info": "^4.0.0",
"is-docker": "^3.0.0",
"which-pm-runs": "^1.1.0"
}
}
================================================
FILE: packages/compiler/plugin.ts
================================================
import type { BabelFileResult, ParserOptions } from '@babel/core';
import { transformAsync } from '@babel/core';
import type { FilterPattern } from '@rollup/pluginutils';
import { createFilter } from '@rollup/pluginutils';
import type { VitePlugin } from 'unplugin';
import { createUnplugin } from 'unplugin';
import { MillionTelemetry } from '../telemetry';
import { babel } from './babel';
import { displayIntro } from './utils/log';
// import { babel } from './babel';
import type { CompilerOptions } from './types';
const DEFAULT_INCLUDE = '**/*.{jsx,tsx}';
const DEFAULT_EXCLUDE = 'node_modules/**/*.{jsx,tsx,ts,js,mjs,cjs}';
interface CompilerOutput {
code: BabelFileResult['code'];
map: BabelFileResult['map'];
}
let ssr = false;
async function compile(
id: string,
code: string,
options: Options,
telemetryInstance: MillionTelemetry,
isServer?: boolean,
): Promise<CompilerOutput> {
if (isServer) {
// for frameworks like remix, even for client, we need to return react-server
ssr = true;
}
displayIntro(options);
const plugins: ParserOptions['plugins'] = [
'jsx',
// import { example } from 'example' with { example: true };
'importAttributes',
// () => throw example
'throwExpressions',
// You know what this is
'decorators',
// const { #example: example } = this;
'destructuringPrivate',
// using example = myExample()
'explicitResourceManagement',
];
if (/\.[mc]?tsx?$/i.test(id)) {
plugins.push('typescript');
}
const result = await transformAsync(code, {
plugins: [
[
babel,
{
telemetry: telemetryInstance,
log: options.log,
server: ssr,
hmr: options.hmr,
auto: options.auto,
rsc: options.rsc,
},
],
],
// plugins: [[babel, options]],
parserOpts: { plugins },
filename: id,
ast: false,
sourceFileName: id,
sourceMaps: true,
configFile: false,
babelrc: false,
});
if (!result) {
throw new Error('invariant');
}
return { code: result.code || '', map: result.map };
}
export interface Options extends Omit<CompilerOptions, 'telemetry'> {
filter?: {
include?: FilterPattern;
exclude?: FilterPattern;
};
/**
* @default 'react'
*/
mode?: 'react' | 'vdom';
/**
* Million.js collects anonymous telemetry data about general usage. Participation is optional, and you may opt-out at any time.
* For more information, please see https://million.dev/telemetry.
* @default true
*/
telemetry?: boolean;
}
// eslint-disable-next-line @typescript-eslint/default-param-last
export const unplugin = createUnplugin((options: Options = {}, meta) => {
if (!options.log) {
options.log = true;
}
const filter = createFilter(
options.filter?.include || DEFAULT_INCLUDE,
options.filter?.exclude || DEFAULT_EXCLUDE,
);
// Backwards compatibility for `mode: 'react-server'`:
// Converts `mode: 'react-server'` to `mode: 'react'` and `server: true`
if (options.mode?.endsWith('-server')) {
options.server = true;
options.mode = 'react';
}
// Notice for `mode: 'preact'`, `mode: 'preact-server'`:
// Throws an error if `mode: 'preact'` or `mode: 'preact-server'` is used
if (options.mode?.startsWith('preact')) {
throw new Error(
'Preact is no longer maintained. Downgrade to a lower version (<= 2.x.x) for support',
);
}
const telemetryInstance = new MillionTelemetry(options.telemetry);
void telemetryInstance.record({
event: 'compile',
payload: {
framework: meta.framework,
mode: options.mode,
server: options.server,
hmr: options.hmr,
rsc: options.rsc,
log: options.log,
auto: options.auto,
// optimize: options.optimize,
},
});
return {
enforce: 'pre',
name: 'million',
transformInclude(id: string): boolean {
return filter(id);
},
async transform(code: string, id: string) {
try {
const result = await compile(
id,
code,
options,
telemetryInstance,
options.server,
);
return {
code: result.code || '',
map: result.map,
};
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
return { code: '' };
}
},
vite: {
configResolved(config) {
// run our plugin before the following plugins:
repushPlugin(config.plugins as VitePlugin[], 'million', [
// https://github.com/withastro/astro/blob/main/packages/astro/src/vite-plugin-jsx/index.ts#L173
'astro:jsx',
// https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react
'vite:react-babel',
'vite:react-jsx',
// https://github.com/preactjs/preset-vite/blob/main/src/index.ts
'vite:preact-jsx',
// https://github.com/vitejs/vite-plugin-react-swc/blob/main/src/index.ts
'vite:react-swc',
]);
options.hmr = config.env.DEV;
},
async transform(code, id, opts) {
try {
if (filter(id)) {
const result = await compile(
id,
code,
options,
telemetryInstance,
opts?.ssr,
);
return {
code: result.code || '',
map: result.map,
};
}
return null;
} catch (_err) {
return null;
}
},
},
};
});
// From: https://github.com/bluwy/whyframe/blob/master/packages/jsx/src/index.js#L27-L37
export const repushPlugin = (
plugins: VitePlugin[],
pluginName: string,
pluginNames: string[],
): void => {
const namesSet = new Set(pluginNames);
let baseIndex = -1;
let targetIndex = -1;
let targetPlugin: VitePlugin;
for (let i = 0, len = plugins.length; i < len; i += 1) {
const current = plugins[i]!;
if (namesSet.has(current.name) && baseIndex === -1) {
baseIndex = i;
}
if (current.name === pluginName) {
targetIndex = i;
targetPlugin = current;
}
}
if (baseIndex !== -1 && targetIndex !== -1 && baseIndex < targetIndex) {
plugins.splice(targetIndex, 1);
plugins.splice(baseIndex, 0, targetPlugin!);
}
};
================================================
FILE: packages/compiler/types.ts
================================================
import type * as t from '@babel/types';
import type { MillionTelemetry } from 'packages/telemetry';
export interface NamedImportDefinition {
name: string;
source: string;
kind: 'named';
}
export interface DefaultImportDefinition {
source: string;
kind: 'default';
}
export type ImportDefinition = NamedImportDefinition | DefaultImportDefinition;
export interface CompilerOptions {
hmr?: boolean;
server?: boolean;
auto?:
| boolean
| {
threshold?: number;
// @deprecated
skip?: (string | RegExp)[];
rsc?: boolean;
};
/**
* @default true
*/
log?: boolean | 'info';
/**
* @default false
*/
rsc?: boolean;
telemetry: MillionTelemetry;
}
export interface StateContext {
options: CompilerOptions;
definitions: {
identifiers: Map<t.Identifier, ImportDefinition>;
namespaces: Map<t.Identifier, ImportDefinition[]>;
};
imports: Map<string, t.Identifier>;
topLevelRSC: boolean;
serverMode: 'server' | 'client';
}
================================================
FILE: packages/compiler/utils/ast.ts
================================================
import type { NodePath } from '@babel/core';
import type * as t from '@babel/types';
import { IGNORE_ANNOTATION } from '../constants';
export const findComment = (node: t.Node, comment: string) => {
const comments = node.leadingComments;
if (!comments) return null;
for (let i = 0, j = comments.length; i < j; ++i) {
if (comments[i]?.value.trim() === comment) {
return comments[i];
}
}
};
export const shouldBeIgnored = (path: NodePath) => {
for (const comment of path.node.leadingComments ?? []) {
if (comment.value.includes(IGNORE_ANNOTATION)) {
return true;
}
}
if (path.parentPath) {
return shouldBeIgnored(path.parentPath);
}
return false;
};
================================================
FILE: packages/compiler/utils/checks.ts
================================================
import type * as t from '@babel/types';
type TypeFilter<V extends t.Node> = (node: t.Node) => node is V;
export function isPathValid<V extends t.Node>(
path: unknown,
key: TypeFilter<V>,
): path is babel.NodePath<V> {
return key((path as babel.NodePath).node);
}
export type NestedExpression =
| t.ParenthesizedExpression
| t.TypeCastExpression
| t.TSAsExpression
| t.TSSatisfiesExpression
| t.TSNonNullExpression
| t.TSInstantiationExpression
| t.TSTypeAssertion;
export function isNestedExpression(node: t.Node): node is NestedExpression {
switch (node.type) {
case 'ParenthesizedExpression':
case 'TypeCastExpression':
case 'TSAsExpression':
case 'TSSatisfiesExpression':
case 'TSNonNullExpression':
case 'TSTypeAssertion':
case 'TSInstantiationExpression':
return true;
default:
return false;
}
}
export type ComponentNode =
| t.ArrowFunctionExpression
| t.FunctionExpression
| t.FunctionDeclaration;
export function isComponent(node: t.Node): node is ComponentNode {
switch (node.type) {
case 'ArrowFunctionExpression':
case 'FunctionExpression':
case 'FunctionDeclaration':
return true;
default:
return false;
}
}
export function isComponentishName(name: string): boolean {
return name[0] >= 'A' && name[0] <= 'Z';
}
export function isStatementTopLevel(
path: babel.NodePath<t.Statement>,
): boolean {
let blockParent = path.scope.getBlockParent();
const programParent = path.scope.getProgramParent();
// a FunctionDeclaration binding refers to itself as the block parent
if (blockParent.path === path) {
blockParent = blockParent.parent;
}
return programParent === blockParent;
}
================================================
FILE: packages/compiler/utils/generate-unique-name.ts
================================================
import type * as babel from '@babel/core';
import * as t from '@babel/types';
export function generateUniqueName(
path: babel.NodePath,
name: string,
): t.Identifier {
let uid;
let i = 1;
do {
uid = `${name}_${i}`;
i++;
} while (
path.scope.hasLabel(uid) ||
path.scope.hasBinding(uid) ||
path.scope.hasGlobal(uid) ||
path.scope.hasReference(uid)
);
const program = path.scope.getProgramParent();
program.references[uid] = true;
program.uids[uid] = true;
return t.identifier(uid);
}
================================================
FILE: packages/compiler/utils/get-descriptive-name.ts
================================================
import type { NodePath } from '@babel/core';
export function getDescriptiveName(
path: NodePath,
defaultName: string,
): string {
let current: babel.NodePath | null = path;
while (current) {
switch (current.node.type) {
case 'FunctionDeclaration':
case 'FunctionExpression': {
if (current.node.id) {
return current.node.id.name;
}
break;
}
case 'VariableDeclarator': {
if (current.node.id.type === 'Identifier') {
return current.node.id.name;
}
break;
}
case 'ClassPrivateMethod':
case 'ClassMethod':
case 'ObjectMethod': {
switch (current.node.key.type) {
case 'Identifier':
return current.node.key.name;
case 'PrivateName':
return current.node.key.id.name;
default:
break;
}
break;
}
default:
break;
}
current = current.parentPath;
}
return defaultName;
}
================================================
FILE: packages/compiler/utils/get-import-specifier.ts
================================================
import type * as babel from '@babel/core';
import * as t from '@babel/types';
import type { ImportDefinition, StateContext } from '../types';
export function getImportIdentifier(
state: StateContext,
path: babel.NodePath,
registration: ImportDefinition,
): t.Identifier {
const name = registration.kind === 'named' ? registration.name : 'default';
const target = `${registration.source}[${name}]`;
const current = state.imports.get(target);
if (current) {
return current;
}
const programParent = path.scope.getProgramParent();
const uid = programParent.generateUidIdentifier(
registration.kind === 'named' ? registration.name : 'default',
);
const newPath = (
programParent.path as babel.NodePath<t.Program>
).unshiftContainer(
'body',
t.importDeclaration(
[
registration.kind === 'named'
? t.importSpecifier(uid, t.identifier(registration.name))
: t.importDefaultSpecifier(uid),
],
t.stringLiteral(registration.source),
),
)[0];
programParent.registerDeclaration(newPath);
state.imports.set(target, uid);
return uid;
}
================================================
FILE: packages/compiler/utils/get-root-statement-path.ts
================================================
import type * as babel from '@babel/core';
import * as t from '@babel/types';
export function getRootStatementPath(path: babel.NodePath): babel.NodePath {
let current = path.parentPath;
while (current) {
const next = current.parentPath;
if (next && t.isProgram(next.node)) {
return current;
}
current = next;
}
return path;
}
================================================
FILE: packages/compiler/utils/get-valid-import-definition.ts
================================================
import * as t from '@babel/types';
import type { ImportDefinition, StateContext } from '../types';
import { unwrapNode } from './unwrap-node';
function isValidIdentifier(
node: t.Node,
): node is t.Identifier | t.JSXIdentifier {
return t.isIdentifier(node) || t.isJSXIdentifier(node);
}
function getValidImportDefinitionFromIdentifier(
ctx: StateContext,
path: babel.NodePath,
): ImportDefinition | undefined {
const id = unwrapNode(path.node, isValidIdentifier);
if (id) {
const binding = path.scope.getBindingIdentifier(id.name) as
| t.Identifier
| undefined;
// babel kinda fcked up
if (binding) {
return ctx.definitions.identifiers.get(binding);
}
return undefined;
}
return undefined;
}
function getValidImportDefinitionFromMemberExpression(
ctx: StateContext,
path: babel.NodePath,
): ImportDefinition | undefined {
const member = unwrapNode(path.node, t.isMemberExpression);
if (!(member && !member.computed && t.isIdentifier(member.property))) {
return undefined;
}
const object = unwrapNode(member.object, t.isIdentifier);
if (!object) {
return undefined;
}
const binding = path.scope.getBindingIdentifier(object.name) as
| t.Identifier
| undefined;
if (!binding) {
return undefined;
}
const defs = ctx.definitions.namespaces.get(binding);
if (!defs) {
return undefined;
}
const propName = member.property.name;
for (let i = 0, len = defs.length; i < len; i++) {
const def = defs[i]!;
if (def.kind === 'named' && def.name === propName) {
return def;
}
if (def.kind === 'default' && propName === 'default') {
return def;
}
}
return undefined;
}
function getValidImportDefinitionFromJSXMemberExpression(
ctx: StateContext,
path: babel.NodePath,
): ImportDefinition | undefined {
if (!path.isJSXMemberExpression()) {
return undefined;
}
const object = unwrapNode(path.node.object, t.isJSXIdentifier);
if (!object) {
return undefined;
}
const binding = path.scope.getBindingIdentifier(object.name) as
| t.Identifier
| undefined;
if (!binding) {
return undefined;
}
const defs = ctx.definitions.namespaces.get(binding);
if (!defs) {
return undefined;
}
const propName = path.node.property.name;
for (let i = 0, len = defs.length; i < len; i++) {
const def = defs[i]!;
if (def.kind === 'named' && def.name === propName) {
return def;
}
if (def.kind === 'default' && propName === 'default') {
return def;
}
}
return undefined;
}
export function getValidImportDefinition(
ctx: StateContext,
path: babel.NodePath,
): ImportDefinition | undefined {
return (
getValidImportDefinitionFromIdentifier(ctx, path) ||
getValidImportDefinitionFromMemberExpression(ctx, path) ||
getValidImportDefinitionFromJSXMemberExpression(ctx, path)
);
}
================================================
FILE: packages/compiler/utils/is-guaranteed-literal.ts
================================================
import * as t from '@babel/types';
export function isGuaranteedLiteral(node: t.Expression): node is t.Literal {
switch (node.type) {
case 'ParenthesizedExpression':
case 'TypeCastExpression':
case 'TSAsExpression':
case 'TSSatisfiesExpression':
case 'TSNonNullExpression':
case 'TSTypeAssertion':
case 'TSInstantiationExpression':
return isGuaranteedLiteral(node.expression);
case 'StringLiteral':
case 'NumericLiteral':
case 'BooleanLiteral':
case 'NullLiteral':
case 'BigIntLiteral':
case 'RegExpLiteral':
return true;
case 'TemplateLiteral':
for (let i = 0, len = node.expressions.length; i < len; i++) {
const expr = node.expressions[i];
if (t.isExpression(expr) && !isGuaranteedLiteral(expr)) {
return false;
}
}
return true;
case 'UnaryExpression':
if (node.operator === 'throw' || node.operator === 'delete') {
return false;
}
return isGuaranteedLiteral(node.argument);
case 'ConditionalExpression':
return (
isGuaranteedLiteral(node.test) &&
isGuaranteedLiteral(node.consequent) &&
isGuaranteedLiteral(node.alternate)
);
case 'BinaryExpression':
if (
node.operator === 'in' ||
node.operator === 'instanceof' ||
node.operator === '|>'
) {
return false;
}
return (
t.isExpression(node.left) &&
isGuaranteedLiteral(node.left) &&
t.isExpression(node.right) &&
isGuaranteedLiteral(node.right)
);
case 'LogicalExpression':
return isGuaranteedLiteral(node.left) && isGuaranteedLiteral(node.right);
default:
return false;
}
}
================================================
FILE: packages/compiler/utils/is-jsx-component-element.ts
================================================
import type * as babel from '@babel/core';
import * as t from '@babel/types';
import { isPathValid } from './checks';
import { isAttributeUnsupported } from './jsx';
export function isJSXComponentElement(
path: babel.NodePath<t.JSXElement>,
): boolean {
const openingElement = path.get('openingElement');
const name = openingElement.get('name');
/**
* Only valid component elements are member expressions and identifiers
* starting with component-ish name
*/
if (isPathValid(name, t.isJSXMemberExpression)) {
return true;
}
if (isPathValid(name, t.isJSXIdentifier)) {
if (/^[A-Z_]/.test(name.node.name)) {
return true;
}
}
const attributes = openingElement.get('attributes');
for (let i = 0, len = attributes.length; i < len; i++) {
const attr = attributes[i];
if (isPathValid(attr, t.isJSXAttribute)) {
if (isAttributeUnsupported(attr.node) || attr.node.name.name === 'ref') {
return true;
}
}
}
return false;
}
================================================
FILE: packages/compiler/utils/is-use-client.ts
================================================
import type * as t from '@babel/types';
export const isUseClient = (directives: t.Directive[]): boolean => {
const directivesLength = directives.length;
if (!directivesLength) return false; // we assume it's server component only
for (let i = 0; i < directivesLength; ++i) {
const directive = directives[i];
if (directive?.value.value === 'use client') return true;
}
return false;
};
================================================
FILE: packages/compiler/utils/jsx.ts
================================================
import * as t from '@babel/types';
import { RENDER_SCOPE } from '../constants';
export const trimJSXChildren = (jsx: t.JSXElement | t.JSXFragment) => {
for (let i = jsx.children.length - 1; i >= 0; i--) {
const child = jsx.children[i]!;
const isEmptyText = t.isJSXText(child) && child.value.trim() === '';
const isEmptyExpression =
t.isJSXExpressionContainer(child) &&
t.isJSXEmptyExpression(child.expression);
const isEmptyFragment =
t.isJSXFragment(child) && child.children.length === 0;
if (isEmptyText || isEmptyExpression || isEmptyFragment) {
jsx.children.splice(i, 1);
}
}
};
export const dedupeJSXAttributes = (
attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[],
) => {
const seen = new Set<string>();
for (let i = attributes.length - 1; i >= 0; i--) {
const attr = attributes[i]!;
if (
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
seen.has(attr.name.name)
) {
attributes.splice(i, 1);
}
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
seen.add(attr.name.name);
}
}
return attributes;
};
export const isAttributeUnsupported = (attribute: t.JSXAttribute) => {
const UNSUPPORTED = ['tw', 'css'];
const attributeName = attribute.name.name;
return (
typeof attributeName === 'string' && UNSUPPORTED.includes(attributeName)
);
};
export const isComponent = (name: string) => {
return (
name[0] &&
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
name[0] === name[0].toUpperCase() &&
!name.startsWith('_') &&
!name.startsWith('use')
);
};
export const isJSXFragment = (
node: t.Node | null | undefined,
): node is t.JSXFragment | t.JSXElement => {
if (!t.isJSXElement(node)) return t.isJSXFragment(node);
const type = node.openingElement.name;
return (
(t.isJSXMemberExpression(type) && type.property.name === 'Fragment') ||
(t.isJSXIdentifier(type) && type.name === 'Fragment')
);
};
export const isSensitiveJSXElement = (jsx: t.JSXElement) => {
// elements that break when the children are not in a specific format
const SENSITIVE_ELEMENTS = ['select'];
const type = jsx.openingElement.name;
return t.isJSXIdentifier(type) && SENSITIVE_ELEMENTS.includes(type.name);
};
export const isStaticAttributeValue = (node: t.Node) => {
if (
t.isTaggedTemplateExpression(node) &&
t.isIdentifier(node.tag) &&
node.quasi.expressions.length === 0 &&
node.tag.name === 'css'
) {
return true;
}
return t.isLiteral(node) && !t.isTemplateLiteral(node);
};
/**
* Turns top-level JSX fragments into a render scope. This is because
* the runtime API does not currently handle fragments. We will deal with
* nested fragments later.
*
* ```js
* function Component() {
* return <>
* <div />
* </>; * }
*
* // becomes
*
* function Component() {
* return <slot>
* <div />
* </slot>;
* }
* ```
*/
export const handleTopLevelFragment = (returnStatement: t.ReturnStatement) => {
const jsx = returnStatement.argument as t.JSXElement | t.JSXFragment;
trimJSXChildren(jsx);
if (jsx.children.length !== 1) {
const renderScopeId = t.jsxIdentifier(RENDER_SCOPE);
returnStatement.argument = t.jsxElement(
t.jsxOpeningElement(renderScopeId, []),
t.jsxClosingElement(renderScopeId),
jsx.children,
);
return;
}
const child = jsx.children[0];
if (t.isJSXElement(child)) {
returnStatement.argument = child;
}
if (t.isJSXExpressionContainer(child)) {
if (t.isJSXEmptyExpression(child.expression)) {
returnStatement.argument = t.nullLiteral();
} else {
returnStatement.argument = child.expression;
}
}
if (isJSXFragment(child)) {
handleTopLevelFragment(child, returnStatement);
}
};
================================================
FILE: packages/compiler/utils/log.ts
================================================
import * as t from '@babel/types';
import {
bold,
cyan,
dim,
green,
magenta,
underline,
yellow,
} from 'kleur/colors';
import type { NodePath } from '@babel/core';
import type { MillionTelemetry } from 'packages/telemetry';
import type { Options } from '../options';
/**
* deopt (deoptimize) will turn a block into a regular function call.
*/
export const deopt = (
message: string | null,
filename: string,
callSitePath: NodePath,
targetPath: NodePath = callSitePath,
): Error => {
const { parent, node } = callSitePath;
// This will attempt to reset the variable to the first argument from
// const foo = block(Component) --> const foo = Component
if (
t.isVariableDeclarator(parent) &&
'arguments' in node &&
(t.isExpression(node.arguments[0]) || t.isIdentifier(node.arguments[0]))
) {
parent.init = node.arguments[0];
}
if (message === null) return new Error('');
return createErrorMessage(targetPath, message, filename);
};
export const warn = (
message: string,
file: string,
path: NodePath,
log?: boolean | string | null,
): void => {
if (log === false) return;
const err = createErrorMessage(path, message, file);
// eslint-disable-next-line no-console
console.warn(
err.message,
'\n',
dim(
`Check out the Rules of Blocks: ${cyan(
'https://million.dev/docs/rules',
)}. Enable the "mute" option to disable this message.`,
),
'\n',
);
};
export const createErrorMessage = (
path: NodePath,
message: string,
filename: string,
): Error => {
return path.buildCodeFrameError(
`\n${magenta('⚠')}${message} ${dim(filename)}`,
);
};
let hasIntroRan = false;
export const displayIntro = (options: Options): void => {
if (hasIntroRan) return;
hasIntroRan = true;
const experiments: string[] = [];
if (typeof options.auto === 'object' && options.auto.rsc) {
experiments.push('auto-rsc');
}
if (options.optimize) {
experiments.push('optimize');
}
let message = `\n ${bold(
magenta(`⚡ Million.js ${process.env.VERSION || ''}`),
)}
- Tip: use ${dim('// million-ignore')} for errors
- Hotline: ${cyan('https://million.dev/hotline')}\n`;
if (experiments.length) {
message += `\n - Experiments (use at your own risk):
· ${experiments.join('\n · ')}
`;
}
// eslint-disable-next-line no-console
console.log(`${message}\n`);
};
export const catchError = (
fn: () => void,
log: boolean | string | undefined | null,
): void => {
try {
fn();
} catch (err: unknown) {
if (err instanceof Error && err.message && log !== false) {
// eslint-disable-next-line no-console
console.warn(err.message, '\n');
}
}
};
export const logImprovement = (
component: string,
improvement: number,
telemetry: MillionTelemetry,
): void => {
void telemetry.record({
event: 'improvement',
payload: { component, improvement },
});
const improvementFormatted = isFinite(improvement)
? (improvement * 100).toFixed(0)
: '∞';
// eslint-disable-next-line no-console
console.log(
`${magenta(' ⚡ ')}${yellow(`<${component}>`)} now renders ${green(
underline(`~${improvementFormatted}%`),
)} faster`,
);
};
export const logIgnore = (component: string): void => {
// eslint-disable-next-line no-console
console.log(dim(` ○ ${yellow(`<${component}>`)} was ignored`));
};
================================================
FILE: packages/compiler/utils/object.ts
================================================
import * as t from '@babel/types';
export const dedupeObjectProperties = (properties: t.ObjectProperty[]) => {
const seen = new Set<string>();
for (let i = properties.length - 1; i >= 0; i--) {
if (!properties[i]) {
properties.splice(i, 1);
continue;
}
const prop = properties[i]!;
if (
t.isObjectProperty(prop) &&
t.isIdentifier(prop.key) &&
seen.has(prop.key.name)
) {
properties.splice(i, 1);
}
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
seen.add(prop.key.name);
}
}
return properties;
};
================================================
FILE: packages/compiler/utils/register-import-definition.ts
================================================
import * as t from '@babel/types';
import type { ImportDefinition, StateContext } from '../types';
function getImportSpecifierName(specifier: t.ImportSpecifier): string {
if (t.isIdentifier(specifier.imported)) {
return specifier.imported.name;
}
return specifier.imported.value;
}
export function registerImportDefinition(
ctx: StateContext,
path: babel.NodePath<t.ImportDeclaration>,
definition: ImportDefinition,
): void {
if (path.node.importKind === 'typeof' || path.node.importKind === 'type') {
return;
}
for (let i = 0, len = path.node.specifiers.length; i < len; i++) {
const specifier = path.node.specifiers[i]!;
if (t.isImportDefaultSpecifier(specifier)) {
if (definition.kind === 'default') {
ctx.definitions.identifiers.set(specifier.local, definition);
}
} else if (t.isImportNamespaceSpecifier(specifier)) {
let current = ctx.definitions.namespaces.get(specifier.local);
if (!current) {
current = [];
}
current.push(definition);
ctx.definitions.namespaces.set(specifier.local, current);
} else if (
!(specifier.importKind === 'typeof' || specifier.importKind === 'type')
) {
const key = getImportSpecifierName(specifier);
if (
(definition.kind === 'named' && key === definition.name) ||
(definition.kind === 'default' && key === 'default')
) {
ctx.definitions.identifiers.set(specifier.local, definition);
}
}
}
}
================================================
FILE: packages/compiler/utils/unwrap-node.ts
================================================
import type * as t from '@babel/types';
import { isNestedExpression } from './checks';
type TypeCheck<K> = K extends ((node: t.Node) => node is infer U extends t.Node)
? U
: never;
type TypeFilter = (node: t.Node) => boolean;
export function unwrapNode<K extends TypeFilter>(
node: t.Node,
key: K,
): TypeCheck<K> | undefined {
if (key(node)) {
return node as TypeCheck<K>;
}
if (isNestedExpression(node)) {
return unwrapNode(node.expression, key);
}
return undefined;
}
================================================
FILE: packages/compiler/utils/unwrap-path.ts
================================================
import type * as t from '@babel/types';
import { isNestedExpression, isPathValid } from './checks';
type TypeFilter<V extends t.Node> = (node: t.Node) => node is V;
export function unwrapPath<V extends t.Node>(
path: unknown,
key: TypeFilter<V>,
): babel.NodePath<V> | undefined {
if (isPathValid(path, key)) {
return path;
}
if (isPathValid(path, isNestedExpression)) {
return unwrapPath(path.get('expression'), key);
}
return undefined;
}
================================================
FILE: packages/compiler/vdom/index.ts
================================================
export { visitor } from './visitor';
================================================
FILE: packages/compiler/vdom/visitor.ts
================================================
/**
* PLEASE DO NOT USE THIS IN PRODUCTION. This is technically a proof-of-concept
* and is _highly_ experimental. Currently, it is mainly used for optimizing the
* JS Framework Benchmark implementation
*/
// import { addNamed } from '@babel/helper-module-imports';
import * as t from '@babel/types';
import type { NodePath } from '@babel/core';
import {
hoistElements,
renderToString,
renderToTemplate,
} from '../experimental/render';
import { chainOrLogic } from '../experimental/utils';
import type { IrEdit, IrEditBase } from '../experimental/types';
import type { Options } from '../options';
export const visitor =
(options: Options = {}) =>
(path: NodePath<t.CallExpression>) => {
if (!options.optimize) return;
// TODO: allow aliasing (block as newBlock)
if (t.isIdentifier(path.node.callee, { name: 'block' })) {
const blockFunction = path.scope.getBinding(path.node.callee.name);
if (!blockFunction) return;
const importSource = blockFunction.path.parent;
if (
!t.isVariableDeclarator(path.parentPath.node) ||
!t.isImportDeclaration(importSource) ||
!importSource.source.value.includes('million')
) {
return;
}
// eslint-disable-next-line prefer-const
let [fn, _unwrap, shouldUpdate] = path.node.arguments;
if (!fn) return;
const [props] = (fn as t.ArrowFunctionExpression).params as (
| t.ObjectPattern
| t.Identifier
)[];
if (t.isArrowFunctionExpression(fn) && t.isJSXElement(fn.body)) {
const edits: IrEdit[] = [];
const holes = t.isObjectPattern(props)
? Object.keys(props.properties).map((key) => {
return props.properties[key].key.name;
})
: [];
const template = renderToTemplate(fn.body, edits, [], holes);
const paths: number[][] = [];
let maxPathLength = 0;
for (let i = 0, j = edits.length; i < j; ++i) {
const path = edits[i]?.path || [];
if (path.length > maxPathLength) maxPathLength = path.length;
paths.push(path);
}
const { declarators, accessedIds } = hoistElements(
paths,
path,
importSource.source.value,
);
const editsArray = t.arrayExpression(
edits.map((edit) => {
const editsProperties: t.ObjectExpression[] = [];
const initsProperties: t.ObjectExpression[] = [];
for (let i = 0, j = edit.edits.length; i < j; ++i) {
const { type, name, hole, listener, value, index } = edit.edits[
i
] as IrEditBase;
editsProperties.push(
t.objectExpression([
t.objectProperty(t.identifier('t'), type),
t.objectProperty(t.identifier('n'), name ?? t.nullLiteral()),
t.objectProperty(t.identifier('v'), value ?? t.nullLiteral()),
t.objectProperty(t.identifier('h'), hole ?? t.nullLiteral()),
t.objectProperty(t.identifier('i'), index ?? t.nullLiteral()),
t.objectProperty(
t.identifier('l'),
listener ?? t.nullLiteral(),
),
t.objectProperty(t.identifier('p'), value ?? t.nullLiteral()),
t.objectProperty(t.identifier('b'), value ?? t.nullLiteral()),
]),
);
}
for (let i = 0, j = edit.inits.length; i < j; ++i) {
const { type, name, hole, listener, value, index } = edit.inits[
i
] as IrEditBase;
initsProperties.push(
t.objectExpression([
t.objectProperty(t.identifier('t'), type),
t.objectProperty(t.identifier('n'), name ?? t.nullLiteral()),
t.objectProperty(t.identifier('v'), value ?? t.nullLiteral()),
t.objectProperty(t.identifier('h'), hole ?? t.nullLiteral()),
t.objectProperty(t.identifier('i'), index ?? t.nullLiteral()),
t.objectProperty(
t.identifier('l'),
listener ?? t.nullLiteral(),
),
t.objectProperty(t.identifier('p'), value ?? t.nullLiteral()),
t.objectProperty(t.identifier('b'), value ?? t.nullLiteral()),
]),
);
}
return t.objectExpression([
t.objectProperty(t.identifier('p'), t.nullLiteral()),
t.objectProperty(
t.identifier('e'),
t.arrayExpression(editsProperties),
),
t.objectProperty(
t.identifier('i'),
initsProperties.length
? t.arrayExpression(initsProperties)
: t.nullLiteral(),
),
]);
}),
);
const stringToDOM = addNamed(
path,
'stringToDOM',
importSource.source.value,
{
nameHint: 'stringToDOM$',
},
);
const shouldUpdateExists =
(t.isIdentifier(shouldUpdate) && shouldUpdate.name !== 'undefined') ||
t.isArrowFunctionExpression(shouldUpdate);
if (!shouldUpdateExists && props && !t.isIdentifier(props)) {
const { properties } = props;
shouldUpdate = t.arrowFunctionExpression(
[t.identifier('oldProps'), t.identifier('newProps')],
chainOrLogic(
...properties
.filter((property) => t.isObjectProperty(property))
.map((property) => {
const key = (property as t.ObjectProperty)
.key as t.Identifier;
return t.binaryExpression(
'!==',
t.optionalMemberExpression(
t.identifier('oldProps'),
key,
false,
true,
),
t.optionalMemberExpression(
t.identifier('newProps'),
key,
false,
true,
),
);
}),
),
);
}
const domVariable = path.scope.generateUidIdentifier('dom$');
const editsVariable = path.scope.generateUidIdentifier('edits$');
const shouldUpdateVariable =
path.scope.generateUidIdentifier('shouldUpdate$');
const getElementsVariable =
path.scope.generateUidIdentifier('getElements$');
const variables = t.variableDeclaration('const', [
t.variableDeclarator(
domVariable,
t.callExpression(stringToDOM, [
t.templateLiteral(
[
t.templateElement({
raw: renderToString(template),
}),
],
[],
),
]),
),
t.variableDeclarator(editsVariable, editsArray),
t.variableDeclarator(
shouldUpdateVariable,
shouldUpdateExists
? t.nullLiteral()
: (shouldUpdate as t.Identifier | t.ArrowFunctionExpression),
),
t.variableDeclarator(
getElementsVariable,
declarators.length
? t.arrowFunctionExpression(
[t.identifier('root')],
t.blockStatement([
t.variableDeclaration('const', declarators),
t.returnStatement(t.arrayExpression(accessedIds)),
]),
)
: t.nullLiteral(),
),
]);
const BlockClass = addNamed(path, 'Block', importSource.source.value, {
nameHint: 'Block$',
});
const blockFactory = t.arrowFunctionExpression(
[
t.identifier('props'),
t.identifier('key'),
t.identifier('shouldUpdate'),
],
t.blockStatement([
t.returnStatement(
t.newExpression(BlockClass, [
domVariable,
editsVariable,
t.identifier('props'),
t.logicalExpression(
'??',
t.identifier('key'),
t.memberExpression(
t.identifier('props'),
t.identifier('key'),
),
),
t.logicalExpression(
'??',
t.identifier('shouldUpdate'),
shouldUpdateVariable,
),
getElementsVariable,
]),
),
]),
);
path.parentPath.parentPath?.insertBefore(variables);
path.replaceWith(t.returnStatement(blockFactory));
}
}
};
================================================
FILE: packages/experimental/README.md
================================================
# `million/experimental`
================================================
FILE: packages/experimental/index.ts
================================================
// eslint-disable-next-line camelcase
export const experimental_options = {
noSlot: false,
};
================================================
FILE: packages/experimental/package.json
================================================
{
"private": true,
"main": "./index.ts",
"module": "./index.ts",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.2.6"
}
}
================================================
FILE: packages/jsx-runtime/README.md
================================================
# `million/jsx-runtime`
================================================
FILE: packages/jsx-runtime/index.ts
================================================
import type { MillionProps } from '../types';
import type { VNode } from '../million';
export const h = (
type: string,
props: MillionProps | null = {},
...children: VNode[]
): VNode => {
if (props === null) props = {};
props.children = children;
return {
type,
props,
};
};
export { h as createElement, h as jsx, h as jsxs };
================================================
FILE: packages/jsx-runtime/package.json
================================================
{
"private": true,
"main": "./index.ts",
"module": "./index.ts"
}
================================================
FILE: packages/kitchen-sink/README.md
================================================
# Million.js Kitchen Sink 🧑🍳
Hey! We're actively recruiting cooks 🧑🍳 to help assemble a list of examples of Million + your favorite React library. [View it live at sink.million.dev](https://sink.million.dev)
## Getting Started
To get started with the Million.js Kitchen Sink, follow these steps:
1. Clone the Million.js repository:
```bash
git clone https://github.com/aidenybai/million.git
cd million
```
2. Install project dependencies using pnpm (pnpm is a package manager similar to npm and yarn, used for the Million.js project):
```bash
pnpm install
```
3. Navigate to the Kitchen Sink directory:
```bash
cd packages/kitchen-sink
```
## Running the Kitchen Sink
You can run the Kitchen Sink application to explore the various scenarios and examples:
```bash
pnpm dev
```
This command will start the development server and open the Kitchen Sink application in your default web browser.
## Examples
The Kitchen Sink showcases Million.js used in a variety of real-life scenarios, utilizing different technologies. Some of the examples you'll find include:
- **Jotai Counter**: A simple counter built with React and that uses Jotai for managing state.
- **Tanstack Query**: A simple application that uses the Tanstack data fetching library and showcases Million.js' usage.
- ...
Each example comes with its own code and resources, demonstrating how Million.js can be integrated into different projects and workflows.
## Adding a New Example
Just create a new file in `packages/kitchen-sink/src/examples` with the name of your example (e.g. `super-epic.tsx`). Make sure you `export default` a React component.
## Contributing
We welcome contributions to the Million.js Kitchen Sink! If you have an interesting example, improvement, or bug fix, here's how you can contribute:
1. Fork the Million.js repository.
2. Create a new branch for your contribution.
3. Make your changes and commit them.
4. Push your changes to your forked repository.
5. Create a pull request targeting the original Million.js repository.
Please make sure to follow our contribution guidelines and code of conduct when submitting your contribution.
## Community and Support
If you have questions, need help, or want to discuss anything related to Million.js or the Kitchen Sink, you can:
- Join our [Discord community](https://million.dev/chat).
- Check out the [Million.js documentation](https://million.dev/docs) for more information and resources.
## License
The Million.js Kitchen Sink is open-source software licensed under the [MIT License](https://github.com/aidenybai/million/blob/feat/kitchen-sink/LICENSE).
================================================
FILE: packages/kitchen-sink/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/tiny.css@0.10/dist/tiny.css"
/>
<title>Million Kitchen Sink</title>
</head>
<body>
<div id="root"></div>
<script src="./src/main.tsx" type="module"></script>
</body>
</html>
================================================
FILE: packages/kitchen-sink/package.json
================================================
{
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"devDependencies": {
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.0",
"vite": "4.4.9",
"vite-plugin-inspect": "^0.7.37"
},
"dependencies": {
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@hello-pangea/dnd": "^16.3.0",
"@reduxjs/toolkit": "^1.9.6",
"@tanstack/react-query": "^4.32.6",
"@tanstack/react-virtual": "3.0.0-beta.54",
"@types/react-redux": "^7.1.27",
"@types/uuid": "9.0.0",
"axios": "^1.4.0",
"emoji-mart": "^5.5.2",
"emoji-picker-react": "^4.5.2",
"jotai": "^2.3.1",
"marked": "^9.1.2",
"react": "^18.2.0",
"react-calendar": "^4.6.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-icons": "^4.11.0",
"react-qr-code": "^2.0.12",
"react-redux": "^8.1.3",
"react-router-dom": "^6.15.0",
"react-shorten-url": "^1.0.5",
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4",
"styled-components": "^6.0.7",
"swr": "^2.2.1",
"use-sound": "^4.0.1",
"uuid": "9.0.0",
"valtio": "^1.11.2",
"wouter": "^2.11.0",
"zustand": "^4.4.1"
}
}
================================================
FILE: packages/kitchen-sink/src/css/examples/age-calculator.css
================================================
.age-calculator h2 {
font-size: 1.2rem;
}
.age-calculator .input-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
align-items: flex-end;
gap: 10px;
}
.age-calculator button {
margin-block: 20px;
}
.age-calculator .form-input-group {
display: flex;
flex-direction: column;
gap: 5px;
}
================================================
FILE: packages/kitchen-sink/src/css/examples/bmi-calculator.css
================================================
.bmi-calculator {
max-width: 900px;
}
.bmi-calculator h2 {
text-align: center;
}
.bmi-calculator .bmi-calculator_wrapper {
max-width: 500px;
display: grid;
grid-template-columns: repeat(2, 1fr);
margin-inline: auto;
gap: 10px 20px;
}
@media screen and (max-width: 500px) {
.bmi-calculator .bmi-calculator_wrapper {
grid-template-columns: 1fr;
}
}
.bmi-calculator .form-input-group {
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 5px;
}
.bmi-calculator input,
.bmi-calculator select {
height: 40px;
color: currentColor;
}
.bmi-calculator select {
background-color: var(--background);
border: none;
border-radius: 5px;
padding: 8px;
}
.bmi-calculator button {
height: 40px;
color: black;
background-color: orange;
margin-top: 10px;
}
================================================
FILE: packages/kitchen-sink/src/css/examples/carousel.css
================================================
/* Required styling for carousel */
.carousel {
display: flex;
overflow: hidden;
gap: 10px;
}
.carousel-inner {
display: flex;
width: 100%;
aspect-ratio: 3/2;
transition: transform 0.3s ease-in-out;
}
.carousel img {
width: 100%;
aspect-ratio: 3/2;
transition: transform 0.3s ease-in-out;
}
================================================
FILE: packages/kitchen-sink/src/css/examples/create-event-calender.css
================================================
/* calendar styles */
.create-event-calender {
/* text-align: center; */
}
.create-event-calender .create-event-calender_wrapper {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: flex-start;
margin-top: 2rem;
}
.create-event-calender .calendar-container {
flex: 1;
max-width: 500px;
margin-right: 2rem;
}
.create-event-calender .event-container {
flex: 1;
max-width: 500px;
}
@media screen and (max-width: 768px) {
.create-event-calender .create-event-calender_wrapper {
flex-direction: column;
}
.create-event-calender .calendar-container,
.create-event-calender .event-container {
max-width: 100%;
margin-right: 0;
margin-bottom: 2rem;
justify-content: center;
}
}
.create-event-calender .react-calendar {
width: 100%;
max-width: 500px;
background-color: white;
border: 1px solid #151515;
font-family: Arial, Helvetica, sans-serif;
line-height: 1.125em;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 10px;
transition: background-color 0.2s;
}
.create-event-calender .react-calendar--doubleView {
width: 100%;
}
.create-event-calender
.react-calendar--doubleView
.react-calendar__viewContainer {
display: flex;
margin: -0.5em;
}
.create-event-calender
.react-calendar--doubleView
.react-calendar__viewContainer
> * {
width: 50%;
margin: 0.5em;
}
.create-event-calender .react-calendar,
.create-event-calender .react-calendar *,
.create-event-calender .react-calendar *:before,
.create-event-calender .react-calendar *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.create-event-calender .react-calendar button {
margin: 0;
border: 0;
outline: none;
color: #151515;
}
.create-event-calender .react-calendar button:enabled:hover {
cursor: pointer;
}
.create-event-calender .react-calendar__navigation {
display: flex;
height: 44px;
margin-bottom: 1em;
}
.create-event-calender .react-calendar__navigation button {
min-width: 44px;
background: none;
}
.create-event-calender .react-calendar__navigation button:disabled {
background-color: black;
}
.create-event-calender .react-calendar__navigation button:enabled:hover,
.create-event-calender .react-calendar__navigation button:enabled:focus {
background-color: black;
}
.create-event-calender .react-calendar__month-view__weekdays {
text-align: center;
text-transform: uppercase;
font-weight: bold;
font-size: 0.8rem;
color: black;
}
.create-event-calender .react-calendar__month-view__weekdays__weekday {
padding: 0.5em;
}
.create-event-calender
.react-calendar__month-view__weekNumbers
.react-calendar__tile {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75em;
font-weight: bold;
}
.create-event-calender .react-calendar__month-view__days__day--weekend {
color: black;
}
.create-event-calender
.react-calendar__month-view__days__day--neighboringMonth {
color: black;
}
.create-event-calender .react-calendar__year-view .react-calendar__tile,
.create-event-calender .react-calendar__decade-view .react-calendar__tile,
.create-event-calender .react-calendar__century-view .react-calendar__tile {
padding: 2em 0.5em;
}
.create-event-calender .react-calendar__tile {
max-width: 100%;
padding: 10px 6.6667px;
background: none;
text-align: center;
line-height: 16px;
}
.create-event-calender .react-calendar__tile:disabled {
background-color: black;
}
.create-event-calender .react-calendar__tile:enabled:hover,
.create-event-calender .react-calendar__tile:enabled:focus {
background-color: black;
}
.create-event-calender .react-calendar__tile--now {
background: black;
color: white !important;
}
.create-event-calender .react-calendar__tile--now:enabled:hover,
.create-event-calender .react-calendar__tile--now:enabled:focus {
background: black;
}
.create-event-calender .react-calendar__tile--hasActive {
background: black;
}
.create-event-calender .react-calendar__tile--hasActive:enabled:hover,
.create-event-calender .react-calendar__tile--hasActive:enabled:focus {
background: black;
}
.create-event-calender .react-calendar__tile--active {
background: black;
color: black;
}
.create-event-calender .react-calendar__tile--active:enabled:hover,
.create-event-calender .react-calendar__tile--active:enabled:focus {
background: black;
}
.create-event-calender
.react-calendar--selectRange
.react-calendar__tile--hover {
background-color: black;
}
.create-event-calender .event-form {
margin-bottom: 20px;
}
.create-event-calender .event-form h2 {
margin-bottom: 10px;
}
.create-event-calender .event-form p {
font-size: 1.2rem;
}
.create-event-calender .event-form input {
padding: 8px;
font-size: 1rem;
margin-right: 10px;
}
.create-event-calender .create-btn {
padding: 8px 6px;
margin-top: 5px;
background-color: white;
color: black;
border: 0.5px solid black;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
.create-event-calender .create-btn:hover {
background-color: black;
color: whitesmoke;
border: 0.5px solid white;
}
.create-event-calender .event-list {
margin-top: 20px;
}
.create-event-calender .event-cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.create-event-calender .event-card {
width: 300px;
background-color: whitesmoke;
border-radius: 8px;
margin: 10px;
padding: 10px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.create-event-calender .event-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.create-event-calender .event-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.create-event-calender .event-date {
font-size: 1rem;
color: black;
}
.create-event-calender .event-actions {
display: flex;
}
.create-event-calender .update-btn,
.create-event-calender .delete-btn {
padding: 8px 12px;
margin-left: 5px;
background-color: white;
color: black;
border: 0.5px solid black;
border-radius: 4px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s;
}
.create-event-calender .update-btn:hover,
.create-event-calender .delete-btn:hover {
background-color: black;
color: aliceblue;
border: 0.5px solid white;
}
.create-event-calender .event-card-body {
padding-bottom: 10px;
}
.create-event-calender .event-title {
font-size: 1.5rem;
color: blueviolet;
}
.create-event-calender .selected {
background-color: #ff4081 !important;
color: black;
}
.create-event-calender .event-marked {
background-color: #ff4081;
color: white;
}
================================================
FILE: packages/kitchen-sink/src/css/examples/dice-roller.css
================================================
/* Dice roller styles */
@keyframes shakeDice {
0% {
transform: translateX(0);
}
10% {
transform: translateX(-5px);
}
20% {
transform: translateX(5px);
}
30% {
transform: translateX(-5px);
}
40% {
transform: translateX(5px);
}
50% {
transform: translateX(-5px);
}
60% {
transform: translateX(5px);
}
70% {
transform: translateX(-5px);
}
80% {
transform: translateX(5px);
}
90% {
transform: translateX(-5px);
}
100% {
transform: translateX(0);
}
}
.dice-roller.roll-animation {
animation: shakeDice 0.5s ease-in-out;
}
================================================
FILE: packages/kitchen-sink/src/css/examples/style.css
================================================
/* Music player styles */
.body {
box-sizing: border-box;
background: linear-gradient(
90deg,
rgba(2, 0, 36, 1) 0%,
rgba(20, 107, 128, 1) 51%,
rgba(0, 212, 255, 1) 100%
);
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.music-card {
min-height: 90vh;
display: flex;
align-items: center;
justify-content: center;
}
.component {
background: linear-gradient(
90deg,
rgba(2, 0, 36, 1) 0%,
rgba(105, 20, 128, 1)
);
width: 50%;
color: #fff;
margin: 1.4em 0;
max-width: 600px;
padding: 1em 2em;
border: 0.1px solid black;
border-radius: 10px;
}
.musicCover {
max-width: 100%;
border-radius: 10%;
}
.playButton {
background: none;
border: none;
align-items: center;
justify-content: center;
}
.subTitle {
margin-top: -1em;
color: #d3c5c5;
}
.time {
margin: 0 auto;
width: 80%;
display: flex;
justify-content: space-between;
color: #828282;
font-size: smaller;
}
.timeline {
width: 80%;
background-color: #27ae60;
}
input[type='range'] {
background-color: #27ae60;
}
@media (max-width: 900px) {
.component {
width: 50%;
}
}
/* */
/* QR code styles */
.qr-code-wrapper {
display: inline-flex;
background: white;
margin-top: 2rem;
padding: 1rem;
}
.news-card {
display: flex;
flex-direction: column;
background-color: #fff;
border-radius: 8px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 20px;
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.04);
}
&:focus {
transform: scale(1.04);
}
.news-container {
justify-content: center;
align-items: center;
}
.image-container {
display: flex;
justify-content: center;
height: 300px;
width: 100%;
&:hover {
opacity: 0.8;
}
img {
width: 1000px;
}
}
.content {
padding: 0 16px;
.title {
font-size: 1.25rem;
font-weight: 800;
margin-bottom: 8px;
color: black;
}
.description {
font-size: 1rem;
color: #333;
margin-bottom: 12px;
}
.source {
font-size: 0.875rem;
color: #777;
margin-bottom: 4px;
}
.published-at {
font-size: 0.875rem;
color: #777;
margin-bottom: 12px;
}
.read-more {
font-size: 1rem;
text-decoration: none;
color: #007bff;
&:hover {
text-decoration: underline;
}
}
}
}
/********** PASSWORD GENERATOR **********/
.password_generator {
width: 80%;
margin: 2em auto;
}
.password_generator_top_section {
display: flex;
gap: 1.5em;
}
.password_generator_output_container {
display: flex;
border-radius: 10px;
background: #353535;
border: 1px solid #dddddd;
align-items: center;
padding-right: 20px;
flex: 5;
}
.password_generator_output_container input {
padding: 14px;
width: 100%;
outline: none;
line-height: 30px;
caret-color: #1c1c1c;
background-color: transparent;
font-size: 16px;
border-radius: 10px;
transition: all 0.3s ease-in;
color: #fff;
font-weight: 600;
font-size: 1.5rem;
}
.password_generator_output_container .right-icon {
font-size: 25px;
cursor: pointer;
border: none;
}
.password_generator_refresh_password {
flex: 0.5;
}
.password_generator_tooltip-container {
position: relative;
}
.password_generator_tooltip-text {
background-color: #333;
color: #fff;
text-align: center;
border-radius: 4px;
padding: 6px;
position: absolute;
z-index: 1;
bottom: 50%;
left: 90%;
font-size: 12px;
opacity: 0;
transition: all 200ms ease-in-out;
}
.password_generator_tooltip-text.visible {
opacity: 1;
}
.password_generator_customizable_options__length {
display: flex;
align-items: center;
}
.password_generator_customizable_options__checkbox {
position: relative;
/* border: 1px solid themed("primary"); */
border-radius: 4px;
background: none;
cursor: pointer;
line-height: 0;
margin: 0 0.6em 0 0;
outline: 0;
padding: 0 !important;
vertical-align: text-top;
height: 18px;
width: 18px;
opacity: 0.5;
transition: all 200ms ease-in-out;
&:hover {
opacity: 1;
}
&:checked {
/* background-color: themed("primary"); */
opacity: 1;
border: none;
}
}
/* */
.container {
width: 50vw;
display: flex;
flex-wrap: wrap;
margin: auto;
margin-top: 50px;
border: 10px solid white;
}
.button-container {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
padding-top: 50px;
}
.sketch-button {
font-size: 1.4rem;
border-radius: 10px;
background: linear-gradient(green, royalblue);
}
.color-container {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
.game-container {
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 25px;
.restart {
background-color: #007bff;
padding: 20px;
font-size: 1.4rem;
}
.box1 {
display: flex;
justify-content: space-between;
gap: 20px;
& button {
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.4em;
border-radius: 10px;
background-color: royalblue;
}
button:active {
transition: 1s;
transform: scale(0.8);
background-color: royalblue;
}
}
.box2 {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
font-size: 1.8em;
text-align: center;
align-items: center;
& .box {
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
border: 2px solid black;
padding: 10px;
}
}
.output1 {
font-size: 1.1rem;
margin: auto;
text-align: center;
margin-top: 20px;
}
.output {
font-size: 1.4rem;
margin: auto;
text-align: center;
font-weight: 700;
}
}
.type-health-bar {
position: relative;
width: 60%;
height: 12px;
left: 50%;
transform: translateX(-50%);
border-radius: 4px;
background-color: #ddd;
}
.type-health {
margin-top: 20px;
height: inherit;
border-radius: inherit;
position: relative;
background-color: #27ae60;
}
.type-mono {
font-family: 'Courier New', Courier, monospace;
}
.type-green {
display: inline;
background-color: #92f77e;
}
.type-red {
display: inline;
background-color: #f7887e;
}
.type-gray {
display: inline;
color: gray;
}
.type-none {
display: inline;
}
.card-game-container {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.card-grid {
display: grid;
grid-template-columns: repeat(4, 100px);
gap: 10px;
margin-top: 20px;
}
.card {
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
border: 2px solid #000;
cursor: pointer;
background-color: #353535;
border: 1px solid #3333331b;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
color: #000000;
border-radius: 5px;
transition: transform 0.3s ease-in-out;
}
.card.flipped {
background-color: #f0f0f0;
transform: rotateY(180deg);
}
.card.flipped .card-content {
transform: rotateY(-180deg);
}
.card.matched {
background-color: #7eff80;
cursor: not-allowed;
}
.play-again-button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
background-color: #4caf50;
color: #fff;
border: none;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
.play-again-button:hover {
background-color: #45a049;
}
/*Snake Game CSS*/
.snakeGame--main {
height: 500px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
/* border: 1px solid red; */
}
.snakeGame--keys {
width: 500px;
height: 150px;
border: 1px solid white;
font-family: monospace;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.snakeGame--keys2 {
display: flex;
flex-direction: row;
width: 65%;
justify-content: space-between;
align-items: center;
}
.snakeGame {
width: 500px;
aspect-ratio: 1;
/* height: 500px; */
border: 10px solid white;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: monospace;
border-radius: 10px;
}
.snakeGame--button {
font-family: inherit;
font-size: 1.5rem;
border-radius: 10px;
color: black;
width: 150px;
height: 50px;
border: none;
background-color: #e73d9f;
font-weight: 600;
z-index: 4;
cursor: pointer;
}
.snakeGame--text {
color: #eb2d2d;
z-index: 4;
opacity: 0.5;
}
.snakeGame--arrow-msg {
font-size: 1rem;
margin: 10px;
}
.snakeGame--game-over {
font-size: 3rem;
}
.snakeGame--count {
position: absolute;
font-size: 2rem;
bottom: 0px;
color: #eb2d2d;
opacity: 0.5;
}
@media (max-width: 768px) {
.snakeGame--main {
flex-direction: column;
height: auto;
width: 100%;
}
.snakeGame {
width: 100%;
}
}
================================================
FILE: packages/kitchen-sink/src/css/style.css
================================================
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
font-family: 'Inter', sans-serif;
}
@supports (font-variation-settings: normal) {
:root {
font-family: 'Inter var', sans-serif;
}
}
.spinner {
width: 2rem;
height: 2rem;
border: 0.25rem solid #56c7ff;
border-bottom: 0.25rem solid rgba(0, 0, 0, 0);
border-radius: 50%;
animation: spin 1s linear infinite;
z-index: 9999;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
button:disabled {
opacity: 0.2 !important;
cursor: not-allowed !important;
}
.kitchen_sink-layout {
margin-top: 1.5em;
display: flex;
height: 100vh;
max-width: 100vw;
overflow: hidden;
}
.kitchen_sink-layout-icon {
outline: none;
border: none;
cursor: pointer;
position: relative;
}
.kitchen_sink-content {
width: 100%;
overflow-x: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
}
.kitchen_sink-content::-webkit-scrollbar {
display: none;
}
.kitchen_sink-content_container {
width: 95%;
margin: 0 auto;
max-width: 1300px;
height: 100vh;
overflow-y: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
}
.kitchen_sink-content_container::-webkit-scrollbar {
display: none;
}
.kitchen_sink-sidebar {
border: 1px solid #353535;
height: 100vh;
box-shadow: 0px 4px 25px rgb(#000, 0.25);
overflow-y: scroll;
transition: all 200ms ease-in-out;
-ms-overflow-style: none;
scrollbar-width: none;
background: #252525;
}
.kitchen_sink-sidebar::-webkit-scrollbar {
display: none;
}
.kitchen_sink-sidebar_container {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: stretch;
gap: 1em;
overflow-y: scroll;
}
.kitchen_sink-sidebar_close {
width: 0px;
overflow: hidden;
transition: all 200ms ease-in;
}
.kitchen_sink-sidebar_open {
padding: 1em;
width: 320px;
min-width: 270px;
}
@media screen and (max-width: 768px) {
.kitchen_sink-sidebar {
position: fixed;
z-index: 99999999;
top: 0;
left: 0;
}
}
pre {
white-space: pre-wrap;
/* css-3 */
white-space: -moz-pre-wrap;
/* Mozilla, since 1999 */
white-space: -pre-wrap;
/* Opera 4-6 */
white-space: -o-pre-wrap;
/* Opera 7 */
word-wrap: break-word;
/* Internet Explorer 5.5+ */
}
================================================
FILE: packages/kitchen-sink/src/examples/age-calculator.tsx
================================================
import { useState } from 'react';
import { block } from 'million/react';
import '../css/examples/age-calculator.css';
const AgeCalculator = block(() => {
const Today = new Date();
const currentYear = Today.getFullYear();
const currentMonth = Today.getMonth();
const currentDay = Today.getDate();
const [birthYear, setBirthYear] = useState('');
const [birthMonth, setBirthMonth] = useState('');
const [birthDay, setBirthDay] = useState('');
const [calculatedYear, setCalculatedYear] = useState(0);
const [calculatedMonth, setCalculatedMonth] = useState(0);
const [calculatedDay, setCalculatedDay] = useState(0);
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
let years = currentYear - Number(birthYear);
let months = currentMonth + 1 - Number(birthMonth);
let days = currentDay - Number(birthDay);
if (days < 0) {
months -= 1;
const daysInPrevMonth = new Date(currentYear, currentMonth, 0).getDate();
days += daysInPrevMonth;
}
if (months < 0) {
years -= 1;
months += 12;
}
if (years < 0) {
years = 0;
months = 0;
days = 0;
}
setCalculatedYear(years);
setCalculatedMonth(months);
setCalculatedDay(days);
}
return (
<main className="age-calculator">
<section>
<h2>Enter you date of birth</h2>
<form onSubmit={handleSubmit} className="calculator-form">
<div className="input-wrapper">
<span className="form-input-group">
<label htmlFor="day">Birth Day</label>
<input
type="number"
name="day"
id="day"
placeholder="DD"
value={birthDay}
onChange={(e) => setBirthDay(e.target.value)}
maxLength={2}
min={1}
pattern="(0?[1-9]|[12][0-9]|3[01])"
/>
</span>
<span className="form-input-group">
<label htmlFor="month">Birth Month</label>
<input
type="number"
name="month"
id="month"
placeholder="MM"
value={birthMonth}
onChange={(e) => setBirthMonth(e.target.value)}
maxLength={2}
min={1}
pattern="(1[0-2]|0?[1-9])"
/>
</span>
<span className="form-input-group">
<label htmlFor="year">Birth Year</label>
<input
type="number"
name="year"
id="year"
placeholder="YYYY"
value={birthYear}
onChange={(e) => setBirthYear(e.target.value)}
maxLength={4}
/>
</span>
</div>
<button type="submit">Calculate</button>
</form>
</section>
<section>
<h2>Your Age</h2>
<p>
You are {calculatedYear} {calculatedYear > 1 ? 'years' : 'year'},{' '}
{calculatedMonth} {calculatedMonth > 1 ? 'months' : 'month'} and{' '}
{calculatedDay} {calculatedDay > 1 ? 'days' : 'day'} old.
</p>
</section>
</main>
);
});
export default AgeCalculator;
================================================
FILE: packages/kitchen-sink/src/examples/bmi-calculator.jsx
================================================
import React, { useState } from 'react';
import { block } from 'million/react';
import '../css/examples/bmi-calculator.css';
const BmiCalculator = block(function BmiComponent() {
const [age, setAge] = useState('');
const [gender, setGender] = useState('male');
const [heightFeet, setHeightFeet] = useState('');
const [heightInches, setHeightInches] = useState('');
const [weight, setWeight] = useState('');
const [bmi, setBmi] = useState(null);
const [bmiCategory, setBmiCategory] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const calculateBmi = () => {
if (
age === '' ||
heightFeet === '' ||
heightInches === '' ||
weight === ''
) {
setErrorMessage('Please fill in all fields.');
return;
} else {
setErrorMessage('');
}
const heightInCentimeters =
parseInt(heightFeet, 10) * 30.48 + parseInt(heightInches, 10) * 2.54;
const heightInMeters = heightInCentimeters / 100;
const calculatedBmi = weight / (heightInMeters * heightInMeters);
const finalBmi = parseFloat(calculatedBmi.toFixed(2));
setBmi(finalBmi);
if (finalBmi < 18.5) {
setBmiCategory('Underweight');
} else if (finalBmi >= 18.5 && finalBmi < 24.9) {
setBmiCategory('Normal');
} else if (finalBmi >= 25 && finalBmi < 29.9) {
setBmiCategory('Overweight');
} else {
setBmiCategory('Obese');
}
};
return (
<main className="bmi-calculator">
<h2>BMI Calculator</h2>
<div className="bmi-calculator_wrapper">
<div className="form-input-group">
<label>Age*:</label>
<input
type="number"
placeholder="Enter your age"
min={1}
value={age}
onChange={(e) => setAge(e.target.value)}
/>
</div>
<div className="form-input-group">
<label>Gender*:</label>
<select value={gender} onChange={(e) => setGender(e.target.value)}>
<option value="male">Male</option>
<option value="female">Female</option>
</select>
</div>
<div className="form-input-group">
<label>Height (enter feet)*:</label>
<input
type="number"
placeholder="Enter your height in feet"
value={heightFeet}
onChange={(e) => setHeightFeet(e.target.value)}
min={1}
/>
</div>
<div className="form-input-group">
<label>Height (enter inches)*:</label>
<input
type="number"
placeholder="Enter your height in inch"
value={heightInches}
onChange={(e) => setHeightInches(e.target.value)}
min={1}
/>
</div>
<div className="form-input-group">
<label>Weight (in kg)*:</label>
<input
type="number"
placeholder="Enter you weight"
value={weight}
onChange={(e) => setWeight(e.target.value)}
min={1}
/>
</div>
<div className="form-input-group">
<button type="button" onClick={calculateBmi}>
Calculate BMI
</button>
</div>
</div>
{errorMessage && (
<p style={{ color: 'tomato', textAlign: 'center' }}>{errorMessage}</p>
)}
{bmi !== null && (
<div
style={{
border: '2px solid gold',
marginTop: '30px',
padding: '20px',
textAlign: 'center',
}}
>
<p>
Your BMI: <b>{bmi}</b>
</p>
<p>
Category: <b>{bmiCategory}</b>
</p>
</div>
)}
</main>
);
});
export default BmiCalculator;
================================================
FILE: packages/kitchen-sink/src/examples/book-recommendation.jsx
================================================
import { block, For } from 'million/react';
import { useEffect } from 'react';
import { useState } from 'react';
import axios from 'axios';
const apiKey = 'AIzaSyBcyDly48QaqUGg-QGB0znsGWWA_TC038Q';
const BookRecommendation = block(() => {
const [subject, setSubject] = useState('JavaScript');
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const abortController = new AbortController();
async function getBooks() {
try {
const response = await axios.get(
`https://www.googleapis.com/books/v1/volumes?q=subject:${subject}&key=${apiKey}`,
);
setBooks(response.data.items || []);
setLoading(false);
} catch (error) {
console.error('Error fetching data:', error);
setLoading(false);
}
}
getBooks();
return () => {
abortController.abort();
};
}, [subject]);
if (loading) {
return <div>Loading...</div>;
}
return (
<main>
<h2
style={{
fontSize: '3rem',
textTransform: 'capitalize',
marginBottom: '1rem',
}}
>
Reading makes the world huge
</h2>
<section
style={{
background: '#ffffff',
color: '#2A2C2E',
padding: '1.2rem',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
gap: '1.2rem',
}}
>
<div
style={{
background: '#2A2C2E',
color: '#ffff',
flexBasis: '50%',
position: 'relative',
padding: '1.2rem',
borderRadius: '1rem',
overflow: 'hidden',
}}
>
<h3 style={{ textTransform: 'uppercase' }}>Find Something to read</h3>
<p style={{ marginBlock: '.5rem 1rem' }}>
Fancy something unusual and unpredictable? Funny or exciting? No
problem. Check out the collections we have prepared for you.
</p>
<button
type="button"
style={{
outline: 'none',
borderRadius: '.8rem',
padding: '1rem 2rem',
border: '1px solid #F4CE47',
color: '#F4CE47',
textTransform: 'capitalize',
}}
>
browse now
</button>
<svg
width="200"
height="240"
viewBox="0 0 259 299"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{ position: 'absolute', right: 0 }}
>
<path
d="M11.9995 305.5L97.745 219.754M97.745 219.754C75.3507 197.36 61.4995 166.422 61.4995 132.25C61.4995 63.9043 116.904 8.49951 185.25 8.49951C253.595 8.49951 309 63.9043 309 132.25C309 200.595 253.595 256 185.25 256C151.077 256 120.139 242.148 97.745 219.754Z"
stroke="black"
strokeOpacity="0.38"
strokeWidth="16"
strokeLinecap="square"
strokeLinejoin="round"
/>
</svg>
</div>
<div>
<img
src="https://i.ibb.co/s5GS61g/book-of-the-day.png"
alt="book image"
/>
</div>
</section>
<Subject selectedSubject={subject} onSelectSubject={setSubject} />
<section
style={{ background: '#2A2C2E', color: '#ffffff ', padding: '1.2rem' }}
>
<h3 style={{ textTransform: 'uppercase', marginBottom: '1.2rem' }}>
Bestsellers
</h3>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
gap: '1.2rem',
}}
>
<For each={books}>
{(book, index) => <Book
key={index}
author={
book?.volumeInfo.authors && book?.volumeInfo.authors.join(', ')
}
image={book?.volumeInfo?.imageLinks?.thumbnail}
title={book?.volumeInfo.title}
url={book?.volumeInfo.previewLink}
/>}
</For>
</div>
</section>
</main>
);
});
const Book = ({ author, image, title, url }) => {
return (
<article
style={{
background: '#ffffff',
color: '#2A2C2E',
borderRadius: '1rem',
padding: '1rem',
}}
>
<img
src={image || 'https://i.ibb.co/KNZGG1G/img.png'}
alt={title}
style={{
display: 'block',
width: '100%',
borderRadius: '1rem',
height: 'auto',
marginBottom: '1rem',
}}
/>
<h5 style={{ marginBlock: 0, fontWeight: 400 }}>
{author || 'Murakami'}
</h5>
<h4 style={{ marginBlock: 0 }}>{title || 'After Dark'}</h4>
<a
href={url}
style={{
textDecoration: 'none',
border: '1px solid #2A2C2E',
borderRadius: '1rem',
color: '#2A2C2E',
padding: '.4rem 1rem',
marginTop: '1rem',
display: 'inline-block',
}}
>
See more
</a>
</article>
);
};
const Subject = block(({ selectedSubject, onSelectSubject }) => {
const SUBJECTS = [
'Science Fiction',
'Fantasy',
'Mystery',
'Romance',
'Thriller',
'Horror',
'Historical Fiction',
'Non-Fiction',
'Biography',
'Self-Help',
'Travel',
'Cooking',
'Science',
'Poetry',
'Young Adult',
'Children',
'Drama',
'Graphic Novel',
'Satire',
'Dystopian',
];
return (
<section
style={{
width: '100%',
display: 'flex',
flexWrap: 'nowrap',
gap: '10px',
overflow: 'auto',
background: 'yellow',
padding: '3rem 1.2rem',
}}
>
{SUBJECTS.map((subject) => (
<button
key={subject}
onClick={() => onSelectSubject(subject)}
style={{
display: 'inline-block',
cursor: 'pointer',
outline: ' none',
borderRadius: '1rem',
padding: '.5rem 1rem',
border: '1px solid #2A2C2E',
background: selectedSubject === subject ? '#2A2C2E' : 'transparent',
color: selectedSubject === subject ? '#fff' : '#2a2c2e',
}}
>
{subject}
</button>
))}
</section>
);
});
export default BookRecommendation;
================================================
FILE: packages/kitchen-sink/src/examples/calculator.tsx
================================================
import { useState } from 'react';
import { block } from 'million/react';
type ICalculator = {
value: number;
newValue: number | null;
operator: string;
};
type IControls = {
value: number;
setValue: Function;
newValue: number | null;
setNewValue: Function;
operator: string;
setOperator: Function;
};
function Calculator() {
const [value, setValue] = useState<number>(0);
const [newValue, setNewValue] = useState<number | null>(null);
const [operator, setOperator] = useState<string>('');
return (
<>
<Screen value={value} newValue={newValue} operator={operator} />
<Controls
value={value}
setValue={setValue}
newValue={newValue}
setNewValue={setNewValue}
operator={operator}
setOperator={setOperator}
/>
</>
);
}
const Screen = block(({ value, newValue, operator }: ICalculator) => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
backgroundColor: '#eee',
marginBottom: '20px',
padding: '5p
gitextract_pl2ny0b4/
├── .eslintignore
├── .eslintrc.cjs
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── COMMIT_CONVENTION.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── documentation.yml
│ │ ├── feature_request.yml
│ │ └── tooling.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── labeler.yml
│ └── workflows/
│ ├── auto-response.yml
│ ├── ci.yml
│ ├── issue-needs-repro.yml
│ └── publish.yml
├── .gitignore
├── .prettierignore
├── LICENSE
├── README.md
├── build.config.ts
├── cli.js
├── compiler.d.ts
├── jsx-runtime.d.ts
├── package.json
├── packages/
│ ├── cli/
│ │ ├── build.mjs
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── compiler/
│ │ ├── README.md
│ │ ├── auto.ts
│ │ ├── babel.ts
│ │ ├── block.ts
│ │ ├── constants.ts
│ │ ├── experimental/
│ │ │ ├── optimize.ts
│ │ │ ├── render.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── for.old.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── plugin.ts
│ │ ├── types.ts
│ │ ├── utils/
│ │ │ ├── ast.ts
│ │ │ ├── checks.ts
│ │ │ ├── generate-unique-name.ts
│ │ │ ├── get-descriptive-name.ts
│ │ │ ├── get-import-specifier.ts
│ │ │ ├── get-root-statement-path.ts
│ │ │ ├── get-valid-import-definition.ts
│ │ │ ├── is-guaranteed-literal.ts
│ │ │ ├── is-jsx-component-element.ts
│ │ │ ├── is-use-client.ts
│ │ │ ├── jsx.ts
│ │ │ ├── log.ts
│ │ │ ├── object.ts
│ │ │ ├── register-import-definition.ts
│ │ │ ├── unwrap-node.ts
│ │ │ └── unwrap-path.ts
│ │ └── vdom/
│ │ ├── index.ts
│ │ └── visitor.ts
│ ├── experimental/
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── jsx-runtime/
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── kitchen-sink/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── css/
│ │ │ │ ├── examples/
│ │ │ │ │ ├── age-calculator.css
│ │ │ │ │ ├── bmi-calculator.css
│ │ │ │ │ ├── carousel.css
│ │ │ │ │ ├── create-event-calender.css
│ │ │ │ │ ├── dice-roller.css
│ │ │ │ │ └── style.css
│ │ │ │ └── style.css
│ │ │ ├── examples/
│ │ │ │ ├── age-calculator.tsx
│ │ │ │ ├── bmi-calculator.jsx
│ │ │ │ ├── book-recommendation.jsx
│ │ │ │ ├── calculator.tsx
│ │ │ │ ├── carousel.tsx
│ │ │ │ ├── context.tsx
│ │ │ │ ├── countdown-timer.jsx
│ │ │ │ ├── counter.tsx
│ │ │ │ ├── create-event-calender.jsx
│ │ │ │ ├── crypto-tracker.jsx
│ │ │ │ ├── currency-convertor.tsx
│ │ │ │ ├── data-converter.tsx
│ │ │ │ ├── dice-roller.tsx
│ │ │ │ ├── digital-personal-journal.jsx
│ │ │ │ ├── discount-calculator.tsx
│ │ │ │ ├── emoji-picker.tsx
│ │ │ │ ├── etch-a-sketch.tsx
│ │ │ │ ├── expense-tracker.tsx
│ │ │ │ ├── file-upload-preview.jsx
│ │ │ │ ├── form.tsx
│ │ │ │ ├── github-user-search.jsx
│ │ │ │ ├── guestbook.tsx
│ │ │ │ ├── hangman-game.tsx
│ │ │ │ ├── interactive-card-game.tsx
│ │ │ │ ├── investment-calculator.tsx
│ │ │ │ ├── jotai-counter.tsx
│ │ │ │ ├── list.tsx
│ │ │ │ ├── location-app.tsx
│ │ │ │ ├── markdown-editor.tsx
│ │ │ │ ├── million-quiz.jsx
│ │ │ │ ├── morse-code-translator.jsx
│ │ │ │ ├── mortgage-calculator.tsx
│ │ │ │ ├── movie-finder.tsx
│ │ │ │ ├── multi-children.jsx
│ │ │ │ ├── music-player.jsx
│ │ │ │ ├── news-aggregator.tsx
│ │ │ │ ├── number-guessing.tsx
│ │ │ │ ├── password-generator.tsx
│ │ │ │ ├── pomodoro-timer.jsx
│ │ │ │ ├── qr-code-generator.tsx
│ │ │ │ ├── quote-generator.tsx
│ │ │ │ ├── react-router-dom.tsx
│ │ │ │ ├── recipe-finder.jsx
│ │ │ │ ├── redux-todo.tsx
│ │ │ │ ├── repro.tsx
│ │ │ │ ├── rock-paper-scissors.tsx
│ │ │ │ ├── snake-game.jsx
│ │ │ │ ├── style.tsx
│ │ │ │ ├── styled-counter.tsx
│ │ │ │ ├── svg.tsx
│ │ │ │ ├── swr.tsx
│ │ │ │ ├── tanstack-query.tsx
│ │ │ │ ├── tanstack-virtual.tsx
│ │ │ │ ├── task-tracker.tsx
│ │ │ │ ├── tic-tac-toe.jsx
│ │ │ │ ├── todolist.tsx
│ │ │ │ ├── type-race.jsx
│ │ │ │ ├── valtio-counter.tsx
│ │ │ │ ├── virtual-whiteboard.tsx
│ │ │ │ ├── weather-app.tsx
│ │ │ │ ├── wouter.tsx
│ │ │ │ └── zustand-counter.tsx
│ │ │ ├── main.tsx
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── million/
│ │ ├── README.md
│ │ ├── alias.ts
│ │ ├── array.ts
│ │ ├── block.ts
│ │ ├── constants.ts
│ │ ├── dom.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── template.ts
│ │ └── types.ts
│ ├── react/
│ │ ├── README.md
│ │ ├── block.ts
│ │ ├── compiled-block.ts
│ │ ├── constants.ts
│ │ ├── for.ts
│ │ ├── index.ts
│ │ ├── its-fine.tsx
│ │ ├── package.json
│ │ └── utils.ts
│ ├── react-server/
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── utils.ts
│ ├── telemetry/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── config.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── post.ts
│ │ ├── project-info.ts
│ │ ├── system-info.ts
│ │ └── utils/
│ │ ├── detect-package-manager.ts
│ │ ├── is-ci.ts
│ │ ├── is-docker.ts
│ │ ├── is-inside-container.ts
│ │ └── is-wsl.ts
│ └── types/
│ ├── README.md
│ ├── index.ts
│ └── package.json
├── pnpm-workspace.yaml
├── react-server.d.ts
├── react.d.ts
├── test/
│ ├── alias.test.ts
│ ├── block.test.ts
│ ├── map-array.test.ts
│ └── prettier.js
├── tsconfig.json
├── types.d.ts
├── vitest.config.ts
└── website/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── components/
│ ├── ad.tsx
│ ├── automatic-mode-warning.tsx
│ ├── back-in-block/
│ │ ├── block-vdom.tsx
│ │ ├── combined-block.tsx
│ │ ├── count.tsx
│ │ ├── slideshow.tsx
│ │ ├── static-analysis.tsx
│ │ └── vdom.tsx
│ ├── bounties.tsx
│ ├── box.tsx
│ ├── chart.tsx
│ ├── cursor.tsx
│ ├── demo.tsx
│ ├── extra-content.tsx
│ ├── home/
│ │ ├── about.tsx
│ │ ├── container.tsx
│ │ ├── cta.tsx
│ │ ├── faq.tsx
│ │ ├── hero.tsx
│ │ ├── index.tsx
│ │ ├── shimmer-button.tsx
│ │ └── showcase.tsx
│ ├── icons/
│ │ ├── discord-icon.tsx
│ │ ├── github-icon.tsx
│ │ └── twitter-x-icon.tsx
│ ├── lint/
│ │ ├── article-wrapper.tsx
│ │ ├── border-beam.tsx
│ │ ├── cases.tsx
│ │ ├── index.tsx
│ │ ├── intro.tsx
│ │ ├── join.tsx
│ │ ├── left.tsx
│ │ └── right.tsx
│ ├── million-library/
│ │ ├── block-878fb9ae.d.ts
│ │ ├── chunks/
│ │ │ ├── block.cjs
│ │ │ ├── block.mjs
│ │ │ ├── constants.cjs
│ │ │ └── constants.mjs
│ │ ├── compiler.cjs
│ │ ├── compiler.d.ts
│ │ ├── compiler.mjs
│ │ ├── jsx-runtime.cjs
│ │ ├── jsx-runtime.d.ts
│ │ ├── jsx-runtime.mjs
│ │ ├── million.cjs
│ │ ├── million.d.ts
│ │ ├── million.mjs
│ │ ├── react-server.cjs
│ │ ├── react-server.d.ts
│ │ ├── react-server.mjs
│ │ ├── react.cjs
│ │ ├── react.d.ts
│ │ ├── react.mjs
│ │ └── types-35702ad2.d.ts
│ ├── retro-grid.tsx
│ ├── spotlight.tsx
│ ├── use-dark-mode.tsx
│ └── wrapped/
│ ├── index.tsx
│ └── useMockProgress.ts
├── hooks/
│ └── use-translations.ts
├── middleware.js
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pages/
│ ├── _app.mdx
│ ├── _meta.en-US.json
│ ├── _meta.es-ES.json
│ ├── _meta.fr-FR.json
│ ├── _meta.zh-CN.json
│ ├── api/
│ │ └── og.tsx
│ ├── blog/
│ │ ├── .prettierignore
│ │ ├── _meta.en-US.json
│ │ ├── _meta.es-ES.json
│ │ ├── _meta.fr-FR.json
│ │ ├── _meta.zh-CN.json
│ │ ├── behind-the-block.en-US.mdx
│ │ ├── behind-the-block.es-ES.mdx
│ │ ├── behind-the-block.fr-FR.mdx
│ │ ├── behind-the-block.zh-CN.mdx
│ │ ├── lint.en-US.mdx
│ │ ├── million-3.en-US.mdx
│ │ ├── million-3.es-ES.mdx
│ │ ├── million-3.fr-FR.mdx
│ │ ├── million-3.zh-CN.mdx
│ │ ├── million-beyond-speed.en-US.mdx
│ │ ├── million-beyond-speed.es-ES.mdx
│ │ ├── million-beyond-speed.fr-FR.mdx
│ │ ├── million-beyond-speed.zh-CN.mdx
│ │ ├── million-v2.5.1.en-US.mdx
│ │ ├── million-v2.5.1.es-ES.mdx
│ │ ├── million-v2.5.1.fr-FR.mdx
│ │ ├── million-v2.5.1.zh-CN.mdx
│ │ ├── million-v2.5.3.en-US.mdx
│ │ ├── million-v2.5.3.es-ES.mdx
│ │ ├── million-v2.5.3.fr-FR.mdx
│ │ ├── million-v2.5.3.zh-CN.mdx
│ │ ├── virtual-dom.en-US.mdx
│ │ ├── virtual-dom.es-ES.mdx
│ │ ├── virtual-dom.fr-FR.mdx
│ │ └── virtual-dom.zh-CN.mdx
│ ├── blog.en-US.mdx
│ ├── blog.es-ES.mdx
│ ├── blog.fr-FR.mdx
│ ├── blog.zh-CN.mdx
│ ├── code-policy.en-US.mdx
│ ├── docs/
│ │ ├── _meta.en-US.json
│ │ ├── _meta.es-ES.json
│ │ ├── _meta.fr-FR.json
│ │ ├── _meta.zh-CN.json
│ │ ├── automatic.en-US.mdx
│ │ ├── automatic.es-ES.mdx
│ │ ├── automatic.fr-FR.mdx
│ │ ├── automatic.zh-CN.mdx
│ │ ├── experimental.en-US.mdx
│ │ ├── index.zh-CN.mdx
│ │ ├── install.en-US.mdx
│ │ ├── install.es-ES.mdx
│ │ ├── install.fr-FR.mdx
│ │ ├── install.zh-CN.mdx
│ │ ├── internals/
│ │ │ ├── _meta.en-US.json
│ │ │ ├── _meta.es-ES.json
│ │ │ ├── _meta.fr-FR.json
│ │ │ ├── _meta.zh-CN.json
│ │ │ ├── block.en-US.mdx
│ │ │ ├── block.es-ES.mdx
│ │ │ ├── block.fr-FR.mdx
│ │ │ ├── block.zh-CN.mdx
│ │ │ ├── map-array.en-US.mdx
│ │ │ ├── map-array.es-ES.mdx
│ │ │ ├── map-array.fr-FR.mdx
│ │ │ ├── map-array.zh-CN.mdx
│ │ │ ├── mount.en-US.mdx
│ │ │ ├── mount.es-ES.mdx
│ │ │ ├── mount.fr-FR.mdx
│ │ │ ├── mount.zh-CN.mdx
│ │ │ ├── patch.en-US.mdx
│ │ │ ├── patch.es-ES.mdx
│ │ │ ├── patch.fr-FR.mdx
│ │ │ ├── patch.zh-CN.mdx
│ │ │ ├── render-to-template.en-US.mdx
│ │ │ ├── render-to-template.es-ES.mdx
│ │ │ ├── render-to-template.fr-FR.mdx
│ │ │ ├── render-to-template.zh-CN.mdx
│ │ │ ├── string-to-dom.en-US.mdx
│ │ │ ├── string-to-dom.es-ES.mdx
│ │ │ ├── string-to-dom.fr-FR.mdx
│ │ │ └── string-to-dom.zh-CN.mdx
│ │ ├── introduction.en-US.mdx
│ │ ├── introduction.es-ES.mdx
│ │ ├── introduction.fr-FR.mdx
│ │ ├── introduction.zh-CN.mdx
│ │ └── manual-mode/
│ │ ├── _meta.en-US.json
│ │ ├── _meta.es-ES.json
│ │ ├── _meta.fr-FR.json
│ │ ├── _meta.zh-CN.json
│ │ ├── block.en-US.mdx
│ │ ├── block.es-ES.mdx
│ │ ├── block.fr-FR.mdx
│ │ ├── block.zh-CN.mdx
│ │ ├── for.en-US.mdx
│ │ ├── for.es-ES.mdx
│ │ ├── for.fr-FR.mdx
│ │ ├── for.zh-CN.mdx
│ │ ├── manual-mode.en-US.mdx
│ │ ├── manual-mode.es-ES.mdx
│ │ ├── manual-mode.fr-FR.mdx
│ │ ├── manual-mode.zh-CN.mdx
│ │ ├── virtualization.en-US.mdx
│ │ ├── virtualization.es-ES.mdx
│ │ ├── virtualization.fr-FR.mdx
│ │ └── virtualization.zh-CN.mdx
│ ├── faq.en-US.mdx
│ ├── faq.es-ES.mdx
│ ├── faq.fr-FR.mdx
│ ├── faq.zh-CN.mdx
│ ├── foundation.en-US.mdx
│ ├── foundation.es-ES.mdx
│ ├── foundation.fr-FR.mdx
│ ├── foundation.zh-CN.mdx
│ ├── index.en-US.mdx
│ ├── index.es-ES.mdx
│ ├── index.fr-FR.mdx
│ ├── index.zh-CN.mdx
│ ├── lint.en-US.mdx
│ ├── privacy.en-US.mdx
│ ├── showcase.en-US.mdx
│ ├── showcase.es-ES.mdx
│ ├── showcase.fr-FR.mdx
│ ├── showcase.zh-CN.mdx
│ ├── telemetry.en-US.mdx
│ ├── terms.en-US.mdx
│ └── wrapped/
│ ├── [id].mdx
│ └── _meta.json
├── postcss.config.js
├── public/
│ └── lint/
│ └── demo.mp4.json
├── styles/
│ ├── global.css
│ └── wrapped.css
├── tailwind.config.js
├── theme.config.tsx
├── translations.ts
├── tsconfig.json
├── vercel.json
└── video.d.ts
SYMBOL INDEX (533 symbols across 120 files)
FILE: build.config.ts
method 'rollup:options' (line 30) | 'rollup:options'(_ctx, options) {
FILE: packages/compiler/auto.ts
type JSXStateContext (line 24) | interface JSXStateContext {
function measureExpression (line 34) | function measureExpression(
function measureJSXSpreadChild (line 59) | function measureJSXSpreadChild(
function measureJSXExpressionContainer (line 66) | function measureJSXExpressionContainer(
function measureJSXAttribute (line 77) | function measureJSXAttribute(
function measureJSXSpreadAttribute (line 91) | function measureJSXSpreadAttribute(
function measureJSXAttributes (line 98) | function measureJSXAttributes(
function measureJSXElement (line 114) | function measureJSXElement(
function measureJSXExpressions (line 135) | function measureJSXExpressions(
function shouldTransform (line 160) | function shouldTransform(
function transformFunctionDeclaration (line 220) | function transformFunctionDeclaration(
function isValidFunction (line 287) | function isValidFunction(
function transformVariableDeclarator (line 293) | function transformVariableDeclarator(
function transformCallExpression (line 341) | function transformCallExpression(
function transformAuto (line 391) | function transformAuto(
FILE: packages/compiler/babel.ts
type PluginState (line 10) | interface PluginState extends PluginPass {
function babel (line 15) | function babel(): PluginObj<PluginState> {
FILE: packages/compiler/block.ts
type JSXStateContext (line 21) | interface JSXStateContext {
function pushExpression (line 30) | function pushExpression(state: JSXStateContext, expr: t.Expression): str...
function pushExpressionAndReplace (line 41) | function pushExpressionAndReplace(
function extractJSXExpressionsFromExpression (line 58) | function extractJSXExpressionsFromExpression(
function extractJSXExpressionsFromJSXSpreadChild (line 77) | function extractJSXExpressionsFromJSXSpreadChild(
function extractJSXExpressionsFromJSXExpressionContainer (line 84) | function extractJSXExpressionsFromJSXExpressionContainer(
function extractJSXExpressionsFromJSXAttribute (line 95) | function extractJSXExpressionsFromJSXAttribute(
function extractJSXExpressionsFromJSXSpreadAttribute (line 109) | function extractJSXExpressionsFromJSXSpreadAttribute(
function extractJSXExpressionsFromJSXAttributes (line 116) | function extractJSXExpressionsFromJSXAttributes(
function isJSXSVGElement (line 132) | function isJSXSVGElement(path: babel.NodePath<t.JSXElement>): boolean {
function isJSXForElement (line 147) | function isJSXForElement(
function transformJSXForElement (line 169) | function transformJSXForElement(
function extractJSXExpressionsFromJSXElement (line 180) | function extractJSXExpressionsFromJSXElement(
function extractJSXChildren (line 205) | function extractJSXChildren(
function extractJSXExpressions (line 233) | function extractJSXExpressions(
function transformJSX (line 261) | function transformJSX(
function transformBlock (line 388) | function transformBlock(
FILE: packages/compiler/constants.ts
constant RENDER_SCOPE (line 3) | const RENDER_SCOPE = 'slot';
constant SKIP_ANNOTATION (line 4) | const SKIP_ANNOTATION = '@million skip';
constant IGNORE_ANNOTATION (line 5) | const IGNORE_ANNOTATION = 'million-ignore';
constant JSX_SKIP_ANNOTATION (line 6) | const JSX_SKIP_ANNOTATION = '@million jsx-skip';
constant SVG_ELEMENTS (line 7) | const SVG_ELEMENTS = [
constant NO_PX_PROPERTIES (line 23) | const NO_PX_PROPERTIES = [
type TrackedImports (line 61) | interface TrackedImports {
constant INVERSE_IMPORTS (line 72) | const INVERSE_IMPORTS = {
constant TRACKED_IMPORTS (line 83) | const TRACKED_IMPORTS: TrackedImports = {
type HiddenImports (line 110) | interface HiddenImports {
constant HIDDEN_IMPORTS (line 117) | const HIDDEN_IMPORTS: HiddenImports = {
type ReactImports (line 132) | interface ReactImports {
constant REACT_IMPORTS (line 139) | const REACT_IMPORTS: ReactImports = {
FILE: packages/compiler/experimental/types.ts
type IrEditBase (line 8) | interface IrEditBase {
type IrEditAttribute (line 17) | interface IrEditAttribute extends IrEditBase {
type IrEditStyleAttribute (line 23) | interface IrEditStyleAttribute extends IrEditBase {
type IrEditSvgAttribute (line 29) | interface IrEditSvgAttribute extends IrEditBase {
type IrEditChild (line 35) | interface IrEditChild extends IrEditBase {
type IrEditBlock (line 41) | interface IrEditBlock extends IrEditBase {
type IrEditEvent (line 46) | interface IrEditEvent extends IrEditBase {
type IrInitEvent (line 52) | interface IrInitEvent extends IrEditBase {
type IrInitChild (line 58) | interface IrInitChild extends IrEditBase {
type IrEdit (line 64) | interface IrEdit {
type IrTreeNode (line 77) | interface IrTreeNode {
type IrPrunedNode (line 82) | interface IrPrunedNode {
FILE: packages/compiler/for.old.ts
method JSXElement (line 69) | JSXElement(path) {
method JSXAttribute (line 76) | JSXAttribute(path) {
method JSXSpreadAttribute (line 85) | JSXSpreadAttribute(path) {
method JSXSpreadChild (line 89) | JSXSpreadChild(path) {
method Identifier (line 93) | Identifier(path: NodePath<t.Identifier>) {
FILE: packages/compiler/index.ts
method webpack (line 25) | webpack(
FILE: packages/compiler/plugin.ts
constant DEFAULT_INCLUDE (line 13) | const DEFAULT_INCLUDE = '**/*.{jsx,tsx}';
constant DEFAULT_EXCLUDE (line 14) | const DEFAULT_EXCLUDE = 'node_modules/**/*.{jsx,tsx,ts,js,mjs,cjs}';
type CompilerOutput (line 16) | interface CompilerOutput {
function compile (line 23) | async function compile(
type Options (line 84) | interface Options extends Omit<CompilerOptions, 'telemetry'> {
method transformInclude (line 146) | transformInclude(id: string): boolean {
method transform (line 149) | async transform(code: string, id: string) {
method configResolved (line 169) | configResolved(config) {
method transform (line 185) | async transform(code, id, opts) {
FILE: packages/compiler/types.ts
type NamedImportDefinition (line 4) | interface NamedImportDefinition {
type DefaultImportDefinition (line 10) | interface DefaultImportDefinition {
type ImportDefinition (line 15) | type ImportDefinition = NamedImportDefinition | DefaultImportDefinition;
type CompilerOptions (line 17) | interface CompilerOptions {
type StateContext (line 40) | interface StateContext {
FILE: packages/compiler/utils/checks.ts
type TypeFilter (line 3) | type TypeFilter<V extends t.Node> = (node: t.Node) => node is V;
function isPathValid (line 5) | function isPathValid<V extends t.Node>(
type NestedExpression (line 12) | type NestedExpression =
function isNestedExpression (line 21) | function isNestedExpression(node: t.Node): node is NestedExpression {
type ComponentNode (line 36) | type ComponentNode =
function isComponent (line 41) | function isComponent(node: t.Node): node is ComponentNode {
function isComponentishName (line 52) | function isComponentishName(name: string): boolean {
function isStatementTopLevel (line 56) | function isStatementTopLevel(
FILE: packages/compiler/utils/generate-unique-name.ts
function generateUniqueName (line 4) | function generateUniqueName(
FILE: packages/compiler/utils/get-descriptive-name.ts
function getDescriptiveName (line 3) | function getDescriptiveName(
FILE: packages/compiler/utils/get-import-specifier.ts
function getImportIdentifier (line 5) | function getImportIdentifier(
FILE: packages/compiler/utils/get-root-statement-path.ts
function getRootStatementPath (line 4) | function getRootStatementPath(path: babel.NodePath): babel.NodePath {
FILE: packages/compiler/utils/get-valid-import-definition.ts
function isValidIdentifier (line 5) | function isValidIdentifier(
function getValidImportDefinitionFromIdentifier (line 11) | function getValidImportDefinitionFromIdentifier(
function getValidImportDefinitionFromMemberExpression (line 29) | function getValidImportDefinitionFromMemberExpression(
function getValidImportDefinitionFromJSXMemberExpression (line 64) | function getValidImportDefinitionFromJSXMemberExpression(
function getValidImportDefinition (line 98) | function getValidImportDefinition(
FILE: packages/compiler/utils/is-guaranteed-literal.ts
function isGuaranteedLiteral (line 3) | function isGuaranteedLiteral(node: t.Expression): node is t.Literal {
FILE: packages/compiler/utils/is-jsx-component-element.ts
function isJSXComponentElement (line 6) | function isJSXComponentElement(
FILE: packages/compiler/utils/register-import-definition.ts
function getImportSpecifierName (line 4) | function getImportSpecifierName(specifier: t.ImportSpecifier): string {
function registerImportDefinition (line 11) | function registerImportDefinition(
FILE: packages/compiler/utils/unwrap-node.ts
type TypeCheck (line 4) | type TypeCheck<K> = K extends ((node: t.Node) => node is infer U extends...
type TypeFilter (line 8) | type TypeFilter = (node: t.Node) => boolean;
function unwrapNode (line 10) | function unwrapNode<K extends TypeFilter>(
FILE: packages/compiler/utils/unwrap-path.ts
type TypeFilter (line 4) | type TypeFilter<V extends t.Node> = (node: t.Node) => node is V;
function unwrapPath (line 6) | function unwrapPath<V extends t.Node>(
FILE: packages/kitchen-sink/src/examples/age-calculator.tsx
function handleSubmit (line 19) | function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
FILE: packages/kitchen-sink/src/examples/book-recommendation.jsx
function getBooks (line 16) | async function getBooks() {
FILE: packages/kitchen-sink/src/examples/calculator.tsx
type ICalculator (line 4) | type ICalculator = {
type IControls (line 10) | type IControls = {
function Calculator (line 19) | function Calculator() {
function Controls (line 77) | function Controls({
FILE: packages/kitchen-sink/src/examples/crypto-tracker.jsx
function CryptoTracker (line 68) | function CryptoTracker() {
FILE: packages/kitchen-sink/src/examples/currency-convertor.tsx
type RatesData (line 4) | interface RatesData {
type CurrencyInputProps (line 7) | interface CurrencyInputProps {
function CurrencyConvertor (line 15) | function CurrencyConvertor() {
FILE: packages/kitchen-sink/src/examples/data-converter.tsx
type DataConverterState (line 4) | interface DataConverterState {
type DataUnit (line 11) | enum DataUnit {
FILE: packages/kitchen-sink/src/examples/discount-calculator.tsx
type Results (line 4) | interface Results {
FILE: packages/kitchen-sink/src/examples/emoji-picker.tsx
type AppProps (line 5) | interface AppProps {}
FILE: packages/kitchen-sink/src/examples/etch-a-sketch.tsx
type GridElement (line 4) | interface GridElement {
FILE: packages/kitchen-sink/src/examples/expense-tracker.tsx
type Transaction (line 4) | type Transaction = {
type Wallet (line 11) | type Wallet = {
function calculateExpenses (line 33) | function calculateExpenses() {
function genUUID (line 54) | function genUUID() {
type InputFormProps (line 126) | type InputFormProps = {
FILE: packages/kitchen-sink/src/examples/file-upload-preview.jsx
function getFile (line 14) | function getFile(event) {
FILE: packages/kitchen-sink/src/examples/guestbook.tsx
type Comment (line 5) | interface Comment {
FILE: packages/kitchen-sink/src/examples/hangman-game.tsx
type Drawing (line 84) | interface Drawing {
type Keyboard (line 254) | interface Keyboard {
type Key (line 279) | interface Key {
type WordProps (line 394) | interface WordProps {
type Letter (line 422) | interface Letter {
function App (line 458) | function App() {
FILE: packages/kitchen-sink/src/examples/interactive-card-game.tsx
type Card (line 3) | interface Card {
FILE: packages/kitchen-sink/src/examples/investment-calculator.tsx
type Results (line 4) | interface Results {
FILE: packages/kitchen-sink/src/examples/million-quiz.jsx
constant SECS_PER_QUESTION (line 5) | const SECS_PER_QUESTION = 30;
function reducer (line 128) | function reducer(state, action) {
FILE: packages/kitchen-sink/src/examples/mortgage-calculator.tsx
type Results (line 4) | interface Results {
FILE: packages/kitchen-sink/src/examples/movie-finder.tsx
type MovieProps (line 4) | interface MovieProps {
FILE: packages/kitchen-sink/src/examples/news-aggregator.tsx
type Source (line 4) | interface Source {
type Article (line 8) | interface Article {
type NewsCardProps (line 16) | interface NewsCardProps {
FILE: packages/kitchen-sink/src/examples/number-guessing.tsx
type NumberGuessingGameProps (line 4) | interface NumberGuessingGameProps {}
FILE: packages/kitchen-sink/src/examples/password-generator.tsx
type IPasswordType (line 136) | type IPasswordType = 'password' | 'pin';
type IPasswordState (line 138) | interface IPasswordState {
type IToolTipProps (line 147) | interface IToolTipProps {
FILE: packages/kitchen-sink/src/examples/redux-todo.tsx
type Todo (line 11) | interface Todo {
type TodoItemProps (line 41) | type TodoItemProps = {
FILE: packages/kitchen-sink/src/examples/repro.tsx
constant AUTO_ARCHIVE_OPTIONS (line 3) | const AUTO_ARCHIVE_OPTIONS = [
FILE: packages/kitchen-sink/src/examples/tanstack-query.tsx
function TanstackQuery (line 11) | function TanstackQuery() {
FILE: packages/kitchen-sink/src/examples/task-tracker.tsx
type itemType (line 11) | type itemType = {
type ListItem (line 16) | type ListItem = {
type ListData (line 21) | type ListData = {
type editItemFuncType (line 27) | type editItemFuncType = (
type addItemFuncType (line 32) | type addItemFuncType = (section: string, itemName: string) => void;
type deleteItemFuncType (line 33) | type deleteItemFuncType = (section: string, uId: string) => void;
type ListDisplayProps (line 35) | type ListDisplayProps = {
type ListItemPropType (line 43) | type ListItemPropType = {
function getSectionTitleColor (line 126) | function getSectionTitleColor(sectionType: string) {
function itemOnInputHandler (line 141) | function itemOnInputHandler(value: string) {
function addItemHandler (line 144) | function addItemHandler() {
function deleteItemHandler (line 149) | function deleteItemHandler() {
function inputChangeHandler (line 240) | function inputChangeHandler(value: string) {
function addItemHandler (line 243) | function addItemHandler() {
function cancleAddItemHandler (line 249) | function cancleAddItemHandler() {
function keyDownHandler (line 253) | function keyDownHandler(key: string) {
function addItem (line 349) | function addItem(section: string, itemName: string) {
function editItem (line 359) | function editItem(section: string, uId: string, newItemName: string) {
function deleteItem (line 370) | function deleteItem(section: string, uId: string) {
FILE: packages/kitchen-sink/src/examples/tic-tac-toe.jsx
function Square (line 4) | function Square({ onClick, value }) {
function calculateWinner (line 122) | function calculateWinner(squares) {
FILE: packages/kitchen-sink/src/examples/type-race.jsx
function getSentence (line 23) | async function getSentence() {
function HealthBar (line 39) | function HealthBar({ status, time, score, currentStc, stopGame }) {
function TypingArea (line 76) | function TypingArea({
function playGame (line 177) | function playGame() {
function onSentenceCompleted (line 199) | function onSentenceCompleted() {
function pushStcLog (line 211) | function pushStcLog(log) {
function stopGame (line 216) | function stopGame() {
FILE: packages/kitchen-sink/src/examples/virtual-whiteboard.tsx
type Mode (line 5) | enum Mode {
type CoordinateType (line 13) | type CoordinateType = Array<{
FILE: packages/kitchen-sink/src/examples/weather-app.tsx
type Weather (line 55) | type Weather = {
function Weather (line 81) | function Weather() {
FILE: packages/kitchen-sink/src/main.tsx
type Module (line 16) | type Module = { default: ComponentType<any> };
function App (line 33) | function App() {
FILE: packages/million/alias.ts
function getAttributeAlias (line 97) | function getAttributeAlias(name: string): string {
FILE: packages/million/array.ts
class ArrayBlock (line 11) | class ArrayBlock extends AbstractBlock {
method constructor (line 13) | constructor(children: AbstractBlock[]) {
method v (line 17) | v() {
method p (line 20) | p(fragment: ArrayBlock) {
method m (line 135) | m(parent: HTMLElement, refNode: Node | null = null): HTMLElement {
method x (line 144) | x() {
method u (line 155) | u(): boolean {
method s (line 158) | s() {
method t (line 161) | t(): HTMLElement | null | undefined {
FILE: packages/million/block.ts
constant HOLE_PROXY (line 33) | const HOLE_PROXY = new Proxy(
method get (line 38) | get(_, key: string): Hole {
class Block (line 109) | class Block extends AbstractBlock {
method constructor (line 113) | constructor(
method m (line 140) | m(
method p (line 244) | p(newBlock: AbstractBlock): HTMLElement {
method v (line 317) | v(block: AbstractBlock | null = null, refNode: Node | null = null): vo...
method x (line 320) | x(): void {
method u (line 324) | u(_oldProps: MillionProps, _newProps: MillionProps): boolean {
method s (line 328) | s(): string {
method t (line 331) | t(): HTMLElement | null | undefined {
FILE: packages/million/constants.ts
constant TEXT_NODE_CACHE (line 20) | const TEXT_NODE_CACHE = '__t';
constant EVENT_PATCH (line 21) | const EVENT_PATCH = '__p';
constant EVENTS_REGISTRY (line 22) | const EVENTS_REGISTRY = '__e';
constant IS_NON_DIMENSIONAL (line 23) | const IS_NON_DIMENSIONAL =
constant XLINK_NS (line 25) | const XLINK_NS = 'http://www.w3.org/1999/xlink';
constant XML_NS (line 26) | const XML_NS = 'http://www.w3.org/2000/xmlns/';
constant X_CHAR (line 27) | const X_CHAR = 120;
constant NON_PROPS (line 29) | const NON_PROPS = new Set$(['href', 'list', 'form', 'tabIndex', 'downloa...
constant VOID_ELEMENTS (line 31) | const VOID_ELEMENTS = new Set$(["area", "base", "basefont", "bgsound", "...
FILE: packages/million/dom.ts
constant HTM_TEMPLATE (line 47) | const HTM_TEMPLATE = /**@__PURE__*/ document$.createElement('template');
constant HTM_TEMPLATE_CONTENT (line 48) | const HTM_TEMPLATE_CONTENT = HTM_TEMPLATE.content;
constant SVG_TEMPLATE (line 50) | const SVG_TEMPLATE = /**@__PURE__*/ document$.createElementNS(
method get (line 118) | get() {
FILE: packages/million/types.ts
type Flags (line 3) | const enum Flags {
type VNode (line 12) | type VNode =
type VElement (line 21) | interface VElement {
type Hole (line 30) | interface Hole {
type EditAttribute (line 65) | interface EditAttribute {
type EditStyleAttribute (line 76) | interface EditStyleAttribute {
type EditSvgAttribute (line 87) | interface EditSvgAttribute {
type EditChild (line 98) | interface EditChild {
type EditEvent (line 109) | interface EditEvent {
type InitEvent (line 120) | interface InitEvent {
type InitChild (line 131) | interface InitChild {
type InitBlock (line 142) | interface InitBlock {
type Edit (line 153) | interface Edit {
FILE: packages/react-server/index.ts
function MillionBlockLoader (line 50) | function MillionBlockLoader(props?: P) {
function For (line 151) | function For<T>({ each, children, ssr, svg }: MillionArrayProps<T>) {
function Effect (line 179) | function Effect({ effect }: { effect: () => void }) {
function Suspend (line 254) | function Suspend({
function isEqual (line 304) | function isEqual(a: unknown, b: unknown): boolean {
function shouldCompiledBlockUpdate (line 310) | function shouldCompiledBlockUpdate(
type CompiledBlockOptions (line 322) | interface CompiledBlockOptions
function compiledBlock (line 328) | function compiledBlock(
FILE: packages/react-server/utils.ts
type SSRContextValue (line 26) | interface SSRContextValue {
type SSRProviderProps (line 44) | interface SSRProviderProps {
function useCounter (line 106) | function useCounter(isDisabled = false) {
function useLegacySSRSafeId (line 149) | function useLegacySSRSafeId(defaultId?: string): string {
function useModernSSRSafeId (line 168) | function useModernSSRSafeId(defaultId?: string): string {
function getSnapshot (line 184) | function getSnapshot() {
function getServerSnapshot (line 188) | function getServerSnapshot() {
function subscribe (line 192) | function subscribe(onStoreChange: () => void): () => void {
function useIsSSR (line 202) | function useIsSSR(): boolean {
FILE: packages/react/compiled-block.ts
function isEqual (line 8) | function isEqual(a: unknown, b: unknown): boolean {
function shouldCompiledBlockUpdate (line 14) | function shouldCompiledBlockUpdate(
type CompiledBlockOptions (line 26) | interface CompiledBlockOptions
function compiledBlock (line 31) | function compiledBlock(
FILE: packages/react/constants.ts
constant RENDER_SCOPE (line 4) | const RENDER_SCOPE = 'slot';
constant SVG_RENDER_SCOPE (line 5) | const SVG_RENDER_SCOPE = 'g';
constant REACT_ROOT (line 6) | const REACT_ROOT = '__react_root';
constant REGISTRY (line 19) | const REGISTRY = new Map<ComponentType, any>();
FILE: packages/react/index.ts
constant INTERNALS (line 7) | const INTERNALS = {
FILE: packages/react/its-fine.tsx
type Fiber (line 37) | type Fiber = ReactReconciler.Fiber;
type FiberSelector (line 42) | type FiberSelector = (node: Fiber) => boolean | void;
function traverse (line 47) | function traverse(
function useFiber (line 69) | function useFiber(): Fiber {
function useNearestParent (line 91) | function useNearestParent<
function useContainer (line 124) | function useContainer<
function useNearestParentFiber (line 145) | function useNearestParentFiber(
FILE: packages/telemetry/config.ts
type ConfigOptions (line 6) | interface ConfigOptions {
class GlobalConfig (line 33) | class GlobalConfig {
method constructor (line 37) | constructor(private project: ConfigOptions) {
method store (line 43) | private get store(): Record<string, any> {
method store (line 55) | private set store(value: Record<string, any>) {
method ensureDir (line 59) | private ensureDir(): void {
method write (line 62) | write(): void {
method clear (line 65) | clear(): void {
method delete (line 69) | delete(key: string): boolean {
method get (line 74) | get(key: string): any {
method has (line 77) | has(key: string): boolean {
method set (line 80) | set(key: string, value: any): void {
FILE: packages/telemetry/index.ts
type TelemetryEvent (line 9) | interface TelemetryEvent {
class MillionTelemetry (line 14) | class MillionTelemetry {
method constructor (line 21) | constructor(private TELEMETRY_DISABLED = false) {
method getConfigWithFallback (line 25) | private getConfigWithFallback<T>(key: string, getValue: () => T): T {
method enabled (line 35) | private get enabled(): boolean {
method anonymousId (line 39) | private get anonymousId(): string {
method anonymousSessionId (line 45) | get anonymousSessionId(): string {
method anonymousProjectInfo (line 51) | private get anonymousProjectInfo(): ProjectInfo {
method isDisabled (line 57) | private get isDisabled(): boolean {
method setEnabled (line 64) | setEnabled(value: boolean): void {
method clear (line 68) | clear(): void {
method showWrapped (line 72) | showWrapped(): void {
method record (line 86) | record({ event, payload }: TelemetryEvent): Promise<void> {
FILE: packages/telemetry/post.ts
constant MILLION_TELEMETRY_ENDPOINT (line 11) | const MILLION_TELEMETRY_ENDPOINT =
FILE: packages/telemetry/project-info.ts
type ProjectInfo (line 6) | interface ProjectInfo {
FILE: packages/telemetry/system-info.ts
type SystemInfo (line 6) | interface SystemInfo {
FILE: packages/telemetry/utils/detect-package-manager.ts
type PackageManager (line 1) | interface PackageManager {
FILE: packages/telemetry/utils/is-ci.ts
type Info (line 3) | interface Info {
constant VENDORS (line 75) | const VENDORS = [
FILE: packages/types/index.ts
type MillionProps (line 4) | type MillionProps = Record<string, any>;
type Options (line 6) | interface Options<T extends MillionProps> {
type MillionArrayProps (line 19) | interface MillionArrayProps<T> {
type ArrayCache (line 31) | interface ArrayCache<T> {
type MillionPortal (line 38) | interface MillionPortal {
FILE: website/components/automatic-mode-warning.tsx
function AutomaticModeWarning (line 3) | function AutomaticModeWarning() {
FILE: website/components/back-in-block/count.tsx
function CountExample (line 3) | function CountExample() {
FILE: website/components/back-in-block/slideshow.tsx
function Slideshow (line 12) | function Slideshow({
FILE: website/components/bounties.tsx
type RemoteData (line 12) | type RemoteData<T> =
type Bounty (line 17) | type Bounty = AlgoraOutput['bounty']['list']['items'][number];
function Bounties (line 19) | function Bounties() {
function BountyCard (line 58) | function BountyCard(props: { bounty: Bounty }) {
function BountyCardSkeleton (line 82) | function BountyCardSkeleton() {
function Callout (line 94) | function Callout() {
FILE: website/components/box.tsx
function Box (line 10) | function Box({
FILE: website/components/chart.tsx
function Chart (line 51) | function Chart() {
type BarChartProps (line 71) | interface BarChartProps {
FILE: website/components/cursor.tsx
function Cursor (line 1) | function Cursor({
FILE: website/components/demo.tsx
function Demo (line 3) | function Demo() {
FILE: website/components/extra-content.tsx
function ExtraContent (line 7) | function ExtraContent() {
function Showdown (line 15) | function Showdown({ initStart = false, amount = 1000 }) {
FILE: website/components/home/about.tsx
function About (line 14) | function About() {
function Graphic (line 140) | function Graphic() {
function GraphicWrapper (line 179) | function GraphicWrapper({
function Card (line 233) | function Card({ title, description, icon }) {
function ThumbsUpIcon (line 256) | function ThumbsUpIcon() {
function LightBulbIcon (line 275) | function LightBulbIcon() {
function BoxIcon (line 294) | function BoxIcon() {
function LightningIcon (line 313) | function LightningIcon() {
function ClockIcon (line 332) | function ClockIcon() {
FILE: website/components/home/container.tsx
function Container (line 1) | function Container({ children }) {
FILE: website/components/home/cta.tsx
function CTA (line 7) | function CTA() {
FILE: website/components/home/faq.tsx
function FAQ (line 7) | function FAQ() {
function Disclosures (line 31) | function Disclosures({ full = false }) {
FILE: website/components/home/hero.tsx
function Hero (line 20) | function Hero() {
function Blur (line 135) | function Blur() {
function Companies (line 147) | function Companies() {
function WyzeLogo (line 243) | function WyzeLogo() {
FILE: website/components/home/index.tsx
function Home (line 6) | function Home(): JSX.Element {
FILE: website/components/home/shimmer-button.tsx
type ShimmerButtonProps (line 3) | interface ShimmerButtonProps {
FILE: website/components/home/showcase.tsx
function Showcase (line 11) | function Showcase() {
FILE: website/components/icons/discord-icon.tsx
function DiscordIcon (line 1) | function DiscordIcon() {
FILE: website/components/icons/github-icon.tsx
function GitHubIcon (line 1) | function GitHubIcon() {
FILE: website/components/icons/twitter-x-icon.tsx
function TwitterXIcon (line 3) | function TwitterXIcon() {
FILE: website/components/lint/article-wrapper.tsx
function ArticleWrapper (line 1) | function ArticleWrapper(
FILE: website/components/lint/border-beam.tsx
type BorderBeamProps (line 1) | interface BorderBeamProps {
FILE: website/components/lint/cases.tsx
function Platform (line 5) | function Platform(props: React.HTMLAttributes<HTMLDivElement>) {
type PlatformCardProps (line 39) | interface PlatformCardProps extends React.HTMLAttributes<HTMLDivElement> {
function PlatformCard (line 45) | function PlatformCard(props: PlatformCardProps) {
FILE: website/components/lint/index.tsx
function Home (line 5) | function Home() {
FILE: website/components/lint/intro.tsx
function Intro (line 130) | function Intro(props: React.HTMLAttributes<HTMLDivElement>) {
FILE: website/components/lint/join.tsx
function Join (line 4) | function Join(props: React.HTMLAttributes<HTMLDivElement>) {
FILE: website/components/million-library/block-878fb9ae.d.ts
class Block (line 28) | class Block extends AbstractBlock {
FILE: website/components/million-library/chunks/block.cjs
class AbstractBlock (line 121) | class AbstractBlock {
class ArrayBlock (line 293) | class ArrayBlock extends AbstractBlock {
method constructor (line 294) | constructor(children) {
method v (line 298) | v() {
method p (line 300) | p(fragment) {
method m (line 405) | m(parent, refNode = null) {
method x (line 415) | x() {
method u (line 426) | u() {
method s (line 429) | s() {
method t (line 432) | t() {
constant HOLE_PROXY (line 443) | const HOLE_PROXY = new Proxy(
method get (line 446) | get(_, key) {
class Block (line 487) | class Block extends AbstractBlock {
method constructor (line 488) | constructor(root, edits, props, key, shouldUpdate, getElements) {
method m (line 500) | m(parent, refNode = null) {
method p (line 567) | p(newBlock) {
method v (line 622) | v(block2 = null, refNode = null) {
method x (line 625) | x() {
method u (line 629) | u(_oldProps, _newProps) {
method s (line 632) | s() {
method t (line 635) | t() {
FILE: website/components/million-library/chunks/block.mjs
class AbstractBlock (line 119) | class AbstractBlock {
class ArrayBlock (line 291) | class ArrayBlock extends AbstractBlock {
method constructor (line 292) | constructor(children) {
method v (line 296) | v() {
method p (line 298) | p(fragment) {
method m (line 403) | m(parent, refNode = null) {
method x (line 413) | x() {
method u (line 424) | u() {
method s (line 427) | s() {
method t (line 430) | t() {
constant HOLE_PROXY (line 441) | const HOLE_PROXY = new Proxy(
method get (line 444) | get(_, key) {
class Block (line 485) | class Block extends AbstractBlock {
method constructor (line 486) | constructor(root, edits, props, key, shouldUpdate, getElements) {
method m (line 498) | m(parent, refNode = null) {
method p (line 565) | p(newBlock) {
method v (line 620) | v(block2 = null, refNode = null) {
method x (line 623) | x() {
method u (line 627) | u(_oldProps, _newProps) {
method s (line 630) | s() {
method t (line 633) | t() {
FILE: website/components/million-library/chunks/constants.cjs
constant TEXT_NODE_CACHE (line 17) | const TEXT_NODE_CACHE = "__t";
constant EVENTS_REGISTRY (line 18) | const EVENTS_REGISTRY = "__m";
constant IS_NON_DIMENSIONAL (line 19) | const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|in...
constant XLINK_NS (line 20) | const XLINK_NS = "http://www.w3.org/1999/xlink";
constant XML_NS (line 21) | const XML_NS = "http://www.w3.org/2000/xmlns/";
constant X_CHAR (line 22) | const X_CHAR = 120;
constant NON_PROPS (line 23) | const NON_PROPS = new Set$(["href", "list", "form", "tabIndex", "downloa...
constant VOID_ELEMENTS (line 24) | const VOID_ELEMENTS = new Set$(["area", "base", "basefont", "bgsound", "...
FILE: website/components/million-library/chunks/constants.mjs
constant TEXT_NODE_CACHE (line 15) | const TEXT_NODE_CACHE = "__t";
constant EVENTS_REGISTRY (line 16) | const EVENTS_REGISTRY = "__m";
constant IS_NON_DIMENSIONAL (line 17) | const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|in...
constant XLINK_NS (line 18) | const XLINK_NS = "http://www.w3.org/1999/xlink";
constant XML_NS (line 19) | const XML_NS = "http://www.w3.org/2000/xmlns/";
constant X_CHAR (line 20) | const X_CHAR = 120;
constant NON_PROPS (line 21) | const NON_PROPS = new Set$(["href", "list", "form", "tabIndex", "downloa...
constant VOID_ELEMENTS (line 22) | const VOID_ELEMENTS = new Set$(["area", "base", "basefont", "bgsound", "...
FILE: website/components/million-library/compiler.cjs
function _interopDefaultLegacy (line 11) | function _interopDefaultLegacy (e) { return e && typeof e === 'object' &...
function _interopNamespace (line 13) | function _interopNamespace(e) {
function declare (line 47) | function declare(builder) {
function copyApiObject (line 64) | function copyApiObject(api) {
function has (line 74) | function has(obj, key) {
function throwVersionError (line 77) | function throwVersionError(range, version) {
class ImportBuilder (line 137) | class ImportBuilder {
method constructor (line 138) | constructor(importedSource, scope, hub) {
method done (line 147) | done() {
method import (line 154) | import() {
method require (line 160) | require() {
method namespace (line 166) | namespace(name = "namespace") {
method default (line 180) | default(name) {
method named (line 194) | named(name, importName) {
method var (line 210) | var(name) {
method defaultInterop (line 228) | defaultInterop() {
method wildcardInterop (line 232) | wildcardInterop() {
method _interop (line 236) | _interop(callee) {
method prop (line 252) | prop(name) {
method read (line 268) | read(name) {
function isModule (line 283) | function isModule(path) {
class ImportInjector (line 313) | class ImportInjector {
method constructor (line 314) | constructor(path, importedSource, opts) {
method addDefault (line 331) | addDefault(importedSourceIn, opts) {
method addNamed (line 335) | addNamed(importName, importedSourceIn, opts) {
method addNamespace (line 341) | addNamespace(importedSourceIn, opts) {
method addSideEffect (line 345) | addSideEffect(importedSourceIn, opts) {
method _applyDefaults (line 349) | _applyDefaults(importedSource, opts, isInit = false) {
method _generateImport (line 370) | _generateImport(opts, importName) {
method _insertStatements (line 542) | _insertStatements(statements, importPosition = "before", blockHoist = ...
function addDefault (line 600) | function addDefault(path, importedSource, opts) {
function addNamed (line 604) | function addNamed(path, name, importedSource, opts) {
function addNamespace (line 608) | function addNamespace(path, importedSource, opts) {
function addSideEffect (line 612) | function addSideEffect(path, importedSource, opts) {
method CallExpression (line 1420) | CallExpression(path) {
method transformInclude (line 1431) | transformInclude(id) {
method transform (line 1434) | async transform(code, id) {
method webpack (line 1453) | webpack(config, options) {
FILE: website/components/million-library/compiler.d.ts
type UserOptions (line 20) | interface UserOptions {
FILE: website/components/million-library/compiler.mjs
function declare (line 26) | function declare(builder) {
function copyApiObject (line 43) | function copyApiObject(api) {
function has (line 53) | function has(obj, key) {
function throwVersionError (line 56) | function throwVersionError(range, version) {
class ImportBuilder (line 116) | class ImportBuilder {
method constructor (line 117) | constructor(importedSource, scope, hub) {
method done (line 126) | done() {
method import (line 133) | import() {
method require (line 139) | require() {
method namespace (line 145) | namespace(name = "namespace") {
method default (line 159) | default(name) {
method named (line 173) | named(name, importName) {
method var (line 189) | var(name) {
method defaultInterop (line 207) | defaultInterop() {
method wildcardInterop (line 211) | wildcardInterop() {
method _interop (line 215) | _interop(callee) {
method prop (line 231) | prop(name) {
method read (line 247) | read(name) {
function isModule (line 262) | function isModule(path) {
class ImportInjector (line 292) | class ImportInjector {
method constructor (line 293) | constructor(path, importedSource, opts) {
method addDefault (line 310) | addDefault(importedSourceIn, opts) {
method addNamed (line 314) | addNamed(importName, importedSourceIn, opts) {
method addNamespace (line 320) | addNamespace(importedSourceIn, opts) {
method addSideEffect (line 324) | addSideEffect(importedSourceIn, opts) {
method _applyDefaults (line 328) | _applyDefaults(importedSource, opts, isInit = false) {
method _generateImport (line 349) | _generateImport(opts, importName) {
method _insertStatements (line 521) | _insertStatements(statements, importPosition = "before", blockHoist = ...
function addDefault (line 579) | function addDefault(path, importedSource, opts) {
function addNamed (line 583) | function addNamed(path, name, importedSource, opts) {
function addNamespace (line 587) | function addNamespace(path, importedSource, opts) {
function addSideEffect (line 591) | function addSideEffect(path, importedSource, opts) {
method CallExpression (line 1399) | CallExpression(path) {
method transformInclude (line 1410) | transformInclude(id) {
method transform (line 1413) | async transform(code, id) {
method webpack (line 1432) | webpack(config, options) {
FILE: website/components/million-library/million.d.ts
class ArrayBlock (line 18) | class ArrayBlock extends AbstractBlock {
FILE: website/components/million-library/react-server.cjs
function MillionBlockLoader (line 11) | function MillionBlockLoader(props) {
function For (line 35) | function For(props) {
FILE: website/components/million-library/react-server.mjs
function MillionBlockLoader (line 7) | function MillionBlockLoader(props) {
function For (line 31) | function For(props) {
FILE: website/components/million-library/react.cjs
constant REACT_ROOT (line 9) | const REACT_ROOT = '__react_root';
constant RENDER_SCOPE (line 10) | const RENDER_SCOPE = 'million-render-scope';
constant REGISTRY (line 89) | const REGISTRY = new constants.Map$();
function MillionBlock (line 94) | function MillionBlock(props) {
FILE: website/components/million-library/react.d.ts
type Options (line 6) | interface Options {
type MillionArrayProps (line 26) | interface MillionArrayProps {
FILE: website/components/million-library/react.mjs
constant REACT_ROOT (line 28) | const REACT_ROOT = '__react_root';
constant RENDER_SCOPE (line 29) | const RENDER_SCOPE = 'million-render-scope';
constant REGISTRY (line 115) | const REGISTRY = new Map$();
function MillionBlock (line 120) | function MillionBlock(props) {
FILE: website/components/million-library/types-35702ad2.d.ts
type Flags (line 1) | const enum Flags {
type VNode (line 9) | type VNode = VElement | string | number | bigint | boolean | undefined |...
type Props (line 10) | type Props = Record<string, any>;
type VElement (line 11) | interface VElement {
type Hole (line 17) | interface Hole {
type EditAttribute (line 37) | interface EditAttribute {
type EditStyleAttribute (line 47) | interface EditStyleAttribute {
type EditSvgAttribute (line 57) | interface EditSvgAttribute {
type EditChild (line 67) | interface EditChild {
type EditEvent (line 77) | interface EditEvent {
type InitEvent (line 87) | interface InitEvent {
type InitChild (line 97) | interface InitChild {
type InitBlock (line 107) | interface InitBlock {
type Edit (line 117) | interface Edit {
FILE: website/components/retro-grid.tsx
function cn (line 4) | function cn(...inputs: ClassValue[]): string {
function RetroGrid (line 8) | function RetroGrid({ className }: { className?: string }): JSX.Element {
FILE: website/components/spotlight.tsx
function Spotlight (line 7) | function Spotlight(props: {
FILE: website/components/wrapped/index.tsx
function Wrapped (line 10) | function Wrapped(props) {
function hash (line 346) | function hash(str: string): number {
FILE: website/components/wrapped/useMockProgress.ts
type MockProgressProps (line 4) | interface MockProgressProps {
constant DEFAULT_TIME_INTERVAL (line 9) | const DEFAULT_TIME_INTERVAL = 500;
constant DEFAULT_AUTO_COMPLETE (line 10) | const DEFAULT_AUTO_COMPLETE = true;
constant PROGRESS_LOWER_LIMIT (line 11) | const PROGRESS_LOWER_LIMIT = 0;
constant PROGRESS_UPPER_LIMIT (line 12) | const PROGRESS_UPPER_LIMIT = 100;
constant MANUAL_PROGRESS_UPPER_LIMIT (line 13) | const MANUAL_PROGRESS_UPPER_LIMIT = 98;
type MockProgressReturnType (line 15) | interface MockProgressReturnType {
function useMockProgress (line 21) | function useMockProgress(props?:MockProgressProps): MockProgressReturnTy...
function useInterval (line 73) | function useInterval(callback: any, delay: number) {
FILE: website/hooks/use-translations.ts
type Translation (line 5) | type Translation = (typeof translations)['en-US'];
function useTranslations (line 12) | function useTranslations(): Translation {
FILE: website/next.config.mjs
method redirects (line 19) | async redirects() {
FILE: website/pages/api/og.tsx
function arrayBufferToBase64 (line 10) | function arrayBufferToBase64(buffer: ArrayBuffer) {
function handler (line 21) | async function handler(request: NextRequest) {
function OgImage (line 83) | function OgImage({
FILE: website/theme.config.tsx
method useNextSeoProps (line 294) | useNextSeoProps() {
function hash (line 312) | function hash(str: string): number {
Condensed preview — 399 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,903K chars).
[
{
"path": ".eslintignore",
"chars": 64,
"preview": "**/*.js\n/*.ts\ndist/\nnode_modules/\npackages/kitchen-sink\n*.old.ts"
},
{
"path": ".eslintrc.cjs",
"chars": 1354,
"preview": "const { resolve } = require('path');\n\nconst project = resolve(__dirname, 'tsconfig.json');\n\nmodule.exports = {\n root: t"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3334,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/COMMIT_CONVENTION.md",
"chars": 2082,
"preview": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-c"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 14536,
"preview": "# ✨ Contributing Guide\n\nFirst of all, thank you for taking the time to contribute! 🎉\n\nThe following is a set of guidelin"
},
{
"path": ".github/FUNDING.yml",
"chars": 20,
"preview": "github: [aidenybai]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 2352,
"preview": "name: \"\\U0001F41B Bug Report\"\ndescription: Report an issue or possible bug\nlabels: []\nassignees: []\nbody:\n - type: mark"
},
{
"path": ".github/ISSUE_TEMPLATE/documentation.yml",
"chars": 1111,
"preview": "description: Report a typo or missing area of documentation\nlabels:\n - \"area: documentation\"\nname: 📝 Documentation\ntitl"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 908,
"preview": "name: \"🚀 Feature Request\"\ndescription: Suggest a Feature or an Improvement\nlabels: [\"pending triage\"]\nbody:\n - type: ma"
},
{
"path": ".github/ISSUE_TEMPLATE/tooling.yml",
"chars": 1030,
"preview": "description: Report a bug or request an enhancement in repository tooling\nlabels:\n - 'area: tooling'\nname: 🛠 Tooling\nti"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 652,
"preview": "**Please describe the changes this PR makes and why it should be merged:**\n\n**Status**\n\n- [ ] Code changes have been tes"
},
{
"path": ".github/labeler.yml",
"chars": 327,
"preview": "name: Auto Labeling\n\non:\n issues:\n types: [opened, labeled, unlabeled]\n pull_request:\n types: [opened, labeled, "
},
{
"path": ".github/workflows/auto-response.yml",
"chars": 1141,
"preview": "name: Automated Responses\n\non:\n issues:\n types: [opened]\n pull_request_target:\n types: [opened]\n\njobs:\n respond"
},
{
"path": ".github/workflows/ci.yml",
"chars": 994,
"preview": "name: CI\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\njobs:\n build:\n runs-on: ubuntu-late"
},
{
"path": ".github/workflows/issue-needs-repro.yml",
"chars": 356,
"preview": "name: Close Issues (needs repro)\n\non:\n schedule:\n - cron: '0 0 * * *'\n\njobs:\n close-issues:\n runs-on: ubuntu-lat"
},
{
"path": ".github/workflows/publish.yml",
"chars": 728,
"preview": "name: Publish Package to npmjs\non:\n release:\n types: [published]\n workflow_dispatch:\njobs:\n build:\n runs-on: ub"
},
{
"path": ".gitignore",
"chars": 139,
"preview": "dist\nnode_modules\npackage-lock.json\nyarn-error.log\nyarn.lock\ndev\ncoverage\n.DS_Store\n.pnpm-debug.log\n*.tgz\n.next\n.turbo\n."
},
{
"path": ".prettierignore",
"chars": 201,
"preview": "node_modules\ndist\nwebsite/pages/docs/compiler.mdx\nwebsite/pages/docs/install.mdx\nwebsite/pages/docs/quickstart.mdx\nwebsi"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) 2021-present Aiden Bai\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 10715,
"preview": "> I'm working on something new (still free + open source!)\n> \n> React Grab allows you to select an element and copy its "
},
{
"path": "build.config.ts",
"chars": 1136,
"preview": "import { defineBuildConfig } from 'unbuild';\nimport banner from 'rollup-plugin-banner2';\nimport { readFileSync } from 'n"
},
{
"path": "cli.js",
"chars": 201,
"preview": "#!/usr/bin/env node\n\nconst { spawn } = require('child_process');\n\ntry {\n spawn('npx', ['@million/install@latest'], {\n "
},
{
"path": "compiler.d.ts",
"chars": 116,
"preview": "import unplugin from './dist/packages/compiler';\nexport * from './dist/packages/compiler';\nexport default unplugin;\n"
},
{
"path": "jsx-runtime.d.ts",
"chars": 45,
"preview": "export * from './dist/packages/jsx-runtime';\n"
},
{
"path": "package.json",
"chars": 4478,
"preview": "{\n \"name\": \"million\",\n \"version\": \"3.1.10\",\n \"description\": \"Make React Faster. Automatically.\",\n \"keywords\": [\n "
},
{
"path": "packages/cli/build.mjs",
"chars": 591,
"preview": "import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimpo"
},
{
"path": "packages/cli/package.json",
"chars": 500,
"preview": "{\n \"main\": \"dist/index.js\",\n \"bin\": {\n \"million\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"prepare\": \"node build."
},
{
"path": "packages/cli/src/index.ts",
"chars": 113,
"preview": "#! /usr/bin/env node\n\nimport { install } from '@million/install';\n\nvoid install('Million', process.env.VERSION);\n"
},
{
"path": "packages/cli/tsconfig.json",
"chars": 334,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es2016\",\n \"lib\": [],\n \"module\": \"commonjs\",\n \"esModuleInterop\": true,\n "
},
{
"path": "packages/compiler/README.md",
"chars": 182,
"preview": "# `million/compiler`\n\n> Note: this page is a stub\n\nWelcome to the Million.js compiler! Here is a [great primer](https://"
},
{
"path": "packages/compiler/auto.ts",
"chars": 12090,
"preview": "import type * as babel from '@babel/core';\nimport * as t from '@babel/types';\nimport { REACT_IMPORTS, TRACKED_IMPORTS } "
},
{
"path": "packages/compiler/babel.ts",
"chars": 2054,
"preview": "import type { PluginObj, PluginPass } from '@babel/core';\nimport { transformAuto } from './auto';\nimport { transformBloc"
},
{
"path": "packages/compiler/block.ts",
"chars": 13070,
"preview": "import * as t from '@babel/types';\nimport {\n HIDDEN_IMPORTS,\n JSX_SKIP_ANNOTATION,\n SKIP_ANNOTATION,\n SVG_ELEMENTS,\n"
},
{
"path": "packages/compiler/constants.ts",
"chars": 2667,
"preview": "import type { ImportDefinition } from './types';\n\nexport const RENDER_SCOPE = 'slot';\nexport const SKIP_ANNOTATION = '@m"
},
{
"path": "packages/compiler/experimental/optimize.ts",
"chars": 4967,
"preview": "import * as t from '@babel/types';\n// import { addNamed } from '@babel/helper-module-imports';\nimport type { NodePath } "
},
{
"path": "packages/compiler/experimental/render.ts",
"chars": 10872,
"preview": "import * as t from '@babel/types';\n// import { addNamed } from '@babel/helper-module-imports';\nimport type { NodePath } "
},
{
"path": "packages/compiler/experimental/types.ts",
"chars": 1812,
"preview": "import type {\n ArrowFunctionExpression,\n StringLiteral,\n NumericLiteral,\n Identifier,\n} from '@babel/types';\n\nexport"
},
{
"path": "packages/compiler/experimental/utils.ts",
"chars": 1848,
"preview": "import * as t from '@babel/types';\n\nexport const createEdit = ({\n type,\n name,\n value,\n hole,\n index,\n listener,\n "
},
{
"path": "packages/compiler/for.old.ts",
"chars": 7763,
"preview": "/**\n * THIS FILE IS DEPRECATED\n *\n * The file is no longer being used due to a broader\n * compilation process that cover"
},
{
"path": "packages/compiler/index.ts",
"chars": 1447,
"preview": "import { existsSync } from 'node:fs';\nimport { babel } from './babel';\nimport type { Options } from './plugin';\nimport {"
},
{
"path": "packages/compiler/package.json",
"chars": 449,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\",\n \"devDependencies\": {\n \"@rollup/pluginutils\":"
},
{
"path": "packages/compiler/plugin.ts",
"chars": 6373,
"preview": "import type { BabelFileResult, ParserOptions } from '@babel/core';\nimport { transformAsync } from '@babel/core';\nimport "
},
{
"path": "packages/compiler/types.ts",
"chars": 1013,
"preview": "import type * as t from '@babel/types';\nimport type { MillionTelemetry } from 'packages/telemetry';\n\nexport interface Na"
},
{
"path": "packages/compiler/utils/ast.ts",
"chars": 700,
"preview": "import type { NodePath } from '@babel/core';\nimport type * as t from '@babel/types';\nimport { IGNORE_ANNOTATION } from '"
},
{
"path": "packages/compiler/utils/checks.ts",
"chars": 1722,
"preview": "import type * as t from '@babel/types';\n\ntype TypeFilter<V extends t.Node> = (node: t.Node) => node is V;\n\nexport functi"
},
{
"path": "packages/compiler/utils/generate-unique-name.ts",
"chars": 531,
"preview": "import type * as babel from '@babel/core';\nimport * as t from '@babel/types';\n\nexport function generateUniqueName(\n pat"
},
{
"path": "packages/compiler/utils/get-descriptive-name.ts",
"chars": 1009,
"preview": "import type { NodePath } from '@babel/core';\n\nexport function getDescriptiveName(\n path: NodePath,\n defaultName: strin"
},
{
"path": "packages/compiler/utils/get-import-specifier.ts",
"chars": 1125,
"preview": "import type * as babel from '@babel/core';\nimport * as t from '@babel/types';\nimport type { ImportDefinition, StateConte"
},
{
"path": "packages/compiler/utils/get-root-statement-path.ts",
"chars": 357,
"preview": "import type * as babel from '@babel/core';\nimport * as t from '@babel/types';\n\nexport function getRootStatementPath(path"
},
{
"path": "packages/compiler/utils/get-valid-import-definition.ts",
"chars": 2894,
"preview": "import * as t from '@babel/types';\nimport type { ImportDefinition, StateContext } from '../types';\nimport { unwrapNode }"
},
{
"path": "packages/compiler/utils/is-guaranteed-literal.ts",
"chars": 1739,
"preview": "import * as t from '@babel/types';\n\nexport function isGuaranteedLiteral(node: t.Expression): node is t.Literal {\n switc"
},
{
"path": "packages/compiler/utils/is-jsx-component-element.ts",
"chars": 997,
"preview": "import type * as babel from '@babel/core';\nimport * as t from '@babel/types';\nimport { isPathValid } from './checks';\nim"
},
{
"path": "packages/compiler/utils/is-use-client.ts",
"chars": 404,
"preview": "import type * as t from '@babel/types';\n\nexport const isUseClient = (directives: t.Directive[]): boolean => {\n const di"
},
{
"path": "packages/compiler/utils/jsx.ts",
"chars": 3847,
"preview": "import * as t from '@babel/types';\nimport { RENDER_SCOPE } from '../constants';\n\nexport const trimJSXChildren = (jsx: t."
},
{
"path": "packages/compiler/utils/log.ts",
"chars": 3415,
"preview": "import * as t from '@babel/types';\nimport {\n bold,\n cyan,\n dim,\n green,\n magenta,\n underline,\n yellow,\n} from 'kl"
},
{
"path": "packages/compiler/utils/object.ts",
"chars": 592,
"preview": "import * as t from '@babel/types';\n\nexport const dedupeObjectProperties = (properties: t.ObjectProperty[]) => {\n const "
},
{
"path": "packages/compiler/utils/register-import-definition.ts",
"chars": 1495,
"preview": "import * as t from '@babel/types';\nimport type { ImportDefinition, StateContext } from '../types';\n\nfunction getImportSp"
},
{
"path": "packages/compiler/utils/unwrap-node.ts",
"chars": 500,
"preview": "import type * as t from '@babel/types';\nimport { isNestedExpression } from './checks';\n\ntype TypeCheck<K> = K extends (("
},
{
"path": "packages/compiler/utils/unwrap-path.ts",
"chars": 465,
"preview": "import type * as t from '@babel/types';\nimport { isNestedExpression, isPathValid } from './checks';\n\ntype TypeFilter<V e"
},
{
"path": "packages/compiler/vdom/index.ts",
"chars": 37,
"preview": "export { visitor } from './visitor';\n"
},
{
"path": "packages/compiler/vdom/visitor.ts",
"chars": 9060,
"preview": "/**\n * PLEASE DO NOT USE THIS IN PRODUCTION. This is technically a proof-of-concept\n * and is _highly_ experimental. Cur"
},
{
"path": "packages/experimental/README.md",
"chars": 25,
"preview": "# `million/experimental`\n"
},
{
"path": "packages/experimental/index.ts",
"chars": 96,
"preview": "// eslint-disable-next-line camelcase\nexport const experimental_options = {\n noSlot: false,\n};\n"
},
{
"path": "packages/experimental/package.json",
"chars": 242,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\",\n \"dependencies\": {\n \"react\": \"^18.2.0\",\n \""
},
{
"path": "packages/jsx-runtime/README.md",
"chars": 24,
"preview": "# `million/jsx-runtime`\n"
},
{
"path": "packages/jsx-runtime/index.ts",
"chars": 350,
"preview": "import type { MillionProps } from '../types';\nimport type { VNode } from '../million';\n\nexport const h = (\n type: strin"
},
{
"path": "packages/jsx-runtime/package.json",
"chars": 72,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\"\n}\n"
},
{
"path": "packages/kitchen-sink/README.md",
"chars": 2622,
"preview": "# Million.js Kitchen Sink 🧑🍳\n\nHey! We're actively recruiting cooks 🧑🍳 to help assemble a list of examples of Million +"
},
{
"path": "packages/kitchen-sink/index.html",
"chars": 541,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "packages/kitchen-sink/package.json",
"chars": 1308,
"preview": "{\n \"type\": \"module\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"serve\": \"vite"
},
{
"path": "packages/kitchen-sink/src/css/examples/age-calculator.css",
"chars": 339,
"preview": ".age-calculator h2 {\n font-size: 1.2rem;\n}\n.age-calculator .input-wrapper {\n display: grid;\n grid-template-columns: r"
},
{
"path": "packages/kitchen-sink/src/css/examples/bmi-calculator.css",
"chars": 807,
"preview": ".bmi-calculator {\n max-width: 900px;\n}\n.bmi-calculator h2 {\n text-align: center;\n}\n.bmi-calculator .bmi-calculator_wra"
},
{
"path": "packages/kitchen-sink/src/css/examples/carousel.css",
"chars": 312,
"preview": "/* Required styling for carousel */\n.carousel {\n display: flex;\n overflow: hidden;\n gap: 10px;\n}\n\n.carousel-inner {\n "
},
{
"path": "packages/kitchen-sink/src/css/examples/create-event-calender.css",
"chars": 6814,
"preview": "/* calendar styles */\n.create-event-calender {\n /* text-align: center; */\n}\n\n.create-event-calender .create-event-calen"
},
{
"path": "packages/kitchen-sink/src/css/examples/dice-roller.css",
"chars": 619,
"preview": "/* Dice roller styles */\n@keyframes shakeDice {\n 0% {\n transform: translateX(0);\n }\n\n 10% {\n transform: transla"
},
{
"path": "packages/kitchen-sink/src/css/examples/style.css",
"chars": 9346,
"preview": "/* Music player styles */\n.body {\n box-sizing: border-box;\n background: linear-gradient(\n 90deg,\n rgba(2, 0, 36,"
},
{
"path": "packages/kitchen-sink/src/css/style.css",
"chars": 2276,
"preview": "* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\n:root {\n font-family: 'Inter', sans-serif;\n}\n\n@supports (fo"
},
{
"path": "packages/kitchen-sink/src/examples/age-calculator.tsx",
"chars": 3318,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\nimport '../css/examples/age-calculator.css';\n\nc"
},
{
"path": "packages/kitchen-sink/src/examples/bmi-calculator.jsx",
"chars": 3803,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\nimport '../css/examples/bmi-calculator.c"
},
{
"path": "packages/kitchen-sink/src/examples/book-recommendation.jsx",
"chars": 6540,
"preview": "import { block, For } from 'million/react';\nimport { useEffect } from 'react';\nimport { useState } from 'react';\nimport "
},
{
"path": "packages/kitchen-sink/src/examples/calculator.tsx",
"chars": 6134,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\n\ntype ICalculator = {\n value: number;\n newVal"
},
{
"path": "packages/kitchen-sink/src/examples/carousel.tsx",
"chars": 1470,
"preview": "import { useState, useEffect } from 'react';\nimport { block } from 'million/react';\nimport '../css/examples/carousel.css"
},
{
"path": "packages/kitchen-sink/src/examples/context.tsx",
"chars": 877,
"preview": "import { useState, createContext, useContext } from 'react';\nimport { block } from 'million/react';\n// import { experime"
},
{
"path": "packages/kitchen-sink/src/examples/countdown-timer.jsx",
"chars": 5942,
"preview": "import React, { useState, useEffect } from 'react';\nimport { block } from 'million/react';\nimport StartSVG from '../../a"
},
{
"path": "packages/kitchen-sink/src/examples/counter.tsx",
"chars": 262,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\n\nconst Counter = /* @optimize */ block(() => {\n"
},
{
"path": "packages/kitchen-sink/src/examples/create-event-calender.jsx",
"chars": 4349,
"preview": "import { useState, useRef } from 'react';\nimport Calendar from 'react-calendar';\nimport 'react-calendar/dist/Calendar.cs"
},
{
"path": "packages/kitchen-sink/src/examples/crypto-tracker.jsx",
"chars": 4296,
"preview": "import { useEffect, useState } from 'react';\nimport { block } from 'million/react';\n\nconst Coin = block(\n ({ image, nam"
},
{
"path": "packages/kitchen-sink/src/examples/currency-convertor.tsx",
"chars": 3912,
"preview": "import { block } from 'million/react';\nimport { useState, useEffect } from 'react';\nimport axios from 'axios';\ninterface"
},
{
"path": "packages/kitchen-sink/src/examples/data-converter.tsx",
"chars": 4649,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\n\ninterface DataConverterState {\n inputU"
},
{
"path": "packages/kitchen-sink/src/examples/dice-roller.tsx",
"chars": 901,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\nimport '../css/examples/dice-roller.css'"
},
{
"path": "packages/kitchen-sink/src/examples/digital-personal-journal.jsx",
"chars": 4709,
"preview": "import React, { useState, useRef } from 'react';\nimport { block } from 'million/react';\n\nconst JournalComponent = ({ ent"
},
{
"path": "packages/kitchen-sink/src/examples/discount-calculator.tsx",
"chars": 1883,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\n\ninterface Results {\n discountPrice: number;\n "
},
{
"path": "packages/kitchen-sink/src/examples/emoji-picker.tsx",
"chars": 1358,
"preview": "import { block } from 'million/react';\nimport { useState } from 'react';\nimport EmojiPicker from 'emoji-picker-react';\ni"
},
{
"path": "packages/kitchen-sink/src/examples/etch-a-sketch.tsx",
"chars": 3041,
"preview": "import React, { useState, useEffect, useRef, ChangeEvent } from 'react';\nimport { block } from 'million/react';\n\ninterfa"
},
{
"path": "packages/kitchen-sink/src/examples/expense-tracker.tsx",
"chars": 5842,
"preview": "import { useState, useRef, useEffect } from 'react';\nimport { block, For } from 'million/react';\n\ntype Transaction = {\n "
},
{
"path": "packages/kitchen-sink/src/examples/file-upload-preview.jsx",
"chars": 664,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\n\nconst FileUpload = block(({ file }) => "
},
{
"path": "packages/kitchen-sink/src/examples/form.tsx",
"chars": 318,
"preview": "import { block } from 'million/react';\n\nconst Form = block(() => {\n const onSubmit = (event: any) => {\n event.preven"
},
{
"path": "packages/kitchen-sink/src/examples/github-user-search.jsx",
"chars": 1875,
"preview": "import React, { useState } from 'react';\nimport { useEffect } from 'react';\nimport axios from 'axios';\nimport { block } "
},
{
"path": "packages/kitchen-sink/src/examples/guestbook.tsx",
"chars": 1558,
"preview": "import React, { useState } from 'react';\nimport { For } from 'million/react';\nimport { block } from 'million/react';\n\nin"
},
{
"path": "packages/kitchen-sink/src/examples/hangman-game.tsx",
"chars": 15967,
"preview": "import { useEffect, useState } from 'react';\nimport { block } from 'million/react';\nimport styled from 'styled-component"
},
{
"path": "packages/kitchen-sink/src/examples/interactive-card-game.tsx",
"chars": 4664,
"preview": "import React, { useState, useEffect } from 'react';\nimport { block } from 'million/react';\ninterface Card {\n id: number"
},
{
"path": "packages/kitchen-sink/src/examples/investment-calculator.tsx",
"chars": 2425,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\n\ninterface Results {\n totalInvestment: number;"
},
{
"path": "packages/kitchen-sink/src/examples/jotai-counter.tsx",
"chars": 317,
"preview": "import { atom, useAtom } from 'jotai';\nimport { block } from 'million/react';\n\nconst countAtom = atom<number>(0);\n\nconst"
},
{
"path": "packages/kitchen-sink/src/examples/list.tsx",
"chars": 322,
"preview": "import { block, For } from 'million/react';\n\nconst List = block(() => {\n const items = { id: 'test' };\n return (\n <"
},
{
"path": "packages/kitchen-sink/src/examples/location-app.tsx",
"chars": 1090,
"preview": "import { block } from 'million/react';\nimport { useState, useEffect } from 'react';\n\nconst LocationComponent = block(() "
},
{
"path": "packages/kitchen-sink/src/examples/markdown-editor.tsx",
"chars": 1049,
"preview": "import { useRef, useEffect } from 'react';\nimport { marked } from 'marked';\nimport { block } from 'million/react';\n\ncons"
},
{
"path": "packages/kitchen-sink/src/examples/million-quiz.jsx",
"chars": 11108,
"preview": "import React, { useEffect, useReducer } from 'react';\nimport { block } from 'million/react';\nimport styled from 'styled-"
},
{
"path": "packages/kitchen-sink/src/examples/morse-code-translator.jsx",
"chars": 5245,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\n\nconst morseCode = {\n A: '.-',\n B: '-."
},
{
"path": "packages/kitchen-sink/src/examples/mortgage-calculator.tsx",
"chars": 2363,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\n\ninterface Results {\n monthlyPayment: number;\n"
},
{
"path": "packages/kitchen-sink/src/examples/movie-finder.tsx",
"chars": 1508,
"preview": "import React from 'react';\nimport { block } from 'million/react';\n\ninterface MovieProps {\n title: string;\n release_dat"
},
{
"path": "packages/kitchen-sink/src/examples/multi-children.jsx",
"chars": 350,
"preview": "import { block } from 'million/react';\n\nconst Wrapper = block(({ children }) => {\n return <div className=\"wrapper\">{chi"
},
{
"path": "packages/kitchen-sink/src/examples/music-player.jsx",
"chars": 3664,
"preview": "import React, { useEffect, useState } from 'react';\nimport useSound from 'use-sound';\nimport Seyi from '../../assets/sey"
},
{
"path": "packages/kitchen-sink/src/examples/news-aggregator.tsx",
"chars": 2665,
"preview": "import React, { useEffect, useState } from 'react';\nimport { block } from 'million/react';\n\ninterface Source {\n name: s"
},
{
"path": "packages/kitchen-sink/src/examples/number-guessing.tsx",
"chars": 1391,
"preview": "import React, { useState, FormEvent } from 'react';\nimport { block } from 'million/react';\n\ninterface NumberGuessingGame"
},
{
"path": "packages/kitchen-sink/src/examples/password-generator.tsx",
"chars": 6187,
"preview": "import { useState, useEffect, useRef } from 'react';\nimport { block } from 'million/react';\nimport { BsClipboardCheck } "
},
{
"path": "packages/kitchen-sink/src/examples/pomodoro-timer.jsx",
"chars": 1349,
"preview": "import React from 'react';\nimport { block } from 'million/react';\n\nconst PomodoroTimer = block(() => {\n const [minutes,"
},
{
"path": "packages/kitchen-sink/src/examples/qr-code-generator.tsx",
"chars": 900,
"preview": "import { block } from 'million/react';\nimport { useState, useRef } from 'react';\nimport QRCode from 'react-qr-code';\n\nco"
},
{
"path": "packages/kitchen-sink/src/examples/quote-generator.tsx",
"chars": 682,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\n\nconst QuoteGenerator: React.FC = block("
},
{
"path": "packages/kitchen-sink/src/examples/react-router-dom.tsx",
"chars": 2661,
"preview": "import { Routes, Route, Outlet, Link } from 'react-router-dom';\nimport { BrowserRouter } from 'react-router-dom';\nimport"
},
{
"path": "packages/kitchen-sink/src/examples/recipe-finder.jsx",
"chars": 13070,
"preview": "import React, { useState } from 'react';\nimport axios from 'axios';\nimport { block } from 'million/react';\n\nconst Recipe"
},
{
"path": "packages/kitchen-sink/src/examples/redux-todo.tsx",
"chars": 2596,
"preview": "import React, { useState } from 'react';\nimport { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';\n"
},
{
"path": "packages/kitchen-sink/src/examples/repro.tsx",
"chars": 1049,
"preview": "import { block } from 'million/react';\n\nconst AUTO_ARCHIVE_OPTIONS = [\n 3 * 86400, // 3 days\n 7 * 86400, // 7 days\n 1"
},
{
"path": "packages/kitchen-sink/src/examples/rock-paper-scissors.tsx",
"chars": 2710,
"preview": "import React, { useState } from 'react';\nimport { block } from 'million/react';\n\nconst Game: React.FC = block(() => {\n "
},
{
"path": "packages/kitchen-sink/src/examples/snake-game.jsx",
"chars": 5428,
"preview": "import { block } from 'million/react';\nimport { useEffect, useRef, useState } from 'react';\n\nconst Snake = block(({ snak"
},
{
"path": "packages/kitchen-sink/src/examples/style.tsx",
"chars": 1247,
"preview": "import { block } from 'million/react';\nimport { useState } from 'react';\n\nconst getRandomColor = () => {\n return `#${Ma"
},
{
"path": "packages/kitchen-sink/src/examples/styled-counter.tsx",
"chars": 488,
"preview": "import { useState } from 'react';\nimport { block } from 'million/react';\nimport styled from 'styled-components';\n\nconst "
},
{
"path": "packages/kitchen-sink/src/examples/svg.tsx",
"chars": 461,
"preview": "import { block } from 'million/react';\n\nconst Svg = block(() => {\n return (\n <div>\n <svg\n xmlns=\"http://"
},
{
"path": "packages/kitchen-sink/src/examples/swr.tsx",
"chars": 633,
"preview": "import useSWR from 'swr';\nimport { block } from 'million/react';\n\nconst fetcher = (url: string) => fetch(url).then((res)"
},
{
"path": "packages/kitchen-sink/src/examples/tanstack-query.tsx",
"chars": 1046,
"preview": "import {\n QueryClient,\n QueryClientProvider,\n useQuery,\n} from '@tanstack/react-query';\nimport axios from 'axios';\nim"
},
{
"path": "packages/kitchen-sink/src/examples/tanstack-virtual.tsx",
"chars": 1327,
"preview": "import { useRef } from 'react';\nimport { For } from 'million/react';\nimport { useVirtualizer } from '@tanstack/react-vir"
},
{
"path": "packages/kitchen-sink/src/examples/task-tracker.tsx",
"chars": 12647,
"preview": "import { useState, useEffect } from 'react';\nimport { v4 as uuid4 } from 'uuid';\nimport {\n DragDropContext,\n Droppable"
},
{
"path": "packages/kitchen-sink/src/examples/tic-tac-toe.jsx",
"chars": 3254,
"preview": "import React from 'react';\nimport { block } from 'million/react';\n\nfunction Square({ onClick, value }) {\n return (\n "
},
{
"path": "packages/kitchen-sink/src/examples/todolist.tsx",
"chars": 2522,
"preview": "import { useState } from 'react';\nimport { block, For } from 'million/react';\n\nconst TodoList = () => {\n const [tasks, "
},
{
"path": "packages/kitchen-sink/src/examples/type-race.jsx",
"chars": 6324,
"preview": "import { useState, useEffect } from 'react';\nimport { block, For } from 'million/react';\n\nconst getWPM = (charCount, tim"
},
{
"path": "packages/kitchen-sink/src/examples/valtio-counter.tsx",
"chars": 285,
"preview": "import { proxy, useSnapshot } from 'valtio';\nimport { block } from 'million/react';\n\nconst state = proxy({ count: 0 });\n"
},
{
"path": "packages/kitchen-sink/src/examples/virtual-whiteboard.tsx",
"chars": 12534,
"preview": "import React, { useRef, useEffect, useState } from 'react';\n\nconst VirtualBoard: React.FC = () => {\n // Enum responsibl"
},
{
"path": "packages/kitchen-sink/src/examples/weather-app.tsx",
"chars": 3624,
"preview": "import styled from 'styled-components';\nimport { useState } from 'react';\nimport React from 'react';\nimport { block } fr"
},
{
"path": "packages/kitchen-sink/src/examples/wouter.tsx",
"chars": 579,
"preview": "import { Link, Route } from 'wouter';\nimport { block } from 'million/react';\n\nconst Wouter = block(() => (\n <div>\n <"
},
{
"path": "packages/kitchen-sink/src/examples/zustand-counter.tsx",
"chars": 373,
"preview": "import { create } from 'zustand';\nimport { block } from 'million/react';\n\nconst useStore = create<{\n count: number;\n i"
},
{
"path": "packages/kitchen-sink/src/main.tsx",
"chars": 5704,
"preview": "import {\n useState,\n type ComponentType,\n lazy,\n Suspense,\n LazyExoticComponent,\n useEffect,\n} from 'react';\nimpor"
},
{
"path": "packages/kitchen-sink/src/vite-env.d.ts",
"chars": 38,
"preview": "/// <reference types=\"vite/client\" />\n"
},
{
"path": "packages/kitchen-sink/tsconfig.json",
"chars": 789,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"useDefineForClassFields\": true,\n \"lib\": [\"ES2020\", \"DOM\", \"DOM."
},
{
"path": "packages/kitchen-sink/tsconfig.node.json",
"chars": 213,
"preview": "{\n \"compilerOptions\": {\n \"composite\": true,\n \"skipLibCheck\": true,\n \"module\": \"ESNext\",\n \"moduleResolution\""
},
{
"path": "packages/kitchen-sink/vite.config.ts",
"chars": 714,
"preview": "import react from '@vitejs/plugin-react';\nimport path from 'path';\nimport { defineConfig } from 'vite';\n// @ts-ignore\nim"
},
{
"path": "packages/million/README.md",
"chars": 12,
"preview": "# `million`\n"
},
{
"path": "packages/million/alias.ts",
"chars": 3699,
"preview": "/**\n * From `getAttributeAlias.js` in React-DOM\n * https://github.com/facebook/react/blob/b09e102ff1e2aaaf5eb6585b04609a"
},
{
"path": "packages/million/array.ts",
"chars": 5090,
"preview": "/* eslint-disable @typescript-eslint/unbound-method */\nimport { setTextContent$ } from './dom';\nimport { AbstractBlock }"
},
{
"path": "packages/million/block.ts",
"chars": 11112,
"preview": "/* eslint-disable no-bitwise */\n/* eslint-disable @typescript-eslint/unbound-method */\nimport type { MillionProps } from"
},
{
"path": "packages/million/constants.ts",
"chars": 1268,
"preview": "/* eslint-disable @typescript-eslint/unbound-method */\nexport const Object$ = Object;\nexport const Map$ = Map;\nexport co"
},
{
"path": "packages/million/dom.ts",
"chars": 8114,
"preview": "/* eslint-disable @typescript-eslint/unbound-method */\n\nimport {\n EVENTS_REGISTRY,\n IS_NON_DIMENSIONAL,\n NON_PROPS,\n "
},
{
"path": "packages/million/index.ts",
"chars": 311,
"preview": "export { mount, patch, block, Block, withKey } from './block';\nexport { mapArray, ArrayBlock } from './array';\nexport { "
},
{
"path": "packages/million/package.json",
"chars": 92,
"preview": "{\n \"type\": \"module\",\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\"\n}\n"
},
{
"path": "packages/million/template.ts",
"chars": 6827,
"preview": "import getAttributeAlias from './alias';\nimport {\n X_CHAR,\n VOID_ELEMENTS,\n EventFlag,\n StyleAttributeFlag,\n SvgAtt"
},
{
"path": "packages/million/types.ts",
"chars": 3791,
"preview": "import type { MillionProps } from '../types';\n\ndeclare const enum Flags {\n Child = 1,\n Attribute = 2,\n Event = 4,\n S"
},
{
"path": "packages/react/README.md",
"chars": 18,
"preview": "# `million/react`\n"
},
{
"path": "packages/react/block.ts",
"chars": 4498,
"preview": "import { createElement, Fragment, useCallback, useMemo, useRef } from 'react';\nimport type { ComponentType, Ref } from '"
},
{
"path": "packages/react/compiled-block.ts",
"chars": 2433,
"preview": "// The following code are for the compield `block` components\nimport type { ReactPortal, ComponentType, JSX } from 'reac"
},
{
"path": "packages/react/constants.ts",
"chars": 428,
"preview": "import { useEffect } from 'react';\nimport type { ComponentType, DependencyList } from 'react';\n\nexport const RENDER_SCOP"
},
{
"path": "packages/react/for.ts",
"chars": 4454,
"preview": "import {\n Fragment,\n createElement,\n memo,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport type { MutableRe"
},
{
"path": "packages/react/index.ts",
"chars": 513,
"preview": "import { block as internalBlock, patch, mount } from '../million';\n\nexport { block } from './block';\nexport { For } from"
},
{
"path": "packages/react/its-fine.tsx",
"chars": 5038,
"preview": "// MIT License\n\n// Copyright (c) 2022 Poimandres\n\n// Permission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "packages/react/package.json",
"chars": 243,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\",\n \"dependencies\": {\n \"react\": \"^18.2.0\",\n \""
},
{
"path": "packages/react/utils.ts",
"chars": 4284,
"preview": "import { Fragment, createContext, createElement, isValidElement } from 'react';\nimport { createPortal } from 'react-dom'"
},
{
"path": "packages/react-server/README.md",
"chars": 25,
"preview": "# `million/react-server`\n"
},
{
"path": "packages/react-server/index.ts",
"chars": 10276,
"preview": "import type {\n ComponentType,\n ForwardedRef,\n JSX,\n PropsWithChildren,\n ReactElement,\n ReactNode,\n ReactPortal,\n}"
},
{
"path": "packages/react-server/package.json",
"chars": 277,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\",\n \"dependencies\": {\n \"react\": \"^18.2.0\",\n \""
},
{
"path": "packages/react-server/utils.ts",
"chars": 8572,
"preview": "/* eslint-disable */\n/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache L"
},
{
"path": "packages/telemetry/LICENSE",
"chars": 3629,
"preview": "MIT License\n\nCopyright (c) 2021 Fred K. Schott\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "packages/telemetry/README.md",
"chars": 332,
"preview": "# Million Telemetry\n\nThis package contains the telemetry service for the compiler. It is responsible for collecting and "
},
{
"path": "packages/telemetry/config.ts",
"chars": 2175,
"preview": "import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport process from 'node:process';\n\ne"
},
{
"path": "packages/telemetry/index.ts",
"chars": 2909,
"preview": "import { randomBytes } from 'node:crypto';\nimport { cyan } from 'kleur/colors';\nimport { isCI } from './utils/is-ci';\nim"
},
{
"path": "packages/telemetry/package.json",
"chars": 72,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\"\n}\n"
},
{
"path": "packages/telemetry/post.ts",
"chars": 623,
"preview": "/* eslint-disable */\nimport { fetch } from 'undici';\n\nif (\n !('markResourceTiming' in performance) ||\n typeof performa"
},
{
"path": "packages/telemetry/project-info.ts",
"chars": 1890,
"preview": "import { execSync } from 'node:child_process';\nimport type { BinaryLike } from 'node:crypto';\nimport { createHash } from"
},
{
"path": "packages/telemetry/system-info.ts",
"chars": 1201,
"preview": "import os from 'node:os';\nimport { getCI, isCI } from './utils/is-ci';\nimport { isWSL } from './utils/is-wsl';\nimport { "
},
{
"path": "packages/telemetry/utils/detect-package-manager.ts",
"chars": 639,
"preview": "interface PackageManager {\n name: string;\n version: string;\n}\n\nconst pmFromUserAgent = (userAgent: string): PackageMan"
},
{
"path": "packages/telemetry/utils/is-ci.ts",
"chars": 7523,
"preview": "const env = process.env;\n\ninterface Info {\n name: string | null;\n isPR?: boolean | null;\n}\n\nconst info: Info = {\n nam"
},
{
"path": "packages/telemetry/utils/is-docker.ts",
"chars": 535,
"preview": "import fs from 'node:fs';\n\nlet isDockerCached: boolean | undefined;\n\nconst hasDockerEnv = (): boolean => {\n try {\n f"
},
{
"path": "packages/telemetry/utils/is-inside-container.ts",
"chars": 419,
"preview": "import fs from 'node:fs';\nimport { isDocker } from './is-docker';\n\nlet cachedResult: boolean | undefined;\n\nconst hasCont"
},
{
"path": "packages/telemetry/utils/is-wsl.ts",
"chars": 583,
"preview": "import process from 'node:process';\nimport os from 'node:os';\nimport fs from 'node:fs';\nimport { isInsideContainer } fro"
},
{
"path": "packages/types/README.md",
"chars": 18,
"preview": "# `million/types`\n"
},
{
"path": "packages/types/index.ts",
"chars": 917,
"preview": "import type { ReactPortal } from 'react';\nimport type { block as createBlock } from '../million';\n\nexport type MillionPr"
},
{
"path": "packages/types/package.json",
"chars": 179,
"preview": "{\n \"private\": true,\n \"main\": \"./index.ts\",\n \"module\": \"./index.ts\",\n \"dependencies\": {\n \"react\": \"^18.2.0\"\n },\n "
},
{
"path": "pnpm-workspace.yaml",
"chars": 58,
"preview": "packages:\n - 'packages/*'\n - 'website'\n - 'playground'\n"
},
{
"path": "react-server.d.ts",
"chars": 46,
"preview": "export * from './dist/packages/react-server';\n"
},
{
"path": "react.d.ts",
"chars": 39,
"preview": "export * from './dist/packages/react';\n"
},
{
"path": "test/alias.test.ts",
"chars": 1086,
"preview": "import { describe, it } from 'vitest';\nimport type { VElement } from '../packages/million';\nimport { block as createBloc"
},
{
"path": "test/block.test.ts",
"chars": 3966,
"preview": "import { describe, it } from 'vitest';\nimport { block as createBlock } from '../packages/million';\nimport type { Million"
},
{
"path": "test/map-array.test.ts",
"chars": 2028,
"preview": "import { describe, it } from 'vitest';\nimport { block as createBlock, mapArray } from '../packages/million';\nimport type"
},
{
"path": "test/prettier.js",
"chars": 262,
"preview": "let Block__callback$ = /*@__SKIP__*/ block(\n ({ i }) => {\n return <div>{i}</div>;\n },\n {\n shouldUpdate: (a, b) "
},
{
"path": "tsconfig.json",
"chars": 539,
"preview": "{\n \"extends\": \"@vercel/style-guide/typescript\",\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"jsx\": \"react-jsx\",\n "
},
{
"path": "types.d.ts",
"chars": 39,
"preview": "export * from './dist/packages/types';\n"
},
{
"path": "vitest.config.ts",
"chars": 171,
"preview": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n environment: 'jsdom',\n cov"
},
{
"path": "website/.eslintignore",
"chars": 42,
"preview": "components\npages/api\npackages/kitchen-sink"
},
{
"path": "website/.eslintrc.js",
"chars": 197,
"preview": "const { resolve } = require('path');\n\nconst project = resolve(__dirname, 'tsconfig.json');\n\nmodule.exports = {\n root: t"
},
{
"path": "website/.gitignore",
"chars": 120,
"preview": ".next\nnode_modules\n.env\n.env.example\n\n# next-video\nvideos/*\n!videos/*.json\n!videos/*.js\n!videos/*.ts\npublic/_next-video\n"
},
{
"path": "website/components/ad.tsx",
"chars": 860,
"preview": "import React, { useEffect } from 'react';\nimport { useRouter } from 'next/router';\n\nexport const CarbonAds = () => {\n c"
},
{
"path": "website/components/automatic-mode-warning.tsx",
"chars": 625,
"preview": "import { Callout } from 'nextra-theme-docs';\n\nexport function AutomaticModeWarning() {\n return (\n <div suppressHydra"
},
{
"path": "website/components/back-in-block/block-vdom.tsx",
"chars": 362054,
"preview": "import { motion } from 'framer-motion';\nimport { Slideshow, motionAnimationProps } from './slideshow';\n\nexport const fra"
},
{
"path": "website/components/back-in-block/combined-block.tsx",
"chars": 486,
"preview": "import { Slideshow } from './slideshow';\nimport {\n frames as staticAnalysisFrames,\n descriptions as staticAnalysisDesc"
}
]
// ... and 199 more files (download for full content)
About this extraction
This page contains the full source code of the aidenybai/million GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 399 files (2.7 MB), approximately 724.7k tokens, and a symbol index with 533 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.